Multiplatform Library Development

Multiplatform Library Development

Multiplatform Kotlin facilitates code-sharing by making platform-agnostic portions of the standard library available in common code that is written once but can run on any target. As Multiplatform development really starts to take off over the next year, there must also be a robust ecosystem of third party libraries available to application developers.

I’ll talk through my experiences building one of the early libraries in the mobile Multiplatform space, with lessons on how to find shared abstractions around different platform APIs, how to handle the fast-paced evolution of this environment, and what this all felt like as a first-time library developer. When we're done, you’ll be ready to leverage the growing ecosystem as well as make your own contributions.

7edddae207b212e5e035133d9eb10b82?s=128

Russell Wolf

August 27, 2019
Tweet

Transcript

  1. 2.

    About Me • @RussHWolf ( or ) • Android Developer

    at Rocket Insights • Organizer of Kotlin Office Hours in Boston • Kotlin/Everywhere Nov 8-9 • Author of Multiplatform Settings
  2. 3.

    Multiplatform Kotlin • Compile common code to multiple targets •

    JVM, JS, Android, Desktop, iOS, Embedded, WASM • Use platform-specific code to access platform APIs
  3. 4.

    Kotlin/Native • Compile Kotlin to LLVM targets • No VM!

    • Desktop, Embedded, Mobile • Interop with C or Objective-C
  4. 6.

    Mobile Multiplatform • Android (JVM) / iOS (Native) • Mobile

    is the killer app for Multiplatform • Same use-case • Similar capabilities
  5. 10.

    Platform-Specific Code interface Foo { ... } class AndroidFoo :

    Foo { ... } class IosFoo : Foo { ... } class SwiftFoo : NsObject, Foo { ... } class MockFoo : Foo { ... }
  6. 19.
  7. 20.
  8. 22.

    Multiplatform Settings interface Settings {
 fun putInt(key: String, value: Int)


    } 
 class AndroidSettings(
 val delegate: SharedPreferences
 ) : Settings { 
 override fun putInt(…) = delegate.putInt(…)
 } 
 class AppleSettings(
 val delegate: NSUserDefaults
 ) : Settings {
 override fun putInt(…) = delegate.setInteger(…)
 }
  9. 23.

    Multiplatform Settings interface Settings {
 fun putInt(key: String, value: Int)


    }
 class MockSettings(
 val delegate: MutableMap<String, Any>
 ) : Settings { 
 override fun putInt(…) = delegate.set(…)
 }
 
 
 
 

  10. 24.

    Multiplatform Settings interface Settings {
 fun putInt(key: String, value: Int)


    }
 class JsSettings(
 val delegate: Storage = localStorage
 ) : Settings { 
 override fun putInt(…) = delegate.set(…)
 }
 class JvmSettings(
 val delegate: Properties
 ) : Settings {
 override fun putInt(…) = delegate.setProperty(…)
 }
  11. 25.

    Multiplatform Settings operator fun Settings.set( key: String, value: Int ):

    Unit = putInt(key, value) settings["a"] = 3 fun Settings.int( key: String? = null, defaultValue: Int = 0 ): ReadWriteProperty<Any?, Int> = ... var a by settings.int("a")
  12. 26.
  13. 27.

    Expect/Actual vs Interface expect class Settings {
 fun putInt(key: String,

    value: Int)
 }
 actual class Settings(
 val delegate: SharedPreferences
 ) { 
 actual fun putInt(…) = delegate.putInt(…)
 }
 actual class Settings(
 val delegate: NSUserDefaults
 ) {
 actual fun putInt(…) = delegate.setInteger(…)
 }
  14. 28.

    Expect/Actual vs Interface interface Settings {
 fun putInt(key: String, value:

    Int)
 } expect class PlatformSettings: Settings {
 override fun putInt(key: String, value: Int)
 }
 actual class PlatformSettings(
 val delegate: SharedPreferences
 ) : Settings { 
 actual fun putInt(…) = delegate.putInt(…)
 }
 actual class PlatformSettings(…) { … }
  15. 29.

    Expect/Actual vs Interface interface Settings {
 fun putInt(key: String, value:

    Int)
 } 
 class AndroidSettings(
 val delegate: SharedPreferences
 ) : Settings { 
 override fun putInt(…) = delegate.putInt(…)
 } 
 class AppleSettings(
 val delegate: NSUserDefaults
 ) : Settings {
 override fun putInt(…) = delegate.setInteger(…)
 }
  16. 37.

    Listener APIs • SharedPreferences
 .OnSharedPreferenceChangeListener • Key-specific • Might get

    called for repeated values • NSNotificationCenter
 NSUserDefaultsDidChangeNotification • Can't tell what changed
  17. 38.

    Listener APIs val prev = cache.value val current = delegate.all[key]

    if (prev != current) { callback.invoke() cache.value = current }
 val prev = cache.value val current = delegate.objectForKey(key) if (prev != current) { callback.invoke() cache.value = current }
  18. 39.

    Listener APIs val prev = cache.value val current = delegate.all[key]

    if (prev != current) { callback.invoke() cache.value = current }
 val prev = cache.value val current = delegate.objectForKey(key) if (prev != current) { callback.invoke() cache.value = current } ??? ???
  19. 40.

    Listener APIs val prev = cache.value val current = delegate.all[key]

    if (prev != current) { callback.invoke() cache.value = current }
 val prev = cache.value val current = delegate.objectForKey(key) if (prev != current) { callback.invoke() cache.value = current } ??? ???
  20. 41.

    Listener APIs interface ObservableSettings : Settings { … } class

    AndroidSettings(…) : ObservableSettings { … } class JsSettings(…) : Settings { … }
  21. 42.

    Experimental @Experimental annotation class ExperimentalListener
 
 @ExperimentalListener interface ObservableSettings :

    
 Settings { … }
 
 @Experimental annotation class ExperimentalJs @ExperimentalJs class JsSettings(…) : 
 Settings { … }
  22. 43.

    Experimental @Experimental(level = WARNING) annotation class ExperimentalListener
 
 @ExperimentalListener interface

    ObservableSettings : 
 Settings { … }
 
 @Experimental(level = WARNING) annotation class ExperimentalJs @ExperimentalJs class JsSettings(…) : 
 Settings { … }
  23. 45.

    PRs

  24. 49.

    PRs

  25. 50.

    Gradle • Android • publishAllLibraryVariants() • iOS • exec {

    commandLine(
 "xcrun", "simctl", "spawn", 
 "iPhone Xʀ", testBinaryPath
 ) } • JS • browser()
  26. 51.

    Other Notes • Kotlin/Native has no version compatibility! Keep up-to-

    date to support clients • Gradle is complicated • https://kotlinlang.org/docs/reference/building-mpp- with-gradle.html • Easier than last year
  27. 52.

    Coming Soon • Maven Central • But at least on

    jcenter now... • CI Build • iOS arm32 • JVM auto-persistence • JVM, JS, macOS samples
  28. 53.

    What about other stuff? • Jetbrains • Coroutines • Serialization

    • Ktor Client • IO • ... • Community • SqlDelight • Stately • CoroutineWorker • Reaktive • ...
  29. 55.
  30. 59.

    Thanks! • Questions? • @RussHWolf ( or ) • https://github.com/russhwolf/multiplatform-settings


    • Shameless plug: Kotlin/Everywhere Boston
 https://sessionize.com/kotlin-in-boston-everywhere