Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin/Native Concurrency for Mobile Multiplatform

Kotlin/Native Concurrency for Mobile Multiplatform

If you're going to build shared Kotlin code with Kotlin/Native, you'll need to understand the concurrency and state model. In this talk, I give you the basics (and a bit more), plus next steps.

Kevin Galligan

June 05, 2020
Tweet

More Decks by Kevin Galligan

Other Decks in Technology

Transcript

  1. kot·lin mul·ti·plat·form /ˌkätˈlin məltiˈplatfôrm,ˌkätˈlin məltīˈplatfôrm/ noun noun: kotlin multiplatform 1.optional,

    natively-integrated, open-source, code sharing platform, based on the popular, modern language kotlin. facilitates non-ui logic availability on many platforms.
  2. class SimpleState { private var i = 0 val currVal:

    Int get() = i fun doStuff(arg: Int) { i += arg } } fun main(){ val s = SimpleState() s.doStuff(22) s.doStuff(33) println("My val is ${s.currVal}") }
  3. Freeze • Recursively freezes everything • One-way (no “unfreeze”) •

    Data may be shared between threads Int String Float MoreData val strData var width SomeData val moreData var count
  4. Int String Float Float String Int MoreData val strData var

    width SomeData val moreData var count
  5. class CounterModel { var count = 0 fun countClicked() {

    count++ background { saveToDb(count) } } }
  6. class CounterModel { var count = 0 fun countClicked() {

    count++ background { saveToDb(count) } } }
  7. class CounterModel { var count = 0 fun countClicked() {

    count++ background { saveToDb(count) } } }
  8. class CounterModel { var count = 0 fun countClicked() {

    count++ background { saveToDb(count) } } }
  9. class CounterModel { var count = 0 fun countClicked() {

    count++ background { saveToDb(count) } } } InvalidMutabilityException: mutation attempt of frozen sample.CounterModel
  10. class CounterModel { var count = 0 fun countClicked() {

    count++ save(count) } private fun save(count:Int) = background { saveToDb(count) } }
  11. class CounterModel { var count = 0 fun countClicked() {

    count++ background { saveToDb(count) } } }
  12. class CounterModel { init { ensureNeverFrozen() } var count =

    0 fun countClicked() { count++ background { saveToDb(count) } } }
  13. class BreedModel( private val viewUpdate: (ItemDataSummary) -> Unit, private val

    errorUpdate: (String) -> Unit ) : BaseModel() { private val dbHelper: DatabaseHelper by inject() private val settings: Settings by inject() init { ensureNeverFrozen() scope.launch { dbHelper.selectAllItems().asFlow() .map { q -> val itemList = q.executeAsList() ItemDataSummary(/* etc */) } .flowOn(Dispatchers.Default) .collect { summary -> viewUpdate(summary) } } }
  14. class BreedModel( private val viewUpdate: (ItemDataSummary) -> Unit, private val

    errorUpdate: (String) -> Unit ) : BaseModel() { private val dbHelper: DatabaseHelper by inject() private val settings: Settings by inject() init { ensureNeverFrozen() scope.launch { dbHelper.selectAllItems().asFlow() .map { q -> val itemList = q.executeAsList() settings.set("Hey", "Bad idea") ItemDataSummary(/* etc */) } .flowOn(Dispatchers.Default) .collect { summary -> viewUpdate(summary) } } }
  15. class BreedModel( private val viewUpdate: (ItemDataSummary) -> Unit, private val

    errorUpdate: (String) -> Unit ) : BaseModel() { private val dbHelper: DatabaseHelper by inject() private val settings: Settings by inject() init { ensureNeverFrozen() scope.launch { dbHelper.selectAllItems().asFlow() .map { q -> val itemList = q.executeAsList() settings.set("Hey", "Bad idea") ItemDataSummary(/* etc */) } .flowOn(Dispatchers.Default) .collect { summary -> viewUpdate(summary) } } }
  16. class BreedModel( private val viewUpdate: (ItemDataSummary) -> Unit, private val

    errorUpdate: (String) -> Unit ) : BaseModel() { private val dbHelper: DatabaseHelper by inject() private val settings: Settings by inject() init { ensureNeverFrozen() scope.launch { dbHelper.selectAllItems().asFlow() .map { q -> val itemList = q.executeAsList() ItemDataSummary(/* etc */) } .flowOn(Dispatchers.Default) .collect { summary -> viewUpdate(summary) } } }
  17. class CounterModel { val count = AtomicInt(0) fun countClicked() {

    count.value++ background { saveToDb(count.value) } } }
  18. class CounterModel { val count = AtomicInt(0) fun countClicked() {

    count.value++ background { saveToDb(count.value) } } }
  19. fun <K, V> IsoMutableMap<K, V>.checkFor2(first: K, second: K, ins: V)=

    access { map -> if (map.containsKey(first)) map.put(first, ins) else map.put(second, ins) }
  20. expect fun <T> T.freeze(): T expect val <T> T.isFrozen: Boolean

    expect fun Any.ensureNeverFrozen() expect annotation class Throws
  21. Switching Threads use withContext (or equivalents) data passed in (or

    captured) is frozen data returned is frozen
  22. suspend fun printStuffBg() { val someData = SomeData("Hello ", 2)

    withContext(workerDispatcher) { println(someData) } }
  23. suspend fun printStuffBg() { val someData = SomeData("Hello ", 2)

    withContext(workerDispatcher) { println(someData) } }
  24. suspend fun printStuffForeground() { val someData = SomeData("Hello ", 2)

    val someMoreData = withContext(workerDispatcher) { SomeData("${someData.s}, Dogs!", someData.i + 1) } println(someMoreData) println("I am frozen? ${someMoreData.isFrozen()}") }
  25. suspend fun printStuffForeground() { val someData = SomeData("Hello ", 2)

    val someMoreData = withContext(workerDispatcher) { SomeData("${someData.s}, Dogs!", someData.i + 1) } println(someMoreData) println("I am frozen? ${someMoreData.isFrozen()}") }
  26. suspend fun printStuffForeground() { val someData = SomeData("Hello ", 2)

    val someMoreData = withContext(workerDispatcher) { SomeData("${someData.s}, Dogs!", someData.i + 1) } println(someMoreData) println("I am frozen? ${someMoreData.isFrozen()}") }