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

Compose로 Android&Desktop 멀티플랫폼 만들기

Compose로 Android&Desktop 멀티플랫폼 만들기

KotlinConf'23 Global in Songdo

Pangmoo

May 13, 2023
Tweet

More Decks by Pangmoo

Other Decks in Programming

Transcript

  1. • UI Android UI • , UI • XML Kotlin

    API Jetpack Compose Compose ?
  2. • Jetpack Compose • Android • Compose for Desktop •

    Windows • Mac • Linux • Compose for Web Compose Multiplatform Android UI .
  3. ?

  4. UI

  5. UI @Composable fun PrimaryButton(text: String, enabled: Boolean = true, onClick:

    () -> Unit) { Button( onClick = onClick, modifier = Modifier.widthIn(min = 168.dp), enabled = enabled, contentPadding = PaddingValues(vertical = 12.dp) ) { Text(text) } } @Composable fun SecondaryButton(text: String, enabled: Boolean = true, onClick: () -> Unit) { OutlinedButton(onClick = onClick, ...) { Text(text) } } PrimaryButton(text = "PangMoo", onClick = {}) SecondaryButton(text = "Secondary", onClick = {}) ButtonComponent.kt
  6. : UI @Composable fun HomeScreen(modifier: Modifier = Modifier) { val

    infiniteTransition = rememberInfiniteTransition() val animateBackgroundColor by infiniteTransition.animateColor(…) Column(…) { Text( text = "KotlinConf'23 Global in Songdo", modifier = Modifier .background( color = animateBackgroundColor, shape = MaterialTheme.shapes.medium) ) ... ) } } HomeSreen.kt
  7. : UI @Composable fun SettingsScreen(modifier: Modifier = Modifier) { var

    value by remember { mutableStateOf(0f) } var checked by remember { mutableStateOf(false) } Column(…) { SomethingValue(value = value, onValueChange = { value = it }) SomethingSwitch(checked = checked, onCheckedChange = { checked = it }) Spacer(modifier = Modifier.weight(1f)) VersionText(version = "1.0.0") } } @Composable private fun SomethingValue(...) {...} @Composable private fun SomethingSwitch(...) {...} @Composable private fun VersionText(...) {...} SettingsSreen.kt
  8. : UI @Composable fun StopwatchScreen( modifier: Modifier = Modifier, stopwatchState:

    StopwatchState, formattedTimeString: String, onStart: () -> Unit, onPause: () -> Unit, onResume: () -> Unit, onReset: () -> Unit ) { Column(…) { ActionButtons( stopwatchState = stopwatchState, formattedTimeString = formattedTimeString, onStart = onStart, onPause = onPause, onResume = onResume, onReset = onReset ) } } ... StopwatchSreen.kt
  9. Light/Dark Theme Material Theme val KawaiBlue = Color(0xFF25AAFF) val KawaiDark

    = Color(0xFF1E1F22) val LightColors = lightColors( primary = KawaiBlue ) val DarkColors = darkColors( primary = KawaiBlue, surface = KawaiDark ) @Composable fun PangmooTheme( isSystemInDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { MaterialTheme( colors = if (isSystemInDarkTheme) DarkColors else LightColors, content = content ) } Theme.kt Colors.kt
  10. class StopwatchUtil { private val _state = MutableStateFlow<StopwatchState>(StopwatchState.STOP) val state

    = _state.asStateFlow() private val _time = MutableStateFlow(0) val time = _time.asStateFlow() private val coroutineScope = CoroutineScope(Dispatchers.IO) private var job: Job? = null val formattedTimeString = time.map { val hour = time.value / 3600 val minute = (time.value % 3600) / 60 val second = time.value % 60 "%02d:%02d:%02d".format(hour, minute, second) }.stateIn(coroutineScope, SharingStarted.Lazily, "00:00:00") fun start() {...} fun pause() {...} ... } StopwatchUtil.kt
  11. Version Android Desktop 1.0.0 1.0.0 1.0.1 Local Storage 1.0.1 Local

    Storage 1.0.2 Social Login 1.0.2 Social Login 1.0.3 Analytics 1.0.3 Analytics
  12. Version Android Desktop 1.0.0 1.0.0 1.0.1 Local Storage 1.0.1 Local

    Storage 1.0.2 Social Login 1.0.2 Social Login 1.0.3 Analytics 1.0.3 Analytics Permission Error!
  13. Version Android Desktop 1.0.0 1.0.0 1.0.1 Local Storage 1.0.1 Local

    Storage 1.0.2 Social Login 1.0.2 Social Login 1.0.3 Analytics 1.0.3 Analytics Permission Error! 1.0.31 Hotfix Permission Error
  14. Version @Composable fun SettingsScreen(modifier: Modifier = Modifier) { var value

    by remember { mutableStateOf(0f) } var checked by remember { mutableStateOf(false) } Column(...) { ... VersionText(version = getVersionName()) } } @Composable fun SettingsScreen(modifier: Modifier = Modifier) { var value by remember { mutableStateOf(0f) } var checked by remember { mutableStateOf(false) } Column(...) { ... VersionText(version = "1.0.0") } }
  15. Android ‒ BuildConfig Version android { defaultConfig { ... applicationId

    = "com.haeyum.android" versionCode = 1 versionName = version.toString() buildConfigField("String", "VERSION_NAME", "\"${versionName}\"") } ... }
  16. ViewModel class AppViewModel(private val stopwatchUtil: StopwatchUtil = StopwatchUtil()) : ViewModel()

    { { val stopwatchState = stopwatchUtil.state val formattedTimeString = stopwatchUtil.formattedTimeString fun start() = stopwatchUtil.start() fun pause() = stopwatchUtil.pause() fun resume() = stopwatchUtil.resume() fun reset() = stopwatchUtil.reset() fun destroy() = stopwatchUtil.destroy() }
  17. class AppViewModel(private val stopwatchUtil: StopwatchUtil = StopwatchUtil()) : ViewModel() {

    { val stopwatchState = stopwatchUtil.state val formattedTimeString = stopwatchUtil.formattedTimeString fun start() = stopwatchUtil.start() fun pause() = stopwatchUtil.pause() fun resume() = stopwatchUtil.resume() fun reset() = stopwatchUtil.reset() fun destroy() = stopwatchUtil.destroy() } ViewModel ViewModel ?
  18. ViewModel class AppViewModel(private val stopwatchUtil: StopwatchUtil = StopwatchUtil()) : ViewModel()

    { val stopwatchState = stopwatchUtil.state val formattedTimeString = stopwatchUtil.formattedTimeString fun start() = stopwatchUtil.start() fun pause() = stopwatchUtil.pause() fun resume() = stopwatchUtil.resume() fun reset() = stopwatchUtil.reset() fun destroy() = stopwatchUtil.destroy() } ViewModel Android Common class AppViewModel(private val stopwatchUtil: StopwatchUtil = StopwatchUtil()) : ViewModel() { { val stopwatchState = stopwatchUtil.state val formattedTimeString = stopwatchUtil.formattedTimeString fun start() = stopwatchUtil.start() fun pause() = stopwatchUtil.pause() fun resume() = stopwatchUtil.resume() fun reset() = stopwatchUtil.reset() fun destroy() = stopwatchUtil.destroy() }
  19. ViewModel class AppViewModel(private val stopwatchUtil: StopwatchUtil = StopwatchUtil()) : BaseViewModel()

    { val stopwatchState = stopwatchUtil.state val formattedTimeString = stopwatchUtil.formattedTimeString fun start() = stopwatchUtil.start() // 혹은 suspend로 구현 후 viewModelScope 사용 fun pause() = stopwatchUtil.pause() fun resume() = stopwatchUtil.resume() fun reset() = stopwatchUtil.reset() override fun destroy() { super.destroy() stopwatchUtil.destroy() } }
  20. AppViewModel Composable Desktop Configuration remember ViewModel DisposableEffect destroy Android viewModel,

    viewModels , ViewModelProvider ViewModel viewModel() Composable . androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1
  21. Android viewModel, viewModels , ViewModelProvider ViewModel viewModel() Composable . androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1

    AppViewModel Composable Desktop Configuration remember ViewModel DisposableEffect destroy
  22. Desktop Configuration remember ViewModel DisposableEffect destroy AppViewModel Composable Android viewModel,

    viewModels , ViewModelProvider ViewModel viewModel() Composable . androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1
  23. Desktop Configuration remember ViewModel DisposableEffect destroy AppViewModel Composable Android viewModel,

    viewModels , ViewModelProvider ViewModel viewModel() Composable . androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1 produceState
  24. UI

  25. UI @Composable fun App(viewModel: AppViewModel) { var screenState by rememberSaveable

    { mutableStateOf(ScreenState.HOME) } val stopwatchState by viewModel.stopwatchState.collectAsState() val formattedTimeString by viewModel.formattedTimeString.collectAsState() PangmooTheme { Surface { Column(modifier = Modifier.fillMaxSize()) { Header(title = screenState.title) when (screenState) { ScreenState.HOME -> HomeScreen(modifier = Modifier.fillMaxWidth().weight(1f)) ScreenState.STOPWATCH -> StopwatchScreen( modifier = Modifier.fillMaxWidth().weight(1f), stopwatchState = stopwatchState, formattedTimeString = formattedTimeString, onStart = viewModel::start, onPause = viewModel::pause, onResume = viewModel::resume, onReset = viewModel::reset ) ScreenState.SETTINGS -> SettingsScreen(modifier = Modifier.fillMaxWidth().weight(1f)) } MainBottomNavigation(screenState = screenState, onScreenChange = { screenState = it }) } } } }
  26. ?

  27. ?

  28. ? DI - Koin API - Ktor RDB ‒ SQLDelight

    Kotlinx(Coroutine, Serialization, …) Reaktive - Rx And then more…