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.

Avatar for Kevin Galligan

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() }