Share
Embed
Transcript
61 slides
-
Agenda • Shared Resources • Parcelable • Dependency Injection •
Shared ViewModel • Coroutines • Flow • Build optimizations -
Shared Resources public actual object MR { public actual object
strings : ResourceContainer<StringResource> { public actual val now: StringResource = StringResource(R.string.now) public actual val ago: StringResource = StringResource(R.string.ago) } } -
Shared Resources Initially no region support for localization: fr-CA, es-US,
etc. ‼🚨 Support added in v0.21.0 -
Shared Resources task("renameAndroidSharedResource") { outputs.upToDateWhen { false } doLast {
copy { from("build/generated/moko/androidMain/res/values-fr") into("build/generated/moko/androidMain/res/values-fr-rCA") } delete("build/generated/moko/androidMain/res/values-fr") } dependsOn("generateMRandroidMain") } -
Shared Resources task("renameIosSharedResource") { outputs.upToDateWhen { false } doLast {
copy { from("build/generated/moko/iosMain/res/fr.lproj") into("build/generated/moko/iosMain/res/fr-CA.lproj") } delete("build/generated/moko/iosMain/res/fr.lproj") } dependsOn("generateMRiosMain") } -
Shared Resources tasks.preBuild { if (tasks.findByName("generateMRiosMain") != null) { dependsOn("renameIosSharedResource")
} if (tasks.findByName("generateMRandroidMain") != null) { dependsOn("renameAndroidSharedResource") } } -
Shared Resources - Localization fun getMyString(): StringDesc { return StringDesc.Resource(MR.strings.my_string)
} // Android val string = getMyString().toString(context = this) // iOS let string = getMyString().localized() -
Shared Resources - Formatting fun getMyFormatDesc(input: String): StringDesc { return
MR.strings.my_string_formatted.format(input) } // Android val string = getMyFormatDesc("hello").toString(context = this) // iOS let string = getMyFormatDesc(input: "hello").localized() -
Shared Resources - Helper // commonMain expect class SharedResource {
fun getLocalisedString(string: StringResource, vararg value: Any): String fun getLocalisedString(string: PluralsResource, quantity: Int): String } -
Shared Resources - Helper // androidMain actual class SharedResource(val context:
Application) { actual fun getLocalisedString(string: StringResource, vararg value: Any): String { return string.format(value.asList()).toString(context) } actual fun getLocalisedString(string: PluralsResource, quantity: Int): String { return string.format(quantity, quantity).toString(context) } } -
Shared Resources - Helper // iosMain actual class SharedResource {
actual fun getLocalisedString(string: StringResource, vararg value: Any): String { return string.format(value.asList()).localized() } actual fun getLocalisedString(string: PluralsResource, quantity: Int): String { return string.format(quantity, quantity).localized() } } -
Shared Resources - Helper // Android val resources = SharedResources(context)
val text = resources.getLocalisedString(MR.string.my_string) // iOS let resources = SharedResources() let string = resources.getLocalisedString(MR.strings().my_string()) -
Parcelable // commonMain expect annotation class Parcelize() expect interface Parcelable
// androidMain actual typealias Parcelize = Parcelize actual typealias Parcelable = Parcelable // iosMain actual interface Parcelable actual annotation class Parcelize -
Dependency Injection - Earlier // commonMain open class ChatMessageChannelViewModelImpl( private
val messageChannelManager: MessageChannelManagerInterface ) : SharedViewModel(), ChatMessageChannelViewModel -
Dependency Injection - Earlier // commonMain open class ChatMessageChannelViewModelImpl( private
val messageChannelManager: MessageChannelManagerInterface ) : SharedViewModel(), ChatMessageChannelViewModel // android @HiltViewModel class ChatMessageChannelViewModelWrapper @Inject constructor( private val messageChannelManager: MessageChannelManagerInterface, ) : ChatMessageChannelViewModelImpl( messageChannelManager ) -
Dependency Injection - Now // commonMain actual annotation class KMMViewModel
// androidMain actual typealias KMMViewModel = HiltViewModel -
Dependency Injection - Now // commonMain actual annotation class KMMViewModel
// androidMain actual typealias KMMViewModel = HiltViewModel // commonMain @KMMViewModel class ChatMessageChannelViewModelImpl( private val messageChannelManager: MessageChannelManagerInterface ) : SharedViewModel(), ChatMessageChannelViewModel -
Shared ViewModel class CloseableCoroutineScope(context: CoroutineContext) : CoroutineScope { override val
coroutineContext: CoroutineContext = context fun close() { coroutineContext.cancel() } } -
Shared ViewModel // commonMain expect abstract class SharedViewModel { internal
val sharedScope: CloseableCoroutineScope protected open fun onCleared() } -
Shared ViewModel // androidMain actual abstract class SharedViewModel : ViewModel()
{ actual val sharedScope = CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main) actual override fun onCleared() { super.onCleared() sharedScope.close() } } -
Shared ViewModel // iosMain actual abstract class SharedViewModel { actual
val sharedScope = CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main) protected actual open fun onCleared() { sharedScope.close() } } -
Coroutines // Kotlin suspend fun asyncFunc() = coroutineScope { launch
{ delay(1000L) println("World!") } println("Hello") } // Swift func test() { PlatformKt.asyncFunc(completionHandler: { _ in print("Completed") }) } -
Coroutines - Dispatchers.IO // commonMain expect fun ktIoDispatcher(): CoroutineDispatcher //
androidMain actual fun ktIoDispatcher(): CoroutineDispatcher { return Dispatchers.IO } -
Coroutines - Dispatchers.IO // commonMain expect fun ktIoDispatcher(): CoroutineDispatcher //
androidMain actual fun ktIoDispatcher(): CoroutineDispatcher { return Dispatchers.IO } // iosMain // coroutines 1.7.0-beta private val iosIoDispatcher = newFixedThreadPoolContext(64, "Dispatchers.IO") actual fun ktIoDispatcher(): CoroutineDispatcher { return iosIoDispatcher } -
Be careful // creates a new thread pool everytime actual
fun ktIoDispatcher(): CoroutineDispatcher { return newFixedThreadPoolContext(64, "Dispatchers.IO") } -
Observable State interface ChatViewModel { val uiState: KLiveData<ChatUIState> fun method1()
fun asyncMethod2() } KLiveData: https://github.com/florent37/Multiplatform-LiveData -
KFlow open class KStateFlow<T>( private val initialValue: T? = null
) { protected val _flow = MutableStateFlow(initialValue) val flow: StateFlow<T?> = _flow open val value: T? get() = _flow.value } -
KFlow class KMutableStateFlow<T>( private val initialValue: T? = null )
: KStateFlow<T>(initialValue) { override var value: T? = super._flow.value get() = super._flow.value set(value) { field = value _flow.value = value } } -
KFlow open class KStateFlow<T>( private val initialValue: T? = null
) { fun collect(scope: KCoroutineScopeInterface, block: (T?) -> Unit) { scope.launch { flow.filterNotNull() .collect { block.invoke(it) } } } } -
KFlow - iOS class BaseViewController: UIViewController { let scope =
KCoroutineScope() deinit { scope.cancel() } } -
Flow on iOS kmmViewModel.uiState.collect(scope: scope) {[weak self] uiState in guard
let uiState = chatUiState else { return } self?.handleKMMUIState(uiState) } -
Build Optimizations • We won’t want to run iOS tasks
when running CI for Android • We won’t want to run Android tasks when running CI for iOS -
Build Optimizations object EnvParams { val disable_iOS: Boolean get() =
System.getProperty("disable_iOS") != null val disable_android: Boolean get() = System.getProperty("disable_android") != null } enum class HostType { MAC_OS, ANDROID } -
Build Optimizations fun KotlinTarget.getHostType(): HostType? = when (platformType) { KotlinPlatformType.androidJvm
-> HostType.ANDROID KotlinPlatformType.native -> when { name.startsWith("ios") -> HostType.MAC_OS name.startsWith("watchos") -> HostType.MAC_OS else -> error("Unsupported native target: $this") } else -> null } -
Build Optimizations fun KotlinTarget.isCompilationAllowed(): Boolean { if (!EnvParams.disable_iOS && !EnvParams.disable_android)
{ return true } else if (getHostType() == HostType.ANDROID && EnvParams.disable_android) { return false } return !(getHostType() == HostType.MAC_OS && EnvParams.disable_iOS) } -
Build Optimizations fun disableKapt(project: Project) { project.tasks.withType<KaptGenerateStubs>().configureEach { enabled =
false } project.tasks.withType<KaptTask>().configureEach { enabled = false } } -
Build Optimizations fun KotlinTarget.disableCompilationsIfNeeded(project: Project) { if (!isCompilationAllowed()) { disableCompilations()
} if (EnvParams.disable_android) { disableKapt(project) } } -
Build Optimizations // build.gradle.kts kotlin { targets.configureEach { disableCompilationsIfNeeded(project) }
} https://github.com/arkivanov/gradle-setup-plugin -
Share your feedback to help us better understand your KotlinConf’23
Global experience!