Professional Documents
Culture Documents
Isf
ag
an
te ng
multi-screen app with Kotlin
and Compose Multiplatform
Muh Isfhani Ghiath • DevFest Surabaya 2023
@isfaaghyth with.isfa.dev
Outline
Overview
Background
Walkthrough
Study Case
Multiplatform options
Multiplatform Alternatives
Hybrid a really native compiled!
KMM and Flutter Comparison
…. React Native?
Share the logic of iOS and Android
apps while keeping the UX native.
Kotlin Multiplatform Mobile is an
SDK for iOS and Android app
development. It offers all the
combined benefits of creating
cross-platform and native apps.
● Data Layer
Build IDE
tooling support
Previous: Configuration targets
kotlin {
androidTarget()
iosArm64()
iosSimulatorArm64()
sourceSets {
val commonMain by getting
val iosMain by getting {
dependsOn(commonMain)
}
}
}
Stable: Configuration targets
kotlin {
androidTarget()
iosArm64()
iosSimulatorArm64()
sourceSets {
commonMain.dependencies {}
androidMain.dependencies {}
}
}
kotl.in/kmp-portal
Compose
Multiplatform! 🤩
jb.gg/compose
Compose Multiplatform
Built on Jetpack Compose
Build Multiplatform UIs
using APIs you already know.
Jetpack Compose
Compose Multiplatform
APIs APIs
Build Multiplatform UIs
using APIs you already know.
Compose
Multiplatform
APIs
Behind Compose Multiplatform
Behind Compose Multiplatform
Behind Compose Multiplatform
Skiko
Kotlin MPP bindings to Skia
Skiko (short for Skia for Kotlin) is the graphical library exposing
significant part of Skia library APIs to Kotlin, along with the
gluing code for rendering context.
Compose for iOS Alpha 😍
Available in Compose Multiplatform 1.5.10
Latest Updates (1.5.10) 🤩
* Dialogs, popups
* Window Insets
* 120Hz refresh rate
* Natural scrolling for iOS
* Stabilized test framework for desktop
kmp.jetbrains.com
Getting Started
commonMain
iosMain
ComposeView.swift
Evaluation Strategies
The app is treated as another being that senses the inputs and expresses its
output.
The app listens to the user actions and modifies the data it has
The changes in the data are reflected through UI as an expression of the app
Mobius Sneak Peek
Objectives
Reactive in nature
Criteria
Platform Agnostic
Scalability
Testability
Immutable States
Community Support
Comparison
Swift TCA
Comparison
Mobius
Comparison
Mobius v TCA
Both architectures have the same way, each arch uses the Redux-like or MVI-like approaches to
maintain the data changes using State and Event as well as contain the side effects handlers.
Testing Benefit
Spec framework!
spec.given(model)
.whenEvent(LoginRequested)
.then(assertThatNext(
hasModel(model.copy(loggingIn = true))
hasEffects(AttemptLogin("isfa@devfest.com", "rahasia"))
))
Project walkthrough
Walkthrough
Inspired: xxfast/NYTimes-KMP
Walkthrough
commonMain
Walkthrough
Router
@Parcelize
sealed class NavRouter : Parcelable {
Root level
@Composable
fun DevFestApp() {
val router: Router<NavRouter> = rememberRouter(NavRouter::class) {
listOf(NavRouter.Home)
}
LazyVerticalGrid(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(24.dp),
columns = GridCells.Adaptive(minSize = 248.dp)
) {
items(items = chapters, key = { it.id }) {
ChapterCard(
chapter = it,
onClick = {
onCardClicked(it)
}
)
}
}
Walkthrough
import android.app.Activity
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
import androidx.compose.runtime.Composable
@Composable
fun calculateWindowSizeClass(activity: Activity): WindowSizeClass
= calculateWindowSizeClass(activity)
Walkthrough
@Composable
fun calculateWindowSizeClass(controller: UIViewController): WindowSizeClass {
val density = LocalDensity.current
AnimatedVisibility(
visible = panelVisibility,
enter = animationSpec.enter,
exit = animationSpec.exit,
modifier = Modifier
.fillMaxWidth(1f - split)
.align(Alignment.CenterEnd)
) {
panel()
}
Walkthrough
class TwoPanelScaffoldAnimationSpec(
val expand: AnimationSpec<Float>,
val enter: EnterTransition,
val exit: ExitTransition,
val finishedListener: ((Float) -> Unit)?,
)
Walkthrough
LaunchedEffect(windowSizeClass) {
router = router.takeIf {
windowSizeClass.widthSizeClass != WindowWidthSizeClasses.Compact
}
showSidePanel = router != null
}
Walkthrough
TwoPanelScaffold(
panelVisibility = showSidePanel,
animationSpec = TwoPanelScaffoldAnimationSpec(
finishedListener = { fraction -> if (fraction == 1f) router = null }
),
body = {
HomeScreenView()
},
panel = {
Surface(tonalElevation = 1.dp) {
DetailScreen()
}
}
)
Walkthrough
TwoPanelScaffold(
Custom Scaffold Usage
panelVisibility = showSidePanel,
animationSpec = TwoPanelScaffoldAnimationSpec(
finishedListener = { fraction -> if (fraction == 1f) router = null }
),
body = {
HomeScreenView(
state = state,
onCardClicked = {
val detailSelection = NavRouter.Detail(it)
if (windowSizeClass.widthSizeClass == WindowWidthSizeClasses.Compact) {
onCardClicked(it)
return@HomeScreenView
}
router = detailSelection
showSidePanel = true
}
)
},
panel = {
Surface(tonalElevation = 1.dp) {
if (details != null) {
DetailScreen(
github.com/isfaaghyth/devfest
Hands-on!
Still work in Progress!