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

Jetpack Compose Recipes

Jetpack Compose Recipes

How to use Jetpack Compose in production application.

ThawZinToe

May 21, 2022
Tweet

More Decks by ThawZinToe

Other Decks in Technology

Transcript

  1. 01 What is Jetpack Compose? 02 Impreactive UI vs Declarative

    UI A quick recall to compose 03 XML to Compose Migration 04 State Handling Explore state handling in compose with a Spinner example 05 Reusable Component 06 Best practices for Compose 07 Questions & Answer Questions on anything “Android”
  2. Adobe Stock#243026154 Why Jetpack Compose? • UI development process နဲ

    ့ Paradigm ကို ရိုး ှင်းေစတယ် • Traditional Layout Inflation (သို့ ) Imperative UI ထက် ေရးရတဲ့ Code ပိုနည်း • Kotlin သံုး ပီးေရးလို့ရ
  3. 01 Efficient UI Rendering 02 Interoperable with existing Widgets and

    Architecture Components 03 Easier State Management 04 Cuts down development time 05 Tooling Support by Android Studio 06 A rapidly growing Community Advantages
  4. 01 Interoperability comes with a price 02 Performance drops in

    Debug mode 03 Requires a Rebuild for preview Caveats
  5. Adobe Stock#243026154 • Line by line execution of UI logic

    • Focuses on “HOW” of UI presentation • ဘယ်ေန ့ မှ ေဆာင်မှာလဲ xD Imperative UI • We describe, or declare, what UI should look like • Focuses on “What” of UI Presentation Declarative UI
  6. Imperative UI Column { Text(text = "Text 1") Text(text =

    "Text 2") Text(text = "Text 3") } Declarative UI
  7. 01 Add dependency 02 Migration strategies 03 App Architecture 04

    Theme in Compose 05 Image Loading XML to Compose Migration
  8. android { defaultConfig { ... } buildFeatures { // Enables

    Jetpack Compose for this module compose true } ... composeOptions { kotlin Compiler Extension Version '1.1.1' } } Configure Gradle
  9. class MainActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?)

    { ... binding.composeView.setContent { Mdc Theme { Surface{ Text("Hello Compose") } } } } } MainActivity.kt
  10. App Architecture @Composable fun MainContent ( viewModel: ViewModel ) Follow

    Activity / Fragment Lifecycle LiveData.observeAsState( ) Flow.collectAsState( ) Observable.subscribeAsState( )
  11. @Composable fun MainScreen() { val viewModel: MainViewModel = viewModel() val

    data = viewModel.something.observeAsState() MainContent(viewModel) } LiveData
  12. @Composable fun MainScreen() { val viewModel: MainViewModel = viewModel() val

    data = viewModel.something.collectAsState() MainContent(viewModel) } Flow
  13. @Composable fun MainScreen() { val viewModel: MainViewModel = viewModel() val

    data = viewModel.something.subscribeAsState() MainContent(viewModel) } Observable
  14. MaterialTheme Themes in Compose MaterialTheme( colors = …, typography =

    …, shapes = … ) { // app content } MDC Compose Theme Adapter goo.gle/mdc-compose-theme-adapter • Use MDC theme as single source of truth • Read the color, text appearance and shape appearance from your MDC theme -> Compose
  15. private val Yellow200 = Color(0xffffeb46) // ... private val DarkColors

    = darkColors( primary = Yellow200, // ... ) private val LightColors = lightColors( primary = Yellow500, primaryVariant = Yellow400, // ... ) 1. Color
  16. val Rubik = FontFamily( Font(R.font.rubik_regular), Font(R.font.rubik_medium, FontWeight.W500), Font(R.font.rubik_bold, FontWeight.Bold) )

    val MyTypography = Typography( h1 = TextStyle( fontFamily = Rubik, fontWeight = FontWeight.W300, fontSize = 96.sp ), /*...*/) 2.Typography
  17. val Shapes = Shapes( small = Rounded Corner Shape(percent =

    50), medium = Rounded Corner Shape(0f), large = Cut Corner Shape( topStart = 16.dp, topEnd = 0.dp, bottomEnd = 0.dp, bottomStart = 16.dp ) ) MaterialTheme(shapes = Shapes, /*...*/) 3.Shape
  18. Image Loading Api to load image from url CoilImage Api

    to load vector drawables & assets PNG image PainterResource
  19. @Composable fun MainScreen() { Icon( painter = painterResource(id = R.drawable.ic_logo),

    contentDescription = null // decorative element ) } PaintersResource
  20. State Handling in Compose What is State and how to

    handle ui state in composition #composestate
  21. Adobe Stock#243026154 အနီးစပ်ဆံုးကေတာ့ UI State What is State? • Card

    ကို clickလိုက်ရင် ripple animation ေလးြဖစ်လာတာက Card ရဲရွှေ့ state တစ်ခုပါပဲ • Event တစ်ခုကို change လိုက်ရင် ြဖစ်လာတဲ့အရာ • Compose မှာလဲ သူ့ data ေတွကို ေြပာင်းလဲြပသတာကို compose state လို့ေခါ်
  22. Jetpack compose ကေန composable ကို executes လုပ်လိုက်တဲ့အချ ိန်မှာ UI ကို

    built လုပ်သွားတာ Composition 01 Data ကို changes လုပ်လိုက်တဲ့အခါ composable ကို ြပန် re-running လုပ်ပီး composition ကို update လုပ်တာကို Recomposition 02 အေြခေနတစ်ခုမှာ composables ေတွကို recomposition အတွက် ြပန်ပီးtrackingလုပ်ေပးတာ Compose’s state tracking system 03 Key Points
  23. Adobe Stock#243026154 State<T> • Read-only value ေတွပဲသိမ်းထားနိုင်တယ် • Value ကို

    ေြပာင်းလိုက်မှ composition ကို ြပန်ပီးေတာ့ notify လုပ်ေပးတယ်
  24. Adobe Stock#243026154 MutableState<T> • Value ေတွကို update ေပးဖို့ လုပ်ေပးတယ် Extension

    function of State • Value ကို ေြပာင်းလိုက်ရင် အဲ့ value ကို recompositon ရဲရွှေ့ RecomposeScopes ကိုြပန်ပီးေတာ့ စီစဥ်တယ်
  25. @Composable fun NoState() { var clickCount = 0 Column {

    Button(onClick = { clickCount++ Log.d("TAG", "NoState: "+clickCount) }) { Text(text = "$clickCount times clicked") } } }
  26. • Mutable သို့ မဟုတ် Immutable ြဖစ်တဲ့ object ေတွကို သိမ်းထားနိုင်တယ် •

    Value သာ ေြပာင်းသွားမယ်ဆိုရင် သူနဲ ့ သက်ဆိုင်တဲ့ widget ကို update သွားလုပ်ေပးဖို့ recomposition ( refresh UI) ကို trigger ြပန်လုပ်ေပးတယ် remember
  27. var selectedIndex by remember{ mutableStateOf(0) } Imperative UI Syntax @Composable

    fun RememberSample() { var clickCount by remember { mutableStateOf(0) } Column { Button(onClick = { clickCount++ }) { Text(text = "$clickCount times clicked") } } } Example
  28. • Remember နဲ ့ တူတူေပမဲ့ သူက activity သို့ မဟုတ် process

    ကီး recreate ြပန်လုပ်ရင်ေတာင်မှ restore ြပန်လုပ်ေပးပါတယ် configuration ေြပာင်းလဲရင်လဲ survive ြဖစ်ပါတယ် rememberSaveable
  29. Adobe Stock#243026154 Stateful Composable function ကို create လုပ်တဲ့အချ ိန်မှာ သူ့ရဲရွှေ့

    state ကို အြပည့်အ၀ control လုပ်နိုင်တာဆိုလိုတယ် အားနည်းချက်ကေတာ့ reusable component လုပ်လို့မရတာရယ် ၊ testing အတွက်ခက်ခဲတာရယ်
  30. @Composable fun MainScreen() { var isExpanded by remember { mutableStateOf(false)

    } Row { Text(modifier = Modifier.weight(1f)) IconButton( onClick = { isExpanded = !isExpanded }) { Image( imageVector = if (isExpanded) Icons.Filled.KeyboardArrowDown else Icons.Filled.KeyboardArrowUp, contentDescription = "" ) } } }
  31. Adobe Stock#243026154 Stateless Composable ကို create လုပ်တဲ့အချ ိန်မှာ သူ့ရဲရွှေ့ state

    ကို modify ြပန်မလုပ်နိုင်ဘူး သူက လက်ခံလို့ရပီး State ကို ပဲ hoist လုပ်ေပးနိုင်တယ်၊ အဲ့တာကို State hoisting လုပ်တယ်လို့ေခါ်တယ်
  32. @Composable fun MainScreen(){ val name by remember { mutableStateOf(“”) }

    CustomTextField(name, onNameChanged = { name = it }) } @Composable fun CustomTextField(name: String,onNameChanged: (String)-> Unit){ Text(text = name ) OutlineTextField( value = name, onValueChanged = onNameChanged, label = { Text(text = name ) } ) }
  33. @Composable fun DropdownDemo() { var expanded by remember { mutableStateOf(false)

    } val items = listOf("Apple", "Ball", "Cat", "Dog", "Elephant", "Florida") var selectedIndex by remember { mutableStateOf(0) } Box(...) { Text(items[selectedIndex]) DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, ) { items.forEachIndexed { index, s -> DropdownMenuItem(onClick = { selectedIndex = index expanded = false }) { Text(text = s + disabledText) } } } } }
  34. Composable တစ်ခု ရဲရွှေ့ state ေတွကို control လုပ်နိုင်တဲ့ privilege ကို upper

    level composable ကိုတစ်ဆင့်ေရရွှေ့ေပးတာ ကို ဆိုလိုတာပါ။ State Hoisting
  35. @Composable fun BurgerItem() { Card { Row { Image(/**/) Column

    { Text("Tower Burger") NumberControl() } } } } State Hoisting
  36. State Hoisting @Composable fun BurgerItem() { Card { Row {

    Image(/**/) Column { Text("Tower Burger") NumberControl() } } } }
  37. State Hoisting @Composable fun NumberControl() { var number by remember

    { mutableStateOf(0) } Row { MinusButton( onClick = { number-- } ) Text(text = number.toString()) PlusButton( onClick = { number++ } ) } }
  38. State Hoisting @Composable fun NumberControl( value: Int = 0, onTapAdd:

    () -> Unit, onTapMinus: () -> Unit ) { Row { MinusButton(onClick = onTapAdd) Text(text = value.toString()) PlusButton(onClick = onTapMinus) } }
  39. State Hoisting @Composable fun BurgerCard() { ... Column { ...

    var count by remember { mutableStateOf(0) } NumberControl( value = count, onTapAdd = { count++ }, onTapMinus = { count++ }, ) } }
  40. @Composable fun MovieScreen( viewModel: MovieViewModel = hiltViewModel() ) { val

    movieList = viewModel.getMoviesFlow().collectAsState(emptyList()) LazyColumn(Modifier.fillMaxSize()) { items(items = movieList.value, key = { it.id }) { movie -> MovieListItem(movie) } } }
  41. @Preview @Composable fun MovieScreen( viewModel: MovieViewModel = hiltViewModel() ) {

    val movieList = viewModel.getMoviesFlow().collectAsState(emptyList()) LazyColumn(Modifier.fillMaxSize()) { items(items = movieList.value, key = { it.id }) { movie -> MovieListItem(movie) } } }
  42. @Composable fun MovieScreen(viewModel: MovieViewModel) { val movieList by viewModel.getMoviesFlow().collectAsState(emptyList()) MovieScreenStateless(movieList)

    } @Preview @Composable fun MovieScreenStateless( movieList: List<Movie> = dummyMovieList, onTapItem: (Movie) -> Unit, onTapFavorite: (Movie) -> Unit, ... ) { ... }
  43. // MovieScreenState.kt data class MovieScreenState( val movieList: State<List<Movie>> = mutableStateOf(emptyList()),

    val onTapMovie: (Movie) -> Unit = {} ) { companion object { val previewState by lazy { MovieScreenState( movieList = mutableStateOf( listOf( Movie(1, "Fast And Furious"), Movie(2, "Hobbs & Shaw"), Movie(3, "Venom 2") ) ) ) } } }
  44. // MovieScreen.kt @Composable fun MovieScreen(viewModel: MovieViewModel) { MovieScreenStateless( state =

    MovieScreenState( movieList = viewModel.getMoviesFlow().collectAsState(emptyList()), onTapMovie = viewModel::onTapMovie, onTapFavorite = viewModel::onTapFavorite ) ) } @Preview @Composable fun MovieScreenStateless( state: MovieScreenState = MovieScreenState.previewState ) { ... }
  45. // MovieScreen.kt @Composable fun MovieScreen(viewModel: MovieViewModel) { MovieScreenStateless( state =

    MovieScreenState( movieList = viewModel.getMoviesFlow().collectAsState(emptyList()), onTapMovie = viewModel::onTapMovie, onTapFavorite = viewModel::onTapFavorite ) ) } @Preview @Composable fun MovieScreenStateless( state: MovieScreenState = MovieScreenState.previewState ) { ... }
  46. // MovieScreen.kt @Composable fun MovieScreen(viewModel: MovieViewModel) { MovieScreenStateless( state =

    MovieScreenState( movieList = viewModel.getMoviesFlow().collectAsState(emptyList()), onTapMovie = viewModel::onTapMovie ) ) } @Preview @Composable fun MovieScreenStateless( state: MovieScreenState = MovieScreenState.previewState ) { ... }
  47. // CustomCalendar.kt @Composable fun CalendarScreen() { val calendarState = rememberCalendarState()

    CustomCalendar(state = calendarState) } @Preview @Composable fun CustomCalendar( state: CalendarState = CalendarState.previewState ) { ... }
  48. // CustomCalendarState.kt @Composable fun rememberCalendarState(): CalendarState = remember { CustomCalendarState()

    } interface CalendarState { ... } private class CustomCalendarState() : CalendarState { ... }
  49. // CustomCalendarState.kt @Composable fun rememberCalendarState( parameter1: String = DEFAULT_PARAM_1, parameter2:

    Int = DEFAULT_PARAM_2, callBack: () -> Unit = {} ): CalendarState = remember { CustomCalendarState(parameter1, parameter2, callBack) } private class CustomCalendarState( val parameter1: String, val parameter2: Int, val callBack: () -> Unit ) : CalendarState
  50. 01 Used name parameter in compose 02 Avoid using fixed

    dimensions 03 Reuse as much as possible 04 Use helper classes 05 Use Effect Handler Top 5 best practices for Compose 06 Avoid as much recomposition as possible 07 Use keys argument for LazyLists for better performance
  51. Adobe Stock#243026154 Used name parameters in compose • So much

    more readable • ဘာေတွကို parameter အေနနဲ ့ ထားခဲ့တယ်ဆိုတာ သဲသဲကွဲကွဲသိနိုင်တယ် • So much clean in code • Should use more than one parameter
  52. @Composable fun MainScreen() { Icon( painter = painterResource(id = R.drawable.ic_logo),

    contentDescription = null // decorative element ) } Example
  53. // Correct way to use onClick Lambdas ListItem( data =

    item, onClick = { /* Handle onClick */ } ) // Be careful with Trailing Lambdas ListItem( data = item ) { /* Handle onClick */ } Example 2
  54. Adobe Stock#243026154 Reused as much as possible • Less code

    • Should use at least twice • More reliability
  55. Adobe Stock#243026154 Use helper classes • Developer should not use

    some long parameters eg.textfield need color , it need style , need value change state and so on.
  56. @Composable fun MainScreen() { var number: State<Int> = mutableStateOf(0) Text(

    text = “Example”, style = mediumStyle() ) } } @Composable fun mediumStyle(@FontRes font: Int = R.font.opensans_regular): TextStyle { return TextStyle( color = DARK, fontFamily = FontFamily(Font(font)), fontSize = dpToSp(dimensionResource(R.dimen.text_size_medium)) ) }
  57. Adobe Stock#243026154 Avoid using fixed dimensions • Dimensions ကို အေသေပးခဲ့မယ်ဆိုရင်

    larger screen မတူတာပဲြဖစ်ြဖစ် resolution ကွာတဲ့ြဖစ်ြဖစ် responsive မြဖစ်ဘူး • အဲ့လိုမြဖစ်ေအာင် weight ေတွ, row and column ေတွနဲ ့ box ေတွသံုးပီး composables ေတွကို align လုပ်လို့ရတယ်
  58. Adobe Stock#243026154 Use effect handlers The concept we called side

    effect • LaunchedEffect Run suspend functions in the scope of composable • DisposableEffect Effect that require cleanup • SideEffect Publish Compose state to non-compose code • produceState Convert non-compose state into Compose state • derivedStateof Convert one or multiple state objects into another state • snapshotFlow Convert Compose’s state into Flows
  59. Adobe Stock#243026154 Avoid as much recomposition as possible • Recomposition

    က ေစျး ကီးပါတယ်။ Recompose လုပ်တိုင်း layout positioning ကို အစက ြပန်လုပ်ရတာမလို့ တတ်နိုင်သေလာက်ေ ှာင်သင့်ပါ တယ် • mutableState ကို သံုးတဲ့အခါ မလိုအပ်တဲ့ recomposition ေတွြဖစ်တတ်လို့ သတိထားသံုးသင့်ပါတယ်။
  60. var transactionAmount by remember { mutableStateOf(0) } transactionAmount += 100

    for (x in 0..5) { transactionAmount *= x Text(transactionAmount.toString()) }
  61. var transactionAmount by remember { mutableStateOf(0) } transactionAmount += 100

    for (x in 0..5) { transactionAmount *= x Text(transactionAmount.toString()) }
  62. ... transactionAmount *= 1 Text(transactionAmount.toString()) transactionAmount *= 2 Text(transactionAmount.toString()) transactionAmount

    *= 3 Text(transactionAmount.toString()) transactionAmount *= 4 Text(transactionAmount.toString()) transactionAmount *= 5 Text(transactionAmount.toString()) ...
  63. val transactionAmounts = listOf(100, 200, 300, 400, 500) for (amount

    in transactionAmounts) { Text(amount.toString()) }
  64. Avoid Recomposition @Composable fun BurgerCard() { Image(/**/) Column { Text(/**/)

    var count by remember { mutableStateOf(0) } NumberControl( value = count, onTapAdd = { count++ }, onTapMinus = { count++ }, ) } }
  65. Thank you! Android Engineer@ Codigo @developer_ptut Thaw Zin Toe Android

    Engineer @Codigo @harryluu_96 Naing Aung Luu / Harry Pe Tut thawzintoe-ptut
  66. iOS