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

Kotlin Multiplatform Architecture

Kotlin Multiplatform Architecture

Discussing building shared architecture apps with Kotlin, mostly native mobile (Android & iOS). Talk about libraries currently available, why Jetbrains has a different threading model, where the ecosystem is at and where I think it's going.

Kevin Galligan

October 26, 2018
Tweet

More Decks by Kevin Galligan

Other Decks in Technology

Transcript

  1. Q3 Q2 Q4 Q1 Q2 2018 2019 0 .6 v0.7

    v0.8 v0.8.2 v0.9.3 IDE tooling! Coroutines?
  2. Q3 Q2 Q4 Q1 Q2 2018 2019 0 .6 v0.7

    v0.8 v0.8.2 v0.9.3 IDE tooling! Coroutines? K/N 1.0, Kotlin 1.3 Gradle 4.10+ Android Studio
  3. Q3 Q2 Q4 Q1 Q2 2018 2019 0 .6 v0.7

    v0.8 v0.8.2 v0.9.3 IDE tooling! Coroutines? K/N 1.0, Kotlin 1.3 Gradle 4.10+ Android Studio MT Coroutines! Other samples/libraries Production deployments
  4. Q3 Q2 Q4 Q1 Q2 2018 2019 0 .6 v0.7

    v0.8 v0.8.2 v0.9.3 IDE tooling! Coroutines? K/N 1.0, Kotlin 1.3 Gradle 4.10+ Android Studio MT Coroutines! Other samples/libraries Production deployments Paid license/debugger Reactive Library Big Production Apps Webassembly stuff?
  5. expect val mainThread:Boolean actual val mainThread: Boolean get() = Looper.myLooper()

    === Looper.getMainLooper() actual val mainThread: Boolean get() = NSThread.isMainThread()
  6. expect val mainThread:Boolean actual val mainThread: Boolean get() = Looper.myLooper()

    === Looper.getMainLooper() actual val mainThread: Boolean get() = NSThread.isMainThread() actual val mainThread: Boolean = true
  7. expect fun currentTimeMillis():Long expect fun <B> backgroundTask(backJob:()-> B, mainJob:(B) ->

    Unit) expect fun backgroundTask(backJob:()->Unit) expect fun networkBackgroundTask(backJob:()->Unit) expect fun initContext():NativeOpenHelperFactory expect fun <T> goFreeze(a:T):T expect fun <T> T.freeze2(): T expect fun simpleGet(url:String):String expect fun logException(t:Throwable) expect fun settingsFactory(): Settings.Factory expect fun createUuid():String
  8. actual class Date(val date:java.util.Date) { actual fun toLongMillis(): Long =

    date.time } actual class DateFormatHelper actual constructor(format: String) { val dateFormatter = object : ThreadLocal<DateFormat>(){ override fun initialValue(): DateFormat = SimpleDateFormat(format) } actual fun toDate(s: String): Date = Date(dateFormatter.get()!!.parse(s)) actual fun format(d: Date): String = dateFormatter.get()!!.format(d.date) }
  9. fun initPlatformClient( staticFileLoader: (filePrefix: String, fileType: String) -> String?, analyticsCallback:

    (name: String, params: Map<String, Any>) -> Unit, clLogCallback: (s: String) -> Unit) {
  10. fun initPlatformClient( staticFileLoader: (filePrefix: String, fileType: String) -> String?, analyticsCallback:

    (name: String, params: Map<String, Any>) -> Unit, clLogCallback: (s: String) -> Unit) { AppContext.initPlatformClient ({filePrefix, fileType -> loadAsset("${filePrefix}.${fileType}")}, {name: String, params: Map<String, Any> -> val event = CustomEvent(name) //Loop Answers.getInstance().logCustom(event) }, { Log.w("MainApp", it) })
  11. let appContext = AppContext() appContext.doInitPlatformClient(staticFileLoader: loadAsset, analyticsCallback: analyticsCallback, clLogCallback: csLog)

    func loadAsset(filePrefix:String, fileType:String) -> String?{ do{ let bundleFile = Bundle.main.path(forResource: filePrefix, ofType: fileType) return try String(contentsOfFile: bundleFile!) } catch { return nil } }
  12. val evenLiveData:EventLiveData init { val query = goFreeze(AppContext.dbHelper. queryWrapper.sessionQueries. sessionById(sessionId))

    evenLiveData = EventLiveData(query) } fun shutDown(){ evenLiveData.removeListener() }
  13. val evenLiveData:EventLiveData init { val query = goFreeze(AppContext.dbHelper. queryWrapper.sessionQueries. sessionById(sessionId))

    evenLiveData = EventLiveData(query) } fun shutDown(){ evenLiveData.removeListener() }
  14. override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

    eventViewModel.eventModel.evenLiveData. observe(viewLifecycleOwner, Observer { dataRefresh(it) }) return initView(inflater, container) }
  15. override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

    eventViewModel.eventModel.evenLiveData. observe(viewLifecycleOwner, Observer { dataRefresh(it) }) return initView(inflater, container) }
  16. fun registerForChanges(proc:(sessionInfo:SessionInfo)->Unit){ eventObserver = object : Observer<SessionInfo>{ override fun onChanged(t:

    SessionInfo?){ if(t != null) proc(t) } } eventModel.evenLiveData.observeForever(eventObserver!!) }
  17. interface SessionDetailsView : BaseView { fun updateView(isFavorite: Boolean, session: SessionModel)

    fun setupRatingButtons(rating: SessionRating?) fun setRatingClickable(clickable: Boolean) } override fun updateView(isFavorite: Boolean, session: SessionModel) { collapsingToolbar.title = session.title speakersTextView.text = session.speakers.joinToString(separator = ", ") { it.fullName } timeTextView.text = session.timeString detailsTextView.text = listOfNotNull(session.roomText, session.category).joinToString(", ") descriptionTextView.text = session.descriptionText val online = context?.let { it.isConnected?.and(!it.isAirplaneModeOn) } ?: false for (button in listOf(votingButtonsLayout, favoriteButton)) { func updateView(isFavorite: Bool, session: SessionModel) { titleLabel.text = session.title let startsAt = session.startsAt let endsAt = session.endsAt if (startsAt != nil && endsAt != nil) { timeLabel.text = KotlinPair(first: startsAt, second: endsAt).toReadableString() } let image = UIImage(named: isFavorite ? "star_full" : "star_empty")! favoriteButton.image = image
  18. class TalkExamples{ var justCountingStuff:Int = 0 init { backgroundCall {

    //do something justCountingStuff++ }.freeze() } }
  19. class TalkExamples{ var justCountingStuff:Int = 0 init { backgroundCall {

    //do something justCountingStuff++ }.freeze() } }
  20. private val stateBox: AtomicReference<DetachedObjectGraph<Any>> = AtomicReference( DetachedObjectGraph(mode = TransferMode.SAFE, producer

    = { mutableListOf<E>() as Any }) ) private val lock = NSLock() internal fun withLockDetached(proc: (MutableList<E>) -> MutableList<E>) { lock.lock() try { stateBox.value = DetachedObjectGraph(mode = TransferMode.SAFE, producer = { val dataList = stateBox.value.attach() as MutableList<E> proc(dataList) as Any }) } finally { lock.unlock() } }
  21. val lambdas = AtomicReference<PlatformLambdas?>(null) fun initPlatformClient( staticFileLoader: (filePrefix: String, fileType:

    String) -> String?, analyticsCallback: (name: String, params: Map<String, Any>) -> Unit, clLogCallback: (s: String) -> Unit) { lambdas.value = PlatformLambdas( staticFileLoader, analyticsCallback, clLogCallback).freeze() }
  22. fun update(frozenData: FrozenData){ frozenData.someCount.increment() val otherState = frozenData.someOtherState.value val updated

    = otherState.copy(otherCount = otherState.otherCount+1) frozenData.someOtherState.value = updated.freeze() }
  23. Multiplatform Definitions • freeze() method and frozen info • Atomics

    (Int, Long, Reference) • K/N state-related annotations (@ThreadLocal/ @SharedImmutable)
  24. val cowList = frozenCopyOnWriteList<SampleData>() val sharedList = frozenLinkedList<SampleData>() val coiSharedList

    = frozenLinkedList<SampleData>(stableIterator = true) val sharedMap = frozenHashMap<String, SampleData>() val lruRemovedCount = AtomicInt(0) val lruCache = frozenLruCache<String, SampleData>(5) { lruRemovedCount.increment() }