Git Product home page Git Product logo

moko-resources's Introduction

moko-resources
GitHub license Download kotlin-version badge badge badge badge badge badge badge badge

Mobile Kotlin resources

This is a Kotlin MultiPlatform library (and Gradle plugin) that provides access to the resources on macOS, iOS, Android the JVM and JS/Browser with the support of the default system localization.

Also MOKO resources supports Compose Multiplatform so you can implement all your UI in Kotlin with Jetpack Compose and MOKO resources.

Table of Contents

Features

  • Strings, Plurals to access the corresponding resources from common code;
  • Colors with light/dark mode support;
  • Compose Multiplatform support;
  • Images support (svg, png, jpg);
  • Fonts support (ttf, otf);
  • Files support (as raw or assets for android);
  • StringDesc for lifecycle-aware access to resources and unified localization on both platforms;
  • Static iOS frameworks support;
  • Fat and XC frameworks support.

Requirements

  • Gradle version 7.5+
  • Android Gradle Plugin 7.4.2+
  • Android API 16+
  • iOS version 11.0+

Installation

Gradle setup

root build.gradle

buildscript {
    repositories {
        gradlePluginPortal()
    }

    dependencies {
        classpath "dev.icerock.moko:resources-generator:0.23.0"
    }
}


allprojects {
    repositories {
        mavenCentral()
    }
}

project build.gradle

apply plugin: "dev.icerock.mobile.multiplatform-resources"

dependencies {
    commonMainApi("dev.icerock.moko:resources:0.23.0")
    commonMainApi("dev.icerock.moko:resources-compose:0.23.0") // for compose multiplatform

    commonTestImplementation("dev.icerock.moko:resources-test:0.23.0")
}

multiplatformResources {
    multiplatformResourcesPackage = "org.example.library" // required
    multiplatformResourcesClassName = "SharedRes" // optional, default MR
    multiplatformResourcesVisibility = MRVisibility.Internal // optional, default Public
    iosBaseLocalizationRegion = "en" // optional, default "en"
    multiplatformResourcesSourceSet = "commonClientMain"  // optional, default "commonMain"
}

Export classes to Swift

To use toUIColor(), toUIImage(), desc() and other iOS extensions from Swift - you should add export declarations:

framework {
    export("dev.icerock.moko:resources:0.23.0")
    export("dev.icerock.moko:graphics:0.9.0") // toUIColor here
}

Multi-module Gradle projects

If you have multiple gradle modules and resources stored not in module that compiles into framework for iOS, for example:

- shared
-- resources
-- feature-1
-- feature-2

You should enable moko-resources gradle plugin in resources module, that contains resources, AND in shared module, that compiles into framework for iOS (same for jvm, JS, macos targets. Only android will works without this).

Xcode setup

In iOS/macOS Info.plist need to add localizations, to use localizations strings.

<key>CFBundleLocalizations</key><array>
<string>en</string>
<string>ru</string>
</array>

in array should be added all used languages.

Android build types

If your project includes a build type, for example staging which isn't in moko-resources. That isn't an issue. Use matchingFallbacks to specify alternative matches for a given build type, as shown below

buildTypes {
    staging {
        initWith debug
        matchingFallbacks = ['debug']
    }
}

JS Webpack

JS/Browser generates json files which is included in webpack by default. For more details about JS see samples/resources-gallery/web-app sample

iOS/macOS static kotlin frameworks support

Static framework can't have own resources, so we should setup additional Build Phase in Xcode that will copy resources to application.

Please replace :yourframeworkproject to kotlin project gradle path, and set correct relative path ($SRCROOT/../ in example).

With org.jetbrains.kotlin.native.cocoapods

In Xcode add Build Phase (at end of list) with script:

"$SRCROOT/../gradlew" -p "$SRCROOT/../" :yourframeworkproject:copyFrameworkResourcesToApp \
    -Pmoko.resources.BUILT_PRODUCTS_DIR="$BUILT_PRODUCTS_DIR" \
    -Pmoko.resources.CONTENTS_FOLDER_PATH="$CONTENTS_FOLDER_PATH" \
    -Pkotlin.native.cocoapods.platform="$PLATFORM_NAME" \
    -Pkotlin.native.cocoapods.archs="$ARCHS" \
    -Pkotlin.native.cocoapods.configuration="$CONFIGURATION" 

Without org.jetbrains.kotlin.native.cocoapods

In Xcode add Build Phase (at end of list) with script:

"$SRCROOT/../gradlew" -p "$SRCROOT/../" :yourframeworkproject:copyFrameworkResourcesToApp \
    -Pmoko.resources.PLATFORM_NAME="$PLATFORM_NAME" \
    -Pmoko.resources.CONFIGURATION="$CONFIGURATION" \
    -Pmoko.resources.ARCHS="$ARCHS" \
    -Pmoko.resources.BUILT_PRODUCTS_DIR="$BUILT_PRODUCTS_DIR" \
    -Pmoko.resources.CONTENTS_FOLDER_PATH="$CONTENTS_FOLDER_PATH" 

Disable warning about static framework usage

To disable warnings about static framework in gradle set flag:

multiplatformResources {
    disableStaticFrameworkWarning = true
}

iOS executable

When you use executable kotlin target you should add custom build phase to xcode, after kotlin compilation:

"$SRCROOT/../gradlew" -p "$SRCROOT/../" :shared:copyResourcesDebugExecutableIosSimulatorArm64 \
    -Pmoko.resources.BUILT_PRODUCTS_DIR=$BUILT_PRODUCTS_DIR \
    -Pmoko.resources.CONTENTS_FOLDER_PATH=$CONTENTS_FOLDER_PATH

copyResourcesDebugExecutableIosSimulatorArm64 should be configured depends on target.

Configured sample you can see in samples/kotlin-ios-app

Creating Fat Framework with resources

Just use FatFrameworkTask from kotlin plugin .

Creating XCFramework with resources

Just use XCFramework from kotlin plugin .

But if you use static frameworks required additional setup - add to Xcode build phase (at end):

"$SRCROOT/../gradlew" -p "$SRCROOT/../" :shared:copyResourcesMPLReleaseXCFrameworkToApp \
    -Pmoko.resources.BUILT_PRODUCTS_DIR=$BUILT_PRODUCTS_DIR \
    -Pmoko.resources.CONTENTS_FOLDER_PATH=$CONTENTS_FOLDER_PATH

Details you can check in sample samples/ios-static-xcframework.

Usage

Example 1 - simple localization string

The first step is a create a file strings.xml in commonMain/resources/MR/base with the following content:

<?xml version="1.0" encoding="UTF-8" ?>
<resources>
    <string name="my_string">My default localization string</string>
</resources>

Next - create a file strings.xml with localized strings in commonMain/resource/MR/<languageCode>. Here's an example of creating commonMain/resource/MR/ru for a Russian localization:

<?xml version="1.0" encoding="UTF-8" ?>
<resources>
    <string name="my_string">Моя строка локализации по умолчанию</string>
</resources>

After adding the resources we can call a gradle sync or execute a gradle task generateMRcommonMain. This will generate a MR class containing MR.strings.my_string, which we can use in commonMain:

fun getMyString(): StringDesc {
    return StringDesc.Resource(MR.strings.my_string)
}

After this we can use our functions on the platform side:
Android:

val string = getMyString().toString(context = this)

iOS:

let string = getMyString().localized()

JS:

val strings = MR.stringsLoader.getOrLoad() // loading localization from a remote file
val string = getMyString().localized(strings)

Note: StringDesc is a multiple-source container for Strings: in StringDesc we can use a resource, plurals, formatted variants, or raw string. To convert StringDesc to String on Android call toString(context) (a context is required for the resources usage), on iOS - call localized().

Compose Multiplatform

with compose you can just call in commonMain

val string: String = stringResource(MR.strings.my_string)

MR directly from native side

Android:

val string = MR.strings.my_string.desc().toString(context = this)

iOS:

let string = MR.strings().my_string.desc().localized()

Get resourceId for Jetpack Compose / SwiftUI

Android:

val resId = MR.strings.my_string.resourceId

for example in Compose:

text = stringResource(id = MR.strings.email.resourceId)

iOS SwiftUI:

let resource = MR.strings().email
Text(
    LocalizedStringKey(resource.resourceId),
    bundle: resource.bundle
)

Note: more info in issue #126.

Example 2 - formatted localization string

In commonMain/resources/MR/base/strings.xml add:

<?xml version="1.0" encoding="UTF-8" ?>
<resources>
    <string name="my_string_formatted">My format \'%s\'</string>
</resources>

Then add the localized values for other languages like in example #1. Now create the following function in commonMain:

fun getMyFormatDesc(input: String): StringDesc {
    return StringDesc.ResourceFormatted(MR.strings.my_string_formatted, input)
}

To create formatted strings from resources you can also use extension format:

fun getMyFormatDesc(input: String): StringDesc {
    return MR.strings.my_string_formatted.format(input)
}

Now add support on the platform side like in example #1:
Android:

val string = getMyFormatDesc("hello").toString(context = this)

iOS:

let string = getMyFormatDesc(input: "hello").localized()

Warning: Do no mix positioned placeholders with unpositioned ones within a string, as this may lead to different behaviour on different platforms. Stick to one style for each string.

Example 3 - plural string

The first step is to create a file plurals.xml in commonMain/resources/MR/base with the following content:

<?xml version="1.0" encoding="UTF-8" ?>
<resources>
    <plural name="my_plural">
        <item quantity="zero">zero</item>
        <item quantity="one">one</item>
        <item quantity="two">two</item>
        <item quantity="few">few</item>
        <item quantity="many">many</item>
        <item quantity="other">other</item>
    </plural>
</resources>

Then add the localized values for other languages like in example #1.
Next, create a function in commonMain:

fun getMyPluralDesc(quantity: Int): StringDesc {
    return StringDesc.Plural(MR.plurals.my_plural, quantity)
}

Now add support on the platform side like in example #1:
Android:

val string = getMyPluralDesc(10).toString(context = this)

iOS:

let string = getMyPluralDesc(quantity: 10).localized()

Example 4 - plural formatted string

The first step is to create file plurals.xml in commonMain/resources/MR/base with the following content:

<?xml version="1.0" encoding="UTF-8" ?>
<resources>
    <plural name="my_plural">
        <item quantity="zero">no items</item>
        <item quantity="one">%d item</item>
        <item quantity="two">%d items</item>
        <item quantity="few">%d items</item>
        <item quantity="many">%d items</item>
        <item quantity="other">%d items</item>
    </plural>
</resources>

Then add the localized values for other languages like in example #1.
Next, create a function in commonMain:

fun getMyPluralFormattedDesc(quantity: Int): StringDesc {
    // we pass quantity as selector for correct plural string and for pass quantity as argument for formatting
    return StringDesc.PluralFormatted(MR.plurals.my_plural, quantity, quantity)
}

To create formatted plural strings from resources you can also use extension format:

fun getMyPluralFormattedDesc(quantity: Int): StringDesc {
    // we pass quantity as selector for correct plural string and for pass quantity as argument for formatting
    return MR.plurals.my_plural.format(quantity, quantity)
}

And like in example #1, add the platform-side support:
Android:

val string = getMyPluralFormattedDesc(10).toString(context = this)

iOS:

let string = getMyPluralFormattedDesc(quantity: 10).localized()

Example 5 - pass raw string or resource

If we already use some resources as a placeholder value, we can use StringDesc to change the string source:

fun getUserName(user: User?): StringDesc {
    if (user != null) {
        return StringDesc.Raw(user.name)
    } else {
        return StringDesc.Resource(MR.strings.name_placeholder)
    }
}

And just like in example 1 usage on platform side:
Android:

val string1 = getUserName(user).toString(context = this) // we got name from User model
val string2 = getUserName(null).toString(context = this) // we got name_placeholder from resources

iOS:

let string1 = getUserName(user: user).localized() // we got name from User model
let string2 = getUserName(user: null).localized() // we got name_placeholder from resources

Example 6 - Select localization in runtime

You can force StringDesc to use preferred localization in common code:

StringDesc.localeType = StringDesc.LocaleType.Custom("es")

and return to system behaviour (when localization depends on device settings):

StringDesc.localeType = StringDesc.LocaleType.System

Android:

Add this to your app's build.gradle to keep all locales in resulting App Bundle if you want them all to be available in runtime (Otherwise, when the user downloads the app from PlayMarket, resources for his system locale only will be available).

android {
    bundle {
        language {
            enableSplit = false
        }
    }
}

Example 7 - Shared Images

Place images in the commonMain/resources/MR/images directory. Nested directories are also supported.

png and jpg

Image names should end with one of:

  • @0.75x - android ldpi;
  • @1x - android mdpi, ios 1x;
  • @1.5x - android hdpi;
  • @2x - android xhdpi, ios 2x;
  • @3x - android xxhdpi, ios 3x;
  • @4x - android xxxhdpi.

If we add the following files to commonMain/resources/MR/images:

Then we get an autogenerated MR.images.home_black_18 ImageResource in code. Usage:

  • Android: imageView.setImageResource(image.drawableResId)
  • iOS: imageView.image = image.toUIImage()

svg

The Image generator also supports svg files.

If we add the following file to commonMain/resources/MR/images:

  • car_black.svg

Then we get an autogenerated MR.images.car_black ImageResource in code. Usage:

  • Android: imageView.setImageResource(image.drawableResId)
  • iOS: imageView.image = image.toUIImage()

On Android it is a VectorDrawable,

On iOS iOS 13 or later it is a UIImage in the Assets catalog with preserves-vector-representation set to true.

images by name

You can get images by their name, too.

In commonMain create a Resources.kt file with the content below.

fun getImageByFileName(name: String): ImageResource {
    val fallbackImage = MR.images.transparent
    return MR.images.getImageByFileName(name) ?: fallbackImage
}

Usage:

  • Android: imageView.setImageResource(getImageByFileName("image_name"))
  • iOS: imageView.image = ResourcesKt.getImageByFileName(name: "image_name").toUIImage()!

Compose Multiplatform

With compose, you can simply use a painterResource in commonMain

val painter: Painter = painterResource(MR.images.home_black_18)

SwiftUI

For SwiftUI, create this Image extension:

extension Image {
    init(resource: KeyPath<MR.images, ImageResource>) {
        self.init(uiImage: MR.images()[keyPath: resource].toUIImage()!)
    }
}

Then, you can refer to ImageResources directly by their key path, which provides compiler errors for typos or missing resources:

Image(resource: \.home_black_18)

Example 8 - pass font

Fonts resources directory is commonMain/resources/MR/fonts.
Font name should be this pattern: <fontFamily>-<fontStyle> like:

  • Raleway-Bold.ttf
  • Raleway-Regular.ttf
  • Raleway-Italic.ttf Supports ttf and otf resources.

If we add to commonMain/resources/MR/fonts files:

  • Raleway-Bold.ttf
  • Raleway-Regular.ttf
  • Raleway-Italic.ttf

We got autogenerated MR.fonts.Raleway.italic, MR.fonts.Raleway.regular, MR.fonts.Raleway.bold FontResource in code, that we can use:

  • Android: textView.typeface = font.getTypeface(context = this)
  • iOS: textView.font = font.uiFont(withSize: 14.0)

Compose Multiplatform

with compose you can just call in commonMain

val fontFamily: FontFamily = fontFamilyResource(MR.fonts.Raleway.italic)

or you can get Font

val font: Font = MR.fonts.Raleway.italic.asFont(
  weight = FontWeight.Normal, // optional
  style = FontStyle.Normal // optional
)

Example 9 - pass colors

Colors resources directory is commonMain/resources/MR/colors.
Colors files is xml with format:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- format: #RRGGBB[AA] or 0xRRGGBB[AA] or RRGGBB[AA] where [AA] - optional -->
    <color name="valueColor">#B02743FF</color>
    <color name="referenceColor">@color/valueColor</color>
    <color name="themedColor">
        <light>0xB92743FF</light>
        <dark>7CCFEEFF</dark>
    </color>
    <color name="themedReferenceColor">
        <light>@color/valueColor</light>
        <dark>@color/referenceColor</dark>
    </color>
</resources>

If you want use one color without light/dark theme selection:

<color name="valueColor">#B02743FF</color>

If you want use value of other color - use references:

<color name="referenceColor">@color/valueColor</color>

If you want different colors in light/dark themes:

<color name="themedColor">
    <light>0xB92743FF</light>
    <dark>7CCFEEFF</dark>
</color>

Also themed colors can be referenced too:

<color name="themedReferenceColor">
    <light>@color/valueColor</light>
    <dark>@color/referenceColor</dark>
</color>

Colors available in common code insode MR.colors.** as ColorResource.
ColorResource can be read from platform side:

android:

val color: Int = MR.colors.valueColor.getColor(context = this)

iOS:

val color: UIColor = MR.colors.valueColor.getUIColor()

macOS:

val color: NSColor = MR.colors.valueColor.getNSColor()

jvm:

val light: Color = MR.colors.valueColor.lightColor
val dark: Color = MR.colors.valueColor.darkColor

web:

val light: Color = MR.colors.valueColor.lightColor
val dark: Color = MR.colors.valueColor.darkColor

Compose Multiplatform

with compose you can just call in commonMain

val color: Color = colorResource(MR.colors.valueColor)

Example 10 - plain file resource access

The first step is a create a resource file test.txt for example, in commonMain/resources/MR/files After gradle sync we can get file by id MR.files.test Moko-resources has out of box implementation function for read text files from common code - readText()

Usage on Android:

val text = MR.files.test.getText(context = this)

Usage on Apple:

val text = MR.files.test.readText()

If you want to read files not as text, add your own implementation to expect/actual FileResource

Compose Multiplatform

with compose you can just call in commonMain

val fileContent: String? by MR.files.test.readTextAsState()

Example 11 - assets access

Assets allow you save directories hierarchy (in files structure is plain). Locate files to commonMain/resources/MR/assets and access to it by MR.assets.*

Compose Multiplatform

with compose you can just call in commonMain

val assetContent: String? by MR.assets.test.readTextAsState()

Known issues

iOS shows key instead of localized text

  1. check that generated Localizable.strings file is valid - open it by Xcode (located in shared/shared/build/bin/iosSimulatorArm64/debugFramework/shared.framework/<project-name>:shared.bundle/Contents/Resources/Base.lproj/Localizable.strings and in other .lproj directories. If Xcode show error in file - you should fix content of strings.xml (for example you use some special character that broke file).

  2. check that your generated .bundle exist inside application at runtime. In Xcode inside group Products select your application and click Show in Finder. Then click Show Package Contents. Inside .app you should see .bundle in root directory if you use static framework. And in Frameworks/shared.framework if you use dynamic framework. If bundle missed - check installation guide. Specifically xcode build phase part if you use static framework. And check that you apply moko-resources plugin in shared gradle module.

  3. check that your strings.xml contains all keys for language that you use. If you have keys test1, test2 in Base/strings.xml, and only test1 in ru/strings.xml then you got key instead of text in ru locale for test2 key. iOS not fallback to base locale now

Samples

In samples directory you can find multiple projects showed different usages.

Set Up Locally

In root of repository contains moko-resources gradle project - libraries and gradle plugin. You can just open project in IDE and develop. Then for tests in samples you should run run ./gradlew publishToMavenLocal gradle task. After this you can open any sample from samples in IDE and test your local version of moko-resources.

To check your changes before pull request run:

# check lib & plugin
./local-check.sh
# check samples
./local-samples-check.sh

Contributing

All development (both new features and bug fixes) is performed in the develop branch. This way master always contains the sources of the most recently released version. Please send PRs with bug fixes to the develop branch. Documentation fixes in the markdown files are an exception to this rule. They are updated directly in master.

The develop branch is pushed to master on release.

For more details on contributing please see the contributing guide.

License

Copyright 2019 IceRock MAG Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

moko-resources's People

Contributors

a-blekot avatar alex009 avatar andreabusi avatar anton6tak avatar asapha avatar atchernov avatar chrisbanes avatar cilestal avatar darronschall avatar deltaagent00 avatar devtchernov avatar dgluhovic avatar dorofeev avatar insanusmokrassar avatar intektor avatar javiersegoviacordoba avatar jittya avatar kevincianfarini avatar krottv avatar maverick-2013 avatar mustafaozhan avatar paulwoitaschek avatar sham0688 avatar syer10 avatar tetraquark avatar tkashkin avatar vchernyshov avatar wakaztahir avatar warnyul avatar y9san9 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

moko-resources's Issues

Localized strings is not available on iOS if iOS-framework was compiled by Gradle FatFramework task

When I use Gradle FatFramework task, I can't see any of my localized strings on iOS side
The task I use to compile my framework:

val fatFramework by tasks.creating(FatFrameworkTask::class) {
    baseName = "multiplatform"

    val targets = listOf(
            kotlin.targets.getByName<KotlinNativeTarget>("iosX64"),
            kotlin.targets.getByName<KotlinNativeTarget>("iosArm64")
    )

    from(targets.map { it.binaries.getFramework("DEBUG") })
}

If I change it to Gradle Sync task it works as expected (I see all my strings on iOS side):

val syncFramework by tasks.creating(Sync::class) {
    val targetDir = File(buildDir, "frameworks")
    val mode = "DEBUG"
    val framework = kotlin.targets
            .getByName<KotlinNativeTarget>("ios")
            .binaries.getFramework(mode)
    inputs.property("mode", mode)
    dependsOn(framework.linkTask)
    from({ framework.outputDirectory })
    into(targetDir)
}

Kotlin 1.3.72, moko-resources 0.10.0 (had the same issue on 0.9.1)

Do you have any ideas what a cause of the issue? And am I able to fix it?

Automatically lowercase android resources filenames

Now for image names like 'logoutIcon.png' android show error:

'I' is not a valid file-based resource name character: File-based resource names must contain only lowercase a-z, 0-9, or underscore

we should automatically convert common resource name to valid on platform side

Object 'MR' has several compatible declarations in modules MyLib_iosArm64, MyLib_iosX64Main

I'm using the latest version of the lib "0.9.1".

My build gradle is as follows:

kotlin {
    iosX64() {
        binaries.framework {
            baseName = "MyLib"
            isStatic = false
        }
    }
    iosArm64() {
        binaries.framework {
            baseName = "MyLib"
            isStatic = false
        }
    }
    sourceSets {
        ...
        val iosX64Main by getting
        val iosX64Test by getting
        val iosArm64Main by getting {
            dependsOn(iosX64Main)
        }
        val iosArm64Test by getting {
            dependsOn(iosX64Test)
        }

        configure(
            listOf(
                iosArm64Main,
                iosX64Main
            )
        ) {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$coroutines_version")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-properties-native:$serialization_version")
                implementation("io.ktor:ktor-client-ios:$ktor_version")
                implementation("io.ktor:ktor-client-core-native:$ktor_version")
                implementation("io.ktor:ktor-client-serialization-native:$ktor_version")
            }
        }
    }
}

After that I updated to (the target def remains the same, only renamed the folder to iosMain from iosX64Main):

val iosMain by creating {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$coroutines_version")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-properties-native:$serialization_version")
                implementation("io.ktor:ktor-client-ios:$ktor_version")
                implementation("io.ktor:ktor-client-core-native:$ktor_version")
                implementation("io.ktor:ktor-client-serialization-native:$ktor_version")
            }
        }

        val iosX64Main by getting {
            dependsOn(iosMain)
        }

        val iosArm64Main by getting {
            dependsOn(iosMain)
        }
        val iosX64Test by getting
        val iosArm64Test by getting

And now when building I'm getting:
Expected object 'MR' has no actual declaration in module <MyLib_test> for Native

Drawables generation in MR gradle-plugin

Add in gradle-plugin support of drawables object generation with DrawableResources and placing image assets to correct folders:
android: drawable (for vector), drawable-**dpi (for bitmaps)
ios: Assets.xcassets directory & compilation of directory (not confirmed that it necessary, but should be researched)

Duplicated actual MR class when used hierarchically source sets

if we configure project like this:

iosX64()   // for iOS Simulator
    iosArm64() // for iOS arm64 devices
    sourceSets {
        iosMain {
            dependencies {
                implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$coroutines_version"
                implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version"
                implementation "org.jetbrains.kotlinx:kotlinx-serialization-properties-native:$serialization_version"
                implementation "io.ktor:ktor-client-ios:$ktor_version"
                implementation "io.ktor:ktor-client-core-native:$ktor_version"
                implementation "io.ktor:ktor-client-serialization-native:$ktor_version"
            }
        }
        configure([targets.iosX64, targets.iosArm64]) {
            compilations.main {
                source(sourceSets.iosMain)
            }
        }
    }

we have sourceSets:

  • iosX64Main
  • iosArm64Main
  • iosMain

and plugin generate sources for all. but targets use multiple sourceSets in same time:

  • to build iosX64 we use iosX64Main + iosMain
  • to build iosArm64 we use iosArm64Main + iosMain

it's reason why we got Object 'MR' has several compatible actual declarations in modules...

Add integration tests

We have in commonMain/resources set of strings and plurals and changes of library can broke some functional like formatting or usage of special symbols.
Need implement test, which automatically build sample app and check that strings in textview is exactly what should be by test data. And check it on different languages of device.

equals no longer working correctly on StringDesc

With the recent changes in #62 the various StringDesc subclasses are now no longer data classes so the equals method is now no longer comparing based on the properties, which is causing our tests to fail.

So before this would be true and now is false because of this:

"a".desc() == "a".desc()

Incorrect generation for strings with special characters

For string with char &, encoded with &amp

    <string name="sell.selectMethod.choosePortfolioAmount">CHOOSE PORTFOLIO &amp; AMOUNT</string>

generated line looks like

    <string name="sell.selectMethod.choosePortfolioAmount">CHOOSE PORTFOLIO & AMOUNT</string>

Android platform get compilation error with this line because of & symbol without encoding

More samples of StringDesc usage

Need more samples of StringDesc in main use cases:

  • mixed source (resources or raw data)
  • lifecycle handling
  • creating from StringResource / PluralResource / Int (on android)

Add colors support

For use light/dark theme feature colors should be defined in resources, so needed: commonMain/resources/MR/colors.xml with:

<colors>
    <color name="white" themed=true>
        <light>0xFFFFFFFF</light>
        <dark>0x000000FF</dark>
    </color>
    <color name="textColor">0xFF00FFFF</color>
</colors>

Custom fonts resources

Required for icerockdev/moko-widgets#6

in commonMain/resources/fonts should be placed custom fonts like:

  • commonMain/resources/fonts/MyFont-regular.ttf
  • commonMain/resources/fonts/MyFont-italic.ttf

and generated into:

object MR {
  object fonts {
    val myFont: FontResource
  }
}

Links:
https://developer.android.com/guide/topics/ui/look-and-feel/fonts-in-xml
https://developer.apple.com/documentation/uikit/text_display_and_fonts/adding_a_custom_font_to_your_app?language=objc

TODO:
think about font weight. Refference from ReactNative: https://github.com/facebook/react-native/blob/master/React/Views/RCTFont.mm

Add a StringDesc type that is a wrapper transform

I have a case where I want to return something like a StringDesc to the UI so that it only has to call localized or toString(context) but where a transformation is ran on the result. The particular case is I have a String defining a date format and the transformation I want to run is use that format as an argument to a format call. I want to define that in common code so it does not know about loacalized or toString(context) and I don't want the code that receives it to have to call the format method.

I would try to define it myself but the fact that StringDesc is sealed makes that impossible

So what I am proposing is a map extension method on StringDesc. So I envision this in the common code:

return MR.strings.my_date_format.desc().map { myDate.format(it).desc() }

So in StringDesc you would need a new class:

class Mapped(stringDesc: StringDesc, transform: (String) -> StringDesc) : StringDesc

You would need extension methods on StringDesc:

fun StringDesc.map(transform: (String) -> StringDesc) = StringDesc.Mapped(this, transform)

And then the Android implementation might be:

 actual data class Mapped actual constructor(
     val stringDesc: StringDesc,
     val transform: (String) -> StringDesc
 ) : StringDesc() {
     override fun toString(context: Context) =
        stringDesc.toString(context).let(transform).toString(context)
 }

Help to get localization to work on iOS

Hi, I setup the library according to the documentation in version 0.9.0 and kotlin 1.3.70.

Now on Android, everything works as expected and I can localize my strings. On iOS though, only the translation key is displayed.

shared lib build.gradle.kts

kotlin {
    //select iOS target platform depending on the Xcode environment variables
    val iOSTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget =
        if (System.getenv("SDK_NAME")?.startsWith("iphoneos") == true)
            ::iosArm64
        else
            ::iosX64

    iOSTarget("ios") {
        binaries {
            framework {
                baseName = "MyLibShared"
                export("dev.icerock.moko:resources:${Versions.Libs.MultiPlatform.mokoResources}")
            }
        }
    }
}
...
multiplatformResources {
    multiplatformResourcesPackage = "jp.mylib.shared"
}

I verified the framework is correctly created and contains the localized files:
Screen Shot 2020-04-01 at 11 59 11

I tried a coupl of approaches to get the string:

// in swift; does not work
Localizer().getMyString().localized() // displays "my_string"
// shared code in iosMain: Localizer object: fun getMyString(): StringDesc = StringDesc.Resource(MR.strings.my_string)

// in swift; does not work; unresolved StringDesc
StringDesc.Resource(MR.strings.my_string).localize()

// in swift; this works though, so the files are loaded
NSLocalizedString("my_string", tableName: nil, bundle: Bundle(identifier: "jp.mylib.shared.MyLibShared")!, value: "", comment: "") // displays "My String!"

Do you have an idea on what the issue could be? Any pointer?

Formatted strings doesn't work properly

I have formatted string in my multiplatform strings that look like:

<string name="myString">%s from %s</string>

When I'm trying to get this string with this code:

StringDesc.ResourceFormatted(MR.strings.myString, arg1, arg2).toString(context)
(arg1 and arg2 are Int)

I'm getting this error:

 java.util.MissingFormatArgumentException: Format specifier '%s'
        at java.util.Formatter.format(Formatter.java:2522)
        at java.util.Formatter.format(Formatter.java:2458)
        at java.lang.String.format(String.java:2883)
        at android.content.res.Resources.getString(Resources.java:481)
        at dev.icerock.moko.resources.desc.ResourceFormattedStringDesc.toString(ResourceFormattedStringDesc.kt:16)
        at co.getfullstack.skill.presentation.common.ResourceProvider.getString(ResourceProvider.kt:29)

Also if I remove one of format argument from my string I will receive strings like this one:
"[Ljava.lang.Object;@6b0dc06 from".

Also, I've tried some variants of string:

  • <string name="myString">%d from %d</string> - java.util.IllegalFormatConversionException: d != [Ljava.lang.Object;, even if I pass Int value.
  • <string name="myString">%1$s from %2$s</string> - java.util.MissingFormatArgumentException: Format specifier '%2$s'

OS: Android
Lib version: 0.9.1, 0.10.0
Kotlin: 1.3.71

Cannot access class 'dev.icerock.moko.resources.desc.StringDesc'

Hello,
i followed the sample project implementation of this library; but when i try to run android app i get this error.
Cannot access class 'dev.icerock.moko.resources.desc.StringDesc'. Check your module classpath for missing or conflicting dependencies.

Unable to resolve dependency in Kotlin MPP

Hello.

I am trying to integrate this library into a project and in iOS, Gradle gives the following error:

Could not determine the dependencies of task ':project:linkDebugFrameworkIosArm32'.
> Could not resolve all task dependencies for configuration ':project:iosArm32CompileKlibraries'.
   > Could not resolve dev.icerock.moko:resources:0.11.0.
     Required by:
         project :project
      > Unable to find a matching variant of dev.icerock.moko:resources:0.11.0:
          - Variant 'android-debugApiElements' capability dev.icerock.moko:resources:0.11.0:
              - Incompatible attribute:
                  - Required org.jetbrains.kotlin.platform.type 'native' and found incompatible value 'androidJvm'.
              - Other attributes:
                  - Found com.android.build.api.attributes.BuildTypeAttr 'debug' but wasn't required.
                  - Found com.android.build.api.attributes.VariantAttr 'debug' but wasn't required.
                  - Found com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' but wasn't required.
                  - Found org.gradle.status 'release' but wasn't required.
                  - Required org.gradle.usage 'kotlin-api' and found compatible value 'java-api'.
                  - Required org.jetbrains.kotlin.native.target 'ios_arm32' but no value provided.
          - Variant 'android-debugRuntimeElements' capability dev.icerock.moko:resources:0.11.0:
              - Incompatible attribute:
                  - Required org.jetbrains.kotlin.platform.type 'native' and found incompatible value 'androidJvm'.
              - Other attributes:
                  - Found com.android.build.api.attributes.BuildTypeAttr 'debug' but wasn't required.
                  - Found com.android.build.api.attributes.VariantAttr 'debug' but wasn't required.
                  - Found com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' but wasn't required.
                  - Found org.gradle.status 'release' but wasn't required.
                  - Required org.gradle.usage 'kotlin-api' and found compatible value 'java-runtime'.
                  - Required org.jetbrains.kotlin.native.target 'ios_arm32' but no value provided.
          - Variant 'android-releaseApiElements' capability dev.icerock.moko:resources:0.11.0:
              - Incompatible attribute:
                  - Required org.jetbrains.kotlin.platform.type 'native' and found incompatible value 'androidJvm'.
              - Other attributes:
                  - Found com.android.build.api.attributes.BuildTypeAttr 'release' but wasn't required.
                  - Found com.android.build.api.attributes.VariantAttr 'release' but wasn't required.
                  - Found com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' but wasn't required.
                  - Found org.gradle.status 'release' but wasn't required.
                  - Required org.gradle.usage 'kotlin-api' and found compatible value 'java-api'.
                  - Required org.jetbrains.kotlin.native.target 'ios_arm32' but no value provided.
          - Variant 'android-releaseRuntimeElements' capability dev.icerock.moko:resources:0.11.0:
              - Incompatible attribute:
                  - Required org.jetbrains.kotlin.platform.type 'native' and found incompatible value 'androidJvm'.
              - Other attributes:
                  - Found com.android.build.api.attributes.BuildTypeAttr 'release' but wasn't required.
                  - Found com.android.build.api.attributes.VariantAttr 'release' but wasn't required.
                  - Found com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' but wasn't required.
                  - Found org.gradle.status 'release' but wasn't required.
                  - Required org.gradle.usage 'kotlin-api' and found compatible value 'java-runtime'.
                  - Required org.jetbrains.kotlin.native.target 'ios_arm32' but no value provided.
          - Variant 'iosArm64-api' capability dev.icerock.moko:resources:0.11.0:
              - Incompatible attribute:
                  - Required org.jetbrains.kotlin.native.target 'ios_arm32' and found incompatible value 'ios_arm64'.
              - Other attributes:
                  - Found org.gradle.status 'release' but wasn't required.
                  - Required org.gradle.usage 'kotlin-api' and found compatible value 'kotlin-api'.
                  - Required org.jetbrains.kotlin.platform.type 'native' and found compatible value 'native'.
          - Variant 'iosX64-api' capability dev.icerock.moko:resources:0.11.0:
              - Incompatible attribute:
                  - Required org.jetbrains.kotlin.native.target 'ios_arm32' and found incompatible value 'ios_x64'.
              - Other attributes:
                  - Found org.gradle.status 'release' but wasn't required.
                  - Required org.gradle.usage 'kotlin-api' and found compatible value 'kotlin-api'.
                  - Required org.jetbrains.kotlin.platform.type 'native' and found compatible value 'native'.
          - Variant 'metadata-api' capability dev.icerock.moko:resources:0.11.0:
              - Incompatible attribute:
                  - Required org.jetbrains.kotlin.platform.type 'native' and found incompatible value 'common'.
              - Other attributes:
                  - Found org.gradle.status 'release' but wasn't required.
                  - Required org.gradle.usage 'kotlin-api' and found compatible value 'kotlin-api'.
                  - Required org.jetbrains.kotlin.native.target 'ios_arm32' but no value provided.
   > Could not resolve dev.icerock.moko:resources-iosarm64:0.11.0.
     Required by:
         project :project
      > Unable to find a matching variant of dev.icerock.moko:resources-iosarm64:0.11.0:
          - Variant 'iosArm64-api' capability dev.icerock.moko:resources-iosarm64:0.11.0:
              - Incompatible attribute:
                  - Required org.jetbrains.kotlin.native.target 'ios_arm32' and found incompatible value 'ios_arm64'.
              - Other attributes:
                  - Found org.gradle.status 'release' but wasn't required.
                  - Required org.gradle.usage 'kotlin-api' and found compatible value 'kotlin-api'.
                  - Required org.jetbrains.kotlin.platform.type 'native' and found compatible value 'native'.
          - Variant 'metadata-api' capability dev.icerock.moko:resources-iosarm64:0.11.0:
              - Incompatible attribute:
                  - Required org.jetbrains.kotlin.platform.type 'native' and found incompatible value 'common'.
              - Other attributes:
                  - Found org.gradle.status 'release' but wasn't required.
                  - Required org.gradle.usage 'kotlin-api' and found compatible value 'kotlin-api'.

My Gradle file, although being in Groovy, it seems to be pretty much correct?

apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.multiplatform'
apply plugin: 'kotlinx-serialization'
apply plugin: 'org.jetbrains.kotlin.native.cocoapods'
apply plugin: "dev.icerock.mobile.multiplatform-resources"


android {
    compileSdkVersion 28

    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 28
    }

    sourceSets {
        main {
            setRoot('src/androidMain')
        }
        release {
            setRoot('src/androidMainRelease')
        }
        debug {
            setRoot('src/androidMainDebug')
        }
        test {
            setRoot('src/androidUnitTest')
        }
        testRelease {
            setRoot('src/androidUnitTestRelease')
        }
        testDebug {
            setRoot('src/androidUnitTestDebug')
        }
    }
}

kotlin {

    version = "0.0.1"

    android("android")

    targets {
        final def buildForDevice = project.findProperty("kotlin.native.cocoapods.target") == "ios_arm"
        if (buildForDevice) {
            iosArm64("iosArm64")
            iosArm32("iosArm32")
        } else {
            iosX64("ios")
        }
    }

    sourceSets {
        commonMain {
            dependencies {
                implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

                implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"

                implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutines_version"

                // resources
                implementation "dev.icerock.moko:resources:$moko_resources_version"
            }
        }

        androidMain {
            dependencies {
                implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

                implementation "io.ktor:ktor-client-android:$ktor_version"
                implementation "io.ktor:ktor-client-okhttp:$ktor_version"
                implementation "io.ktor:ktor-client-serialization-jvm:$ktor_version"

                implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serialization_version"

                implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutines_version"
            }
        }

        iosMain {
            dependencies {
                implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
                implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$coroutines_version"
                implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version"

                // resources
                implementation "dev.icerock.moko:resources-iosarm64:$moko_resources_version"
            }
        }

        iosArm32Main {
            dependsOn iosMain
        }

        iosArm64Main {
            dependsOn iosMain
        }
    }

    cocoapods {
        //version = "0.1.0" // Defaults to "1.0.0-LOCAL"
        homepage = "www.example.com"  // Default to empty
        summary = "..." // Defaults to empty
        frameworkName = "project"
    }

}

multiplatformResources {
    multiplatformResourcesPackage = "com.project.app"
    //iosBaseLocalizationRegion = "en" //optional, default "en"
    //multiplatformResourcesSourceSet = "commonClientMain"  // optional, default "commonMain"
}

I also have enableFeaturePreview("GRADLE_METADATA") in my settings.gradle, removing it causes some problems.
Also, it works well on Android.

Any clues?

Thanks

MR file generator

gradle plugin for MR object generation contains:

  • strings
  • plurals

later - drawables (for now just add to sample how store drawable resources)

plugin should generate from one strings, plurals localized source in commonMain platform-specific resources (android - strings.xml, ios - Localizable.string) and attach to multiplatform project output

detekt not work on CI

detekt not see sources of plugin on CI:
> Task :gradle-plugin:detekt NO-SOURCE
maybe it not work at all on this project. need fix

iosTest fails with Null pointer exception loading bundle

Maybe related to #48 #80 or #97, but translations are working when running the app, but when I try to run iosTest task I get a null pointer exception in this code:

actual object MR {
  private val bundle: NSBundle by lazy {
      NSBundle.loadableBundle("com.genesys.purecloud.wfmshared.MR") }

Presumably because the resource bundle cannot be found.

I cannot make any headway on trying to track this down

Can't access files from Swift

Hello!

This is a very similar issue to #80 .
Regardless, it is being added as Embed&Sign and yet, when I try to access the files through the debugger, I get this:

(lldb) p MR.files().testfile
error: Execution was interrupted, reason: internal c++ exception breakpoint(-4)..
The process has been returned to the state before expression evaluation.
(lldb) p MR.files().testfile
(ResourcesFileResource) $R0 = <uninitialized>
(lldb) p MR.files().testfile()
(String) $R2 = ""
(lldb) 

If I try to access it through the Kotlin MPP shared code, a NullPointerException is guaranteed:

0   KotlinMPPClient                         0x0000000106878d2e kfun:kotlin.Throwable.<init>()kotlin.Throwable + 62
1   KotlinMPPClient                         0x000000010686b8a7 kfun:kotlin.Exception.<init>()kotlin.Exception + 55
2   KotlinMPPClient                         0x000000010686bac7 kfun:kotlin.RuntimeException.<init>()kotlin.RuntimeException + 55
3   KotlinMPPClient                         0x000000010686bce7 kfun:kotlin.NullPointerException.<init>()kotlin.NullPointerException + 55
4   KotlinMPPClient                         0x00000001068ee0e7 ThrowNullPointerException + 119
5   KotlinMPPClient                         0x00000001075dbd47 kfun:dev.icerock.moko.resources.utils.loadableBundle@platform.Foundation.NSBundle.Companion.(kotlin.String)platform.Foundation.NSBundle + 4951
6   KotlinMPPClient                         0x0000000106347302 kfun:com.romeu.project.MR.<init>$lambda-0#internal + 162
7   KotlinMPPClient                         0x0000000106347424 kfun:com.romeu.project.MR.$<init>$lambda-0$FUNCTION_REFERENCE$1.invoke#internal + 132
8   KotlinMPPClient                         0x00000001068c494c kfun:kotlin.native.concurrent.FreezeAwareLazyImpl.getOrInit#internal + 1004
9   KotlinMPPClient                         0x00000001068c5079 kfun:kotlin.native.concurrent.FreezeAwareLazyImpl.<get-value>()T + 393
10  KotlinMPPClient                         0x0000000106346093 kfun:com.romeu.project.MR.<get-bundle>#internal + 323
11  KotlinMPPClient                         0x0000000106346a1b kfun:com.romeu.project.MR.files.<init>()com.romeu.project.MR.files + 427
12  KotlinMPPClient                         0x0000000106c9fb55 InitSharedInstanceStrict + 197
13  KotlinMPPClient                         0x00000001064d079f kfun:com.romeu.project.utils.getResourceFileAsText(com.romeu.project.utils.ResourceMocks)kotlin.String + 1359
14  KotlinMPPClient                         0x000000010639f9e0 kfun:data.manager.Repository.$getFileStringCOROUTINE$28.invokeSuspend(kotlin.Result<kotlin.Any?>)kotlin.Any? + 24960
15  KotlinMPPClient                         0x00000001063a08ab kfun:data.manager.Repository.getFileString(kotlin.Int)kotlin.collections.List<data.models.MyChannel> + 283
16  KotlinMPPClient                         0x00000001063524eb kfun:KotlinMPPClient.$getFileString$lambda-4COROUTINE$3.invokeSuspend#internal + 1291
17  KotlinMPPClient                         0x0000000106352d7c kfun:KotlinMPPClient.$getFileString$lambda-4COROUTINE$3.invoke#internal + 268
18  KotlinMPPClient                         0x0000000106364e5a kfun:KotlinMPPClient.$executeRequest$<anonymous>_3COROUTINE$21.invokeSuspend#internal + 2730
19  KotlinMPPClient                         0x00000001068a4e88 kfun:kotlin.coroutines.native.internal.BaseContinuationImpl.resumeWith(kotlin.Result<kotlin.Any?>) + 712
20  KotlinMPPClient                         0x0000000106f0af66 kfun:kotlinx.coroutines.DispatchedTask.run() + 2614
21  KotlinMPPClient                         0x00000001064cba08 kfun:com.romeu.project.NsQueueDispatcher.dispatch$lambda-0#internal + 88
22  KotlinMPPClient                         0x00000001064cbafe kfun:com.romeu.project.NsQueueDispatcher.$dispatch$lambda-0$FUNCTION_REFERENCE$4.invoke#internal + 62
23  KotlinMPPClient                         0x00000001064cbb5e kfun:com.romeu.project.NsQueueDispatcher.$dispatch$lambda-0$FUNCTION_REFERENCE$4.$<bridge-UNN>invoke()#internal + 62
24  KotlinMPPClient                         0x00000001064cbc47 _6d656f676f73646b_knbridge22 + 183
25  libdispatch.dylib                         0x000000010f8719da _dispatch_call_block_and_release + 12
26  libdispatch.dylib                         0x000000010f872bb6 _dispatch_client_callout + 8
27  libdispatch.dylib                         0x000000010f881050 _dispatch_main_queue_callback_4CF + 1152
28  CoreFoundation                        0x000000010a9e6b8b __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
29  CoreFoundation                        0x000000010a9e16ad __CFRunLoopRun + 2111

Everything works fine on Android and in both platforms the MR stuff generation is done.

What could be causing this?

Thanks.

Edit:

It seems the NullPointerException comes from loadableBundle() extension from Moko-Resources.
But I do not know which line specifically. Could be return NSBundle.bundleWithIdentifier(identifier)!!.

I should also say that the iOS app is being ran on iOS 14.

Build fails if it is not running on MacOS

Execution failed for task ':mppLibrary:generateMRiosMain'.
Caused by: org.gradle.api.UncheckedIOException: java.io.IOException: Cannot run program "xcrun" (in directory "/home/.../mppLibrary/build/generated/moko/iosMain/res"): error=2, No such file or directory

I fix this by checking the OS, but I think it would be logical to embed something like this in the plugin :)

tasks.whenTaskAdded {
    if (name == "generateMRiosMain") onlyIf {
        org.gradle.internal.os.OperatingSystem.current()!!.isMacOsX
    }
}

Support for other platforms

Have there been any discussions around supporting platforms other than Android and iOS? It'd be nice to have support for, say, macOS.

Platforms list:

  • Android
  • iOS
  • macOS #127
  • JVM #151
  • JS #272
  • Windows
  • Linux
  • watchOS
  • tvOS
  • androidNative
  • WebAssembly

Support localization change in runtime

  • Add locale property in StringDesc companion
  • Support System / Custom(locale: String) values, implement behavior in actual classes
  • Generate additional iOS localization tables (*.strings files) for custom locales

Update readme

  • logo (?)
  • description with goals
  • gif with clear example in action
  • features - strings, plurals, drawables resources support. StringDesc for lifecycle aware resources access
  • simple usage
  • installation steps
  • detailed usage
  • how to contribute
  • license block

Add extension methods for additional StringResource types

You have desc extension methods for easily creating StringResource.Raw, StringResource.Resource, and StringDesc.PluralsResource, but there is none for ResourceFormatted or PluralsResourceFormatted. Would be nice to have a simple way to do composition as well with a separator via extension method.

So I am envisioning methods like this:

    myStringResource.format(a, b)

    myPluralsStringResource.format(count, a, b)

   separatorStringDesc.join(stringDesc1, stringDesc2)

   myStringDescList.join(additionalStringDesc, additionalStringDesc, separator = separatorDesc)

Allow for source set overrides

Thanks for your work on this library, it's great. Would you consider tweaking the gradle plugin to allow for overriding of commonMain as the base source set? Along the lines of dgluhovic@a19d126 . This works for my purpose, would be happy to submit a PR if not too hacky. My use case is, commonMain contains models, constants etc, shared with a backend jvm target not supported by MR. Shared client code is in commonClientMain, would like to set this as the MR base source set.

Shared file resources

Implementation of a feature to share resource files or assets (e.g. config file) from same location for both platforms and for native and common code (if it's possible) is needed.

Can't link iOS framework in offline

when try link in offline got:

Execution failed for task ':mpp-library:linkMultiPlatformLibraryDebugFrameworkIosX64'.
> java.net.UnknownHostException: www.apple.com

* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':mpp-library:linkMultiPlatformLibraryDebugFrameworkIosX64'.
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$3.accept(ExecuteActionsTaskExecuter.java:148)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$3.accept(ExecuteActionsTaskExecuter.java:145)
        at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:191)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:138)
        at org.gradle.api.internal.tasks.execution.ResolveBeforeExecutionStateTaskExecuter.execute(ResolveBeforeExecutionStateTaskExecuter.java:75)
        at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:62)
        at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:108)
        at org.gradle.api.internal.tasks.execution.ResolveBeforeExecutionOutputsTaskExecuter.execute(ResolveBeforeExecutionOutputsTaskExecuter.java:67)
        at org.gradle.api.internal.tasks.execution.StartSnapshotTaskInputsBuildOperationTaskExecuter.execute(StartSnapshotTaskInputsBuildOperationTaskExecuter.java:62)
        at org.gradle.api.internal.tasks.execution.ResolveAfterPreviousExecutionStateTaskExecuter.execute(ResolveAfterPreviousExecutionStateTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:94)
        at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:95)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:73)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:49)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:416)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:406)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:102)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:49)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:43)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:355)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:343)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:336)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:322)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker$1.execute(DefaultPlanExecutor.java:134)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker$1.execute(DefaultPlanExecutor.java:129)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:202)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:193)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:129)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
Caused by: org.gradle.api.UncheckedIOException: java.net.UnknownHostException: www.apple.com
        at org.gradle.internal.UncheckedException.throwAsUncheckedException(UncheckedException.java:61)
        at org.gradle.internal.UncheckedException.throwAsUncheckedException(UncheckedException.java:41)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$5.run(ExecuteActionsTaskExecuter.java:431)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:390)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:373)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.access$200(ExecuteActionsTaskExecuter.java:79)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.execute(ExecuteActionsTaskExecuter.java:210)
        at org.gradle.internal.execution.steps.ExecuteStep.lambda$execute$1(ExecuteStep.java:33)
        at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:33)
        at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:26)
        at org.gradle.internal.execution.steps.CleanupOutputsStep.execute(CleanupOutputsStep.java:58)
        at org.gradle.internal.execution.steps.CleanupOutputsStep.execute(CleanupOutputsStep.java:35)
        at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:48)
        at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:33)
        at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:39)
        at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:73)
        at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:54)
        at org.gradle.internal.execution.steps.CatchExceptionStep.execute(CatchExceptionStep.java:35)
        at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:51)
        at org.gradle.internal.execution.steps.SnapshotOutputsStep.execute(SnapshotOutputsStep.java:45)
        at org.gradle.internal.execution.steps.SnapshotOutputsStep.execute(SnapshotOutputsStep.java:31)
        at org.gradle.internal.execution.steps.CacheStep.executeWithoutCache(CacheStep.java:201)
        at org.gradle.internal.execution.steps.CacheStep.execute(CacheStep.java:70)
        at org.gradle.internal.execution.steps.CacheStep.execute(CacheStep.java:45)
        at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:49)
        at org.gradle.internal.execution.steps.StoreSnapshotsStep.execute(StoreSnapshotsStep.java:43)
        at org.gradle.internal.execution.steps.StoreSnapshotsStep.execute(StoreSnapshotsStep.java:32)
        at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:38)
        at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:24)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:96)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$0(SkipUpToDateStep.java:89)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:54)
        at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:38)
        at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:77)
        at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:37)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:36)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:26)
        at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:90)
        at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:48)
        at org.gradle.internal.execution.impl.DefaultWorkExecutor.execute(DefaultWorkExecutor.java:33)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:117)
        ... 36 more
Caused by: java.net.UnknownHostException: www.apple.com
        at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:647)
        at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1304)
        at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startDTDEntity(XMLEntityManager.java:1270)
        at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.setInputSource(XMLDTDScannerImpl.java:259)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$DTDDriver.dispatch(XMLDocumentScannerImpl.java:1162)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$DTDDriver.next(XMLDocumentScannerImpl.java:1045)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:959)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:602)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:505)
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:841)
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:770)
        at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
        at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:243)
        at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:339)
        at dev.icerock.gradle.generator.IosMRGenerator$apply$$inlined$forEach$lambda$1.execute(IosMRGenerator.kt:79)
        at dev.icerock.gradle.generator.IosMRGenerator$apply$$inlined$forEach$lambda$1.execute(IosMRGenerator.kt:26)
        at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:702)
        at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:669)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$5.run(ExecuteActionsTaskExecuter.java:401)
        ... 81 more


* Get more help at https://help.gradle.org

Error at

Error when building my project

I get the following error:
`> Task :SDK:compileKotlinIos FAILED
w: skipping /Users/dev/.gradle/caches/modules-2/files-2.1/dev.icerock.moko/resources-iosx64/0.9.0/b3ee2c301a8ab2048c8261ebc92c854fa7698d6d/resources.klib. The abi versions don't match. Expected '[17]', found '22'

w: The compiler versions don't match either. Expected '[]', found '1.3.70-release'

e: Could not find "/Users/dev/.gradle/caches/modules-2/files-
2.1/dev.icerock.moko/resources-iosx64/0.9.0/b3ee2c301a8ab2048c8261ebc92c854fa7698d6d/resources.klib" in [/Users/dev/development/my-project/SDK, /Users/dev/.konan/klib, /Users/dev/.konan/kotlin-native-macos-1.3.61/klib/common, /Users/dev/.konan/kotlin-native-macos-1.3.61/klib/platform/ios_x6`

What might be happening?

Support multiple strings files

In Android development, it is quite convenient for larger apps to separate string resources by module/logical unit. Sample would be a common strings.xml, and then strings-about.xml, strings-settings.xml etc.

It would be great for moko resources to support this as well, as it helps organize strings in larger apps with lots of strings.

Missed camelCase in R class

When I add some resources in multiplatform resources with camel case key, e.g. testTest, when, after that, I try to access it from R class or from XML I find string testtest. So the question is why is camelCase is missing after generation and how can I fix that behavior?

Accessing MR strings from Swift

Hi!

I'm testing this plugin and everything works fine, but, from XCode I'm not able to access to MR.strings. values

I would like to use them directly to for passing it as parameter to a Shared function.

Is this possible?

Thanks for your effort!

Document the export necessary to work on iOS in the readme

In Issue #53 we learn that there is an extra export statement that is necessary to get this to work on iOS. After losing an entire day trying to figure out why this was not working on iOS, I think it should definitely be documented in the README

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.