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

Discovering Jetpack Compose

B6bf611a9e4f70a8455cb175339e0587?s=47 Gerard
February 28, 2020

Discovering Jetpack Compose

Jetpack Compose will be the new declarative UI to build Android apps. Based on this new paradigm, let's see how we can use it with some simple samples!

B6bf611a9e4f70a8455cb175339e0587?s=128

Gerard

February 28, 2020
Tweet

More Decks by Gerard

Other Decks in Technology

Transcript

  1. @GerardPaligot DISCOVERING JETPACK COMPOSE

  2. WHAT’S JETPACK COMPOSE? 100% Kotlin Compiler Open Source Declarative UI

    toolkit Android Team
  3. THINK DIFFERENT.

  4. None
  5. None
  6. MovieMetadata Rating TopAppBar Poster PosterNoted MovieItem

  7. None
  8. None
  9. None
  10. None
  11. App MovieHome Home Section Home Section Home Section Data Events

  12. App MovieHome Home Section Home Section Home Section Data Events

  13. PLACING ELEMENTS

  14. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { }
  15. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { Text( text = title ) }
  16. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { Text( text = title, style = MaterialTheme.typography.h6 ) }
  17. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { Text( text = title, style = MaterialTheme.typography.h6, maxLines = 2, overflow = TextOverflow.Ellipsis ) }
  18. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { Column { Text( text = title, style = MaterialTheme.typography.h6, maxLines = 2, overflow = TextOverflow.Ellipsis ) } }
  19. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { Column { Text(...) } }
  20. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { Column { Text(...) genres.forEach { Tag(text = it) } } }
  21. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { Column { Text(...) Row { genres.forEach { Tag(text = it) } } } }
  22. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { Column { Text(...) Row(modifier = Modifier.padding(top = 5.dp)) { genres.forEach { Tag(text = it) } } } }
  23. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { Column { Text(...) Row(modifier = Modifier.padding(top = 5.dp)) { ... } } }
  24. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { Column { Text(...) Row(modifier = Modifier.padding(top = 5.dp)) { ... } val year = releaseDate.getCalendar().get(Calendar.YEAR) val time = "${runtime / 60}h ${runtime % 60}min" Text( text ="$year - $time", modifier = Modifier.padding(top = 5.dp) ) } }
  25. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { Column { Text(...) Row(modifier = Modifier.padding(top = 5.dp)) { ... } val year = releaseDate.getCalendar().get(Calendar.YEAR) val time = "${runtime / 60}h ${runtime % 60}min" Text( text = if (runtime != 0) "$year - $time" else "$year", modifier = Modifier.padding(top = 5.dp) ) } }
  26. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { Column { Text(...) Row(modifier = Modifier.padding(top = 5.dp)) { ... } CompositionLocalProvider( LocalContentAlpha provides ContentAlpha.medium ) { val year = releaseDate.getCalendar().get(Calendar.YEAR) val time = "${runtime / 60}h ${runtime % 60}min" Text( text = if (runtime != 0) "$year - $time" else "$year", modifier = Modifier.padding(top = 5.dp) ) } } }
  27. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int ) { Column { Text(...) Row(modifier = Modifier.padding(top = 5.dp)) { ... } CompositionLocalProvider( LocalContentAlpha provides ContentAlpha.medium ) { ... } } }
  28. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int, modifier: Modifier = Modifier ) { Column(modifier = modifier) { Text(...) Row(modifier = Modifier.padding(top = 5.dp)) { ... } CompositionLocalProvider( LocalContentAlpha provides ContentAlpha.medium ) { ... } } }
  29. @Composable fun MovieMetadata( title: String, genres: List<String>, releaseDate: String, runtime:

    Int, modifier: Modifier = Modifier ) { Column(modifier = modifier.semantics { contentDescription = "$title published $releaseDate" }) { Text(...) Row(modifier = Modifier.padding(top = 5.dp)) { ... } CompositionLocalProvider( LocalContentAlpha provides ContentAlpha.medium ) { ... } } }
  30. LET’S CODE A LIST

  31. @Composable fun PosterNoted( posterUrl: String, voteAverage: Int, ratingSize: Dp, ratingAlignment:

    Alignment, onClick: () -> Unit = {} )
  32. @Composable fun MovieItem( movie: Movie, modifier: Modifier = Modifier, onClick:

    (movie: Movie) -> Unit ) { }
  33. @Composable fun MovieItem( movie: Movie, modifier: Modifier = Modifier, onClick:

    (movie: Movie) -> Unit ) { PosterNoted( posterUrl = movie.pictureUrl, voteAverage = movie.percentage, ratingSize = 50.dp, ratingAlignment = Alignment.TopEnd ) }
  34. @Composable fun MovieItem( movie: Movie, modifier: Modifier = Modifier, onClick:

    (movie: Movie) -> Unit ) { Box( modifier = Modifier .width(130.dp) .aspectRatio(0.7f) ) { PosterNoted( posterUrl = movie.pictureUrl, voteAverage = movie.percentage, ratingSize = 50.dp, ratingAlignment = Alignment.TopEnd ) } }
  35. @Composable fun MovieItem( movie: Movie, modifier: Modifier = Modifier, onClick:

    (movie: Movie) -> Unit ) { Box( modifier = Modifier .width(130.dp) .aspectRatio(0.7f) ) { … } }
  36. @Composable fun MovieItem( movie: Movie, modifier: Modifier = Modifier, onClick:

    (movie: Movie) -> Unit ) { Box(modifier = modifier) { Box( modifier = Modifier .width(130.dp) .aspectRatio(0.7f) ) { ... } } }
  37. @Composable fun MovieItem( movie: Movie, modifier: Modifier = Modifier, onClick:

    (movie: Movie) -> Unit ) { val posterWidth = 130 val height = (posterWidth / 0.7f).roundToInt() Box(modifier = modifier) { Surface( modifier = Modifier .fillMaxWidth() .preferredHeight((height - 30).dp) ) { } Box( modifier = Modifier .width(posterWidth.dp) .aspectRatio(0.7f) ) { ... } } }
  38. @Composable fun MovieItem( movie: Movie, modifier: Modifier = Modifier, onClick:

    (movie: Movie) -> Unit ) { val posterWidth = 130 val height = (posterWidth / 0.7f).roundToInt() Box(modifier = modifier) { Surface( modifier = Modifier .padding(top = 60.dp) .fillMaxWidth() .preferredHeight((height - 30).dp) ) { } Box( modifier = Modifier .width(posterWidth.dp) .aspectRatio(0.7f) ) { ... } } }
  39. @Composable fun MovieItem( movie: Movie, modifier: Modifier = Modifier, onClick:

    (movie: Movie) -> Unit ) { val posterWidth = 130 val height = (posterWidth / 0.7f).roundToInt() Box(modifier = modifier) { Surface( modifier = Modifier .padding(top = 60.dp) .fillMaxWidth() .preferredHeight((height - 30).dp) .clickable(onClick = { onClick(movie) }) ) { } Box( modifier = Modifier .width(posterWidth.dp) .aspectRatio(0.7f) ) { ... } } }
  40. @Composable fun MovieItem( movie: Movie, modifier: Modifier = Modifier, onClick:

    (movie: Movie) -> Unit ) { val posterWidth = 130 val height = (posterWidth / 0.7f).roundToInt() Box(modifier = modifier) { Surface( modifier = Modifier .padding(top = 60.dp) .fillMaxWidth() .preferredHeight((height - 30).dp) .clickable(onClick = { onClick(movie) }), shape = RoundedCornerShape(8.dp) ) { } Box( modifier = Modifier .width(posterWidth.dp) .aspectRatio(0.7f) ) { ... } } }
  41. @Composable fun MovieItem( movie: Movie, modifier: Modifier = Modifier, onClick:

    (movie: Movie) -> Unit ) { val posterWidth = 130 val height = (posterWidth / 0.7f).roundToInt() Box(modifier = modifier) { Surface( modifier = Modifier .padding(top = 60.dp) .fillMaxWidth() .preferredHeight((height - 30).dp) .clickable(onClick = { onClick(movie) }), shape = RoundedCornerShape(8.dp), elevation = 5.dp ) { } Box( modifier = Modifier .width(posterWidth.dp) .aspectRatio(0.7f) ) { ... } } }
  42. val posterWidth = 130 val height = (posterWidth / 0.7f).roundToInt()

    Box(modifier = modifier) { Surface( modifier = Modifier .padding(top = 60.dp) .fillMaxWidth() .preferredHeight((height - 30).dp) .clickable(onClick = { onClick(movie) }), shape = RoundedCornerShape(8.dp), elevation = 5.dp ) { } Box( modifier = Modifier .width(posterWidth.dp) .aspectRatio(0.7f) ) { ... } } }
  43. val posterWidth = 130 val height = (posterWidth / 0.7f).roundToInt()

    Box(modifier = modifier) { Surface( modifier = Modifier .padding(top = 60.dp) .fillMaxWidth() .preferredHeight((height - 30).dp) .clickable(onClick = { onClick(movie) }), shape = RoundedCornerShape(8.dp), elevation = 5.dp ) { } Box( modifier = Modifier .padding(start = 30.dp) .width(posterWidth.dp) .aspectRatio(0.7f) ) { ... } } }
  44. val posterWidth = 130 val height = (posterWidth / 0.7f).roundToInt()

    Box(modifier = modifier) { Surface( modifier = Modifier .padding(top = 60.dp) .fillMaxWidth() .preferredHeight((height - 30).dp) .clickable(onClick = { onClick(movie) }), shape = RoundedCornerShape(8.dp), elevation = 5.dp ) { MovieMetadata( title = movie.title, genres = movie.genres, releaseDate = movie.releaseDate, runtime = movie.runtime, modifier = Modifier.padding(start = (posterWidth + 15).dp) .align(alignment = Alignment.CenterStart) ) } Box( modifier = Modifier .padding(start = 30.dp) .width(posterWidth.dp) .aspectRatio(0.7f) ) { ... } }
  45. @Composable fun MovieList( title: String, movies: List<Movie>, onClick: (movie: Movie)

    -> Unit ) { }
  46. @Composable fun MovieList( title: String, movies: List<Movie>, onClick: (movie: Movie)

    -> Unit ) { val state = rememberScrollState() Column(modifier = Modifier.verticalScroll(state = state)) { movies.forEach { MovieItem( movie = it, modifier = Modifier .padding(start = 10.dp, top = 10.dp, end = 10.dp), onClick = onClick ) } } }
  47. @Composable fun MovieList( title: String, movies: List<Movie>, onClick: (movie: Movie)

    -> Unit ) { LazyColumn { items(movies) { movie -> MovieItem( movie = movie, modifier = Modifier .padding(start = 10.dp, top = 10.dp, end = 10.dp), onClick = onClick ) } } }
  48. @Composable fun MovieList( title: String, movies: List<Movie>, onClick: (movie: Movie)

    -> Unit ) { MovieScaffold(title = title) { LazyColumn { items(movies) { movie -> MovieItem( movie = it, modifier = Modifier .padding(start = 10.dp, top = 10.dp, end = 10.dp), onClick = onClick ) } } } }
  49. LET’S CODE A LIST WITH ACTUAL FRAMEWORK UI

  50. <?xml version="1.0" encoding="utf-8"?> <androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" />

  51. <?xml version="1.0" encoding="utf-8"?> <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="80dp"> <androidx.constraintlayout.widget.ConstraintLayout

    android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/picture" android:layout_width="50dp" android:layout_height="50dp" android:layout_marginStart="20dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:srcCompat="@tools:sample/avatars" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_begin="100dp" />
  52. <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="20dp" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/price" app:layout_constraintStart_toStartOf="@+id/guideline3" app:layout_constraintTop_toTopOf="parent"> <TextView

    android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.Decathlon.Subtitle2" android:textColor="@color/colorOnSurface" /> <TextView android:id="@+id/type" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.Decathlon.Subtitle2" android:textColor="@color/grey" /> </LinearLayout>
  53. <TextView android:id="@+id/price" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="20dp" android:textAppearance="@style/TextAppearance.Decathlon.Subtitle2" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="50$"

    /> </androidx.constraintlayout.widget.ConstraintLayout> </com.google.android.material.card.MaterialCardView>
  54. data class Activity( @DrawableRes val picture: Int, val displayName: String,

    val type: String, val price: Int ) class ActivityListAdapter(val items: MutableList<Activity>) : RecyclerView.Adapter<ActivityListAdapter.ActivityViewHolder>() { val onClick: LiveData<Activity> get() = this._onClick private val _onClick: MutableLiveData<Activity> by lazy { return@lazy MutableLiveData<Activity>() }
  55. override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ActivityViewHolder = ActivityViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_activity, parent,

    false)) override fun getItemCount(): Int = items.size override fun onBindViewHolder(holder: ActivityViewHolder, position: Int) { val activity = items[position] holder.bind(activity, View.OnClickListener { _onClick.value = activity }) } fun update(items: List<Activity>) { this.items.clear() this.items.addAll(items) notifyDataSetChanged() }
  56. class ActivityViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(activity: Activity, onClickListener:

    View.OnClickListener) { itemView.setOnClickListener(onClickListener) itemView.picture.setImageDrawable(ContextCompat.getDrawable(itemView.context, activity.picture)) itemView.name.text = activity.displayName itemView.type.text = activity.type val color = if (activity.price > 0) ContextCompat.getColor(itemView.context, R.color.green) else ContextCompat.getColor(itemView.context, R.color.red) itemView.price.setTextColor(color) } } }
  57. LIGHT DARK

  58. val orange800 = Color(255, 150, 29) val orange900 = Color(255,

    118, 26) val blue400 = Color(26, 163, 255) private val LightColorPalette = lightColors( primary = orange900, primaryVariant = orange800, secondary = blue400, background = Color.White, surface = Color.White, onPrimary = Color.White, onSecondary = Color.Black, onBackground = Color.Black, onSurface = Color.Black, )
  59. val orange400 = Color(255, 205, 56) val blue800 = Color(0,

    98, 203) val blue900 = Color(0, 68, 171) val grayDark = Color(45, 47, 49) private val DarkColorPalette = darkColors( primary = blue900, primaryVariant = blue800, secondary = orange400, background = grayDark, surface = grayDark, onPrimary = Color.White, onSecondary = Color.Black, onBackground = Color.White, onSurface = Color.White )
  60. val typography = Typography( h5 = TextStyle( fontFamily = FontFamily.Default,

    fontWeight = FontWeight.W700, fontSize = 24.sp ), subtitle1 = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.W700, fontSize = 16.sp ), body1 = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 16.sp ), caption = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.W400, fontSize = 10.sp ) )
  61. val shapes = Shapes( small = RoundedCornerShape(2.dp), medium = RoundedCornerShape(4.dp),

    large = RoundedCornerShape(8.dp) )
  62. @Composable fun MaterialTheme( colors: Colors = MaterialTheme.colors, typography: Typography =

    MaterialTheme.typography, shapes: Shapes = MaterialTheme.shapes, content: @Composable () -> Unit )
  63. @Composable fun ExploringMoviesTheme( isDarkMode: Boolean = isSystemInDarkTheme(), content: @Composable() ()

    -> Unit ) { }
  64. @Composable fun ExploringMoviesTheme( isDarkMode: Boolean = isSystemInDarkTheme(), content: @Composable() ()

    -> Unit ) { MaterialTheme( content = content ) }
  65. @Composable fun ExploringMoviesTheme( isDarkMode: Boolean = isSystemInDarkTheme(), content: @Composable() ()

    -> Unit ) { MaterialTheme( colors = if (isDarkMode) DarkColorPalette else LightColorPalette, content = content ) }
  66. @Composable fun ExploringMoviesTheme( isDarkMode: Boolean = isSystemInDarkTheme(), content: @Composable() ()

    -> Unit ) { MaterialTheme( colors = if (isDarkMode) DarkColorPalette else LightColorPalette, typography = typography, content = content ) }
  67. @Composable fun ExploringMoviesTheme( isDarkMode: Boolean = isSystemInDarkTheme(), content: @Composable() ()

    -> Unit ) { MaterialTheme( colors = if (isDarkMode) DarkColorPalette else LightColorPalette, typography = typography, shapes = shapes, content = content ) }
  68. @Composable fun MovieList( title: String, movies: List<Movie>, onClick: (movie: Movie)

    -> Unit ) { MovieScaffold( title = title ){ LazyColumnFor(items = movies) { MovieItem( movie = it, modifier = Modifier .padding(start = 10.dp, top = 10.dp, end = 10.dp), onClick = onClick ) } } }
  69. @Composable fun MovieList( title: String, movies: List<Movie>, isDarkModeActive: Boolean, switchDarkMode:

    () -> Unit = {}, onClick: (movie: Movie) -> Unit ) { MovieScaffold( title = title, isDarkModeActive = isDarkModeActive, switchDarkMode = switchDarkMode ){ LazyColumnFor(items = movies) { MovieItem( movie = it, modifier = Modifier .padding(start = 10.dp, top = 10.dp, end = 10.dp), onClick = onClick ) } } }
  70. setContent { val isSystemDark = isSystemInDarkTheme() }

  71. setContent { val isSystemDark = isSystemInDarkTheme() val isDarkModeState = remember

    { mutableStateOf(isSystemDark) } }
  72. setContent { val isSystemDark = isSystemInDarkTheme() val isDarkModeState = remember

    { mutableStateOf(isSystemDark) } ExploringMoviesTheme(isDarkMode = isDarkModeState.value) { } }
  73. setContent { val isSystemDark = isSystemInDarkTheme() val isDarkModeState = remember

    { mutableStateOf(isSystemDark) } ExploringMoviesTheme(isDarkMode = isDarkModeState.value) { App( isDarkModeActive = isDarkModeState.value ) } }
  74. setContent { val isSystemDark = isSystemInDarkTheme() val isDarkModeState = remember

    { mutableStateOf(isSystemDark) } ExploringMoviesTheme(isDarkMode = isDarkModeState.value) { App( isDarkModeActive = isDarkModeState.value, switchDarkMode = { isDarkModeState.value = !isDarkModeState.value } ) } }
  75. OBSERVING DATA WITH VIEWMODEL

  76. class MovieViewModel : ViewModel() { fun getPopulars() = flow {

    ... } fun getDailyTrending() = flow { ... } fun getUpComing() = flow { ... } fun getMovieDetail(movieId: Int) = flow { ... } }
  77. ▸ LiveData.observeAsState() ▸ Flow.collectAsState() ▸ Observable.subscribeAsState()

  78. @Composable fun MyExample() { // Returns the same instance as

    long as the activity is alive, // just as if you grabbed the instance from an Activity or // Fragment val viewModel: MovieViewModel = viewModel() } @Composable fun MyExample2() { // Same instance as in MyExample val viewModel: MovieViewModel = viewModel() }
  79. @Composable fun MovieListViewModel( movieSection: MovieSection, isDarkModeActive: Boolean, switchDarkMode: () ->

    Unit = {}, onClick: (movie: Movie) -> Unit ) { MovieList( title = title, movies = movies.value, isDarkModeActive = isDarkModeActive, switchDarkMode = switchDarkMode, onClick = onClick ) }
  80. @Composable fun MovieListViewModel( movieSection: MovieSection, isDarkModeActive: Boolean, switchDarkMode: () ->

    Unit = {}, onClick: (movie: Movie) -> Unit ) { val viewModel: MovieViewModel = viewModel() MovieList( title = title, movies = movies.value, isDarkModeActive = isDarkModeActive, switchDarkMode = switchDarkMode, onClick = onClick ) }
  81. @Composable fun MovieListViewModel( movieSection: MovieSection, isDarkModeActive: Boolean, switchDarkMode: () ->

    Unit = {}, onClick: (movie: Movie) -> Unit ) { val viewModel: MovieViewModel = viewModel() val movies = when (movieSection) { MovieSection.UPCOMING -> viewModel.getUpComing() .collectAsState(initial = emptyList()) MovieSection.POPULAR -> viewModel.getPopulars() .collectAsState(initial = emptyList()) MovieSection.TRENDING -> viewModel.getDailyTrending() .collectAsState(initial = emptyList()) } MovieList( movies = movies.value, isDarkModeActive = isDarkModeActive, switchDarkMode = switchDarkMode, onClick = onClick ) }
  82. @Composable fun MovieListViewModel( movieSection: MovieSection, isDarkModeActive: Boolean, switchDarkMode: () ->

    Unit = {}, onClick: (movie: Movie) -> Unit ) { val viewModel: MovieViewModel = viewModel() val movies = when (movieSection) { MovieSection.UPCOMING -> viewModel.getUpComing() .collectAsState(initial = emptyList()) MovieSection.POPULAR -> viewModel.getPopulars() .collectAsState(initial = emptyList()) MovieSection.TRENDING -> viewModel.getDailyTrending() .collectAsState(initial = emptyList()) } MovieList( movies = movies.value, isDarkModeActive = isDarkModeActive, switchDarkMode = switchDarkMode, onClick = onClick ) }
  83. @GerardPaligot DISCOVERING JETPACK COMPOSE

  84. @GerardPaligot DISCOVERING COMPOSE MULTIPLATFORM

  85. UI/UX UI/UX UI/UX BUSINESS LOGIC AND CORE

  86. UI/UX UI/UX UI/UX BUSINESS LOGIC AND CORE ACTUAL ACTUAL ACTUAL

  87. BUSINESS LOGIC AND CORE ACTUAL ACTUAL ACTUAL COMPOSE MULTIPLATFORM

  88. None
  89. BUSINESS LOGIC AND CORE ACTUAL ACTUAL ACTUAL COMPOSE MULTIPLATFORM

  90. UX UX UX BUSINESS LOGIC AND CORE ACTUAL ACTUAL ACTUAL

    COMPOSE MULTIPLATFORM
  91. COMPILER RUNTIME UI FOUNDATION-LAYOUT ANIMATION FOUNDATION MATERIAL 3 Transf m

    @C posable functi s Pro amming model, state management and runtime f the c pose c pil plugin Fundamental c p ents Basics lay ts like B , R and C umn C p ents animati s Basics c p ents and int acti s Implementati of Mat ial Design specificati s Implementati of Mat ial Y
  92. COMPILER RUNTIME UI FOUNDATION-LAYOUT ANIMATION FOUNDATION MATERIAL 3 Transf m

    @C posable functi s Pro amming model, state management and runtime f the c pose c pil plugin
  93. COMPILER RUNTIME UI FOUNDATION-LAYOUT ANIMATION FOUNDATION MATERIAL 3 Transf m

    @C posable functi s Pro amming model, state management and runtime f the c pose c pil plugin
  94. COMPILER RUNTIME UI FOUNDATION-LAYOUT ANIMATION FOUNDATION MATERIAL 3 Transf m

    @C posable functi s Pro amming model, state management and runtime f the c pose c pil plugin WEB-CORE-RUNTIME Runtime dedicated to web apps to gen ate DOM tags
  95. COMPILER RUNTIME UI FOUNDATION-LAYOUT ANIMATION FOUNDATION MATERIAL 3 Transf m

    @C posable functi s Pro amming model, state management and runtime f the c pose c pil plugin WEB-CORE-RUNTIME WEB-CORE Runtime dedicated to web apps to gen ate DOM tags Basics native web c p ents
  96. COMPILER RUNTIME UI FOUNDATION-LAYOUT ANIMATION FOUNDATION MATERIAL 3 Transf m

    @C posable functi s Pro amming model, state management and runtime f the c pose c pil plugin WEB-CORE-RUNTIME WEB-CORE WIDGETS Runtime dedicated to web apps to gen ate DOM tags Basics native web c p ents Modifi s and lay ts
  97. UX UX UX BUSINESS LOGIC AND CORE ACTUAL ACTUAL ACTUAL

    COMPOSE MULTIPLATFORM
  98. UX UX UX BUSINESS LOGIC AND CORE ACTUAL ACTUAL ACTUAL

    COMPOSE MULTIPLATFORM COMPOSE WEB
  99. WHY COMPOSE WEB?

  100. WHY COMPOSE WEB? ▸ Compose Canvas rendering not compatible (yet)

    with web technologies
  101. WHY COMPOSE WEB? ▸ Compose Canvas rendering not compatible (yet)

    with web technologies ▸ Compose Web emit DOM UI for NPM librairies compatibility and SEO
  102. :android-app :desktop-app :data

  103. :android-app :desktop-app :data

  104. :android-app :desktop-app :data :components :theming

  105. :android-app :desktop-app :data :components :theming

  106. plugins { id("com.android.library") } android { ... }

  107. plugins { id("com.android.library") kotlin("multiplatform") id("org.jetbrains.compose") } android { ... }

  108. plugins { id("com.android.library") kotlin("multiplatform") id("org.jetbrains.compose") } android { ... }

    kotlin { android() jvm("desktop") }
  109. import org.jetbrains.compose.compose plugins { id("com.android.library") kotlin("multiplatform") id("org.jetbrains.compose") } android {

    ... } kotlin { android() jvm("desktop") sourceSets { named("commonMain") { dependencies { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material) } } } }
  110. SOURCE SET desktopMain SOURCE SET androidMain SOURCE SET commonMain

  111. @Composable expect fun Font( fontName: String, weight: FontWeight, style: FontStyle

    ): Font SOURCE SET commonMain
  112. @Composable actual fun Font( fontName: String, weight: FontWeight, style: FontStyle

    ): Font { val context = LocalContext.current val id = context.resources .getIdentifier(fontName, "font", context.packageName) return Font(id, weight, style) } SOURCE SET androidMain
  113. @Composable actual fun Font( fontName: String, weight: FontWeight, style: FontStyle

    ): Font { return androidx.compose.ui.text.platform.Font( "font/${fontName}.ttf", weight, style ) } SOURCE SET desktopMain
  114. object Fonts { val roboto: FontFamily @Composable get() = FontFamily(

    Font( "roboto_bold", FontWeight.Bold, FontStyle.Normal ), Font( "roboto_italic", FontWeight.Normal, FontStyle.Italic ), Font( "roboto_regular", FontWeight.Normal, FontStyle.Normal ) ) } SOURCE SET commonMain
  115. object Fonts { val roboto: FontFamily @Composable get() = ...

    } SOURCE SET commonMain
  116. object Fonts { val roboto: FontFamily @Composable get() = ...

    } object Typographies { val roboto: Typography @Composable get() = Typography(defaultFontFamily = Fonts.roboto) } SOURCE SET commonMain
  117. object Fonts { val roboto: FontFamily @Composable get() = ...

    } object Typographies { val roboto: Typography @Composable get() = Typography(defaultFontFamily = Fonts.roboto) } @Composable fun ExploringMoviesTheme( isDarkMode: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { MaterialTheme( colors = if (isDarkMode) DarkColorPalette else LightColorPalette, typography = Typographies.roboto, shapes = shapes, content = content ) } SOURCE SET commonMain
  118. :android-app :desktop-app :data :components :theming

  119. @Composable expect fun RemoteImage( url: String, contentDescription: String?, modifier: Modifier

    = Modifier, contentScale: ContentScale = ContentScale.Fit ) SOURCE SET commonMain
  120. import org.jetbrains.compose.compose plugins { ... } android { ... }

    kotlin { android() jvm("desktop") sourceSets { named("commonMain") { ... } named("androidMain") { dependencies { implementation(Dependencies.coil) } } } }
  121. @Composable actual fun RemoteImage( url: String, contentDescription: String?, modifier: Modifier,

    contentScale: ContentScale ) { Image( painter = rememberCoilPainter(request = url), modifier = modifier, contentDescription = contentDescription, contentScale = ContentScale.Crop, ) } SOURCE SET androidMain
  122. import org.jetbrains.compose.compose plugins { ... } android { ... }

    kotlin { android() jvm("desktop") sourceSets { named("commonMain") { ... } named("androidMain") { ... } named("desktopMain") { dependencies { implementation(Dependencies.okhttp) } } } }
  123. @Composable actual fun RemoteImage( url: String, contentDescription: String?, modifier: Modifier,

    contentScale: ContentScale ) { val image = remember(url) { mutableStateOf<ImageBitmap?>(null) } if (image.value != null) { Image( bitmap = image.value!!, contentDescription = contentDescription, modifier = modifier, contentScale = contentScale ) } } SOURCE SET desktopMain
  124. @Composable actual fun RemoteImage( url: String, contentDescription: String?, modifier: Modifier,

    contentScale: ContentScale ) { val image = remember(url) { mutableStateOf<ImageBitmap?>(null) } LaunchedEffect(url) { } if (image.value != null) { Image( bitmap = image.value!!, contentDescription = contentDescription, modifier = modifier, contentScale = contentScale ) } } SOURCE SET desktopMain
  125. @Composable actual fun RemoteImage( url: String, contentDescription: String?, modifier: Modifier,

    contentScale: ContentScale ) { val image = remember(url) { mutableStateOf<ImageBitmap?>(null) } LaunchedEffect(url) { ImageLoader.load(url)?.let { } } if (image.value != null) { Image( bitmap = image.value!!, contentDescription = contentDescription, modifier = modifier, contentScale = contentScale ) } } SOURCE SET desktopMain
  126. @Composable actual fun RemoteImage( url: String, contentDescription: String?, modifier: Modifier,

    contentScale: ContentScale ) { val image = remember(url) { mutableStateOf<ImageBitmap?>(null) } LaunchedEffect(url) { ImageLoader.load(url)?.let { image.value = org.jetbrains.skia.Image.makeFromEncoded(it) .toComposeImageBitmap() } } if (image.value != null) { Image( bitmap = image.value!!, contentDescription = contentDescription, modifier = modifier, contentScale = contentScale ) } } SOURCE SET desktopMain
  127. WHY SHOULD CARE ABOUT COMPOSE?

  128. WHY SHOULD CARE ABOUT COMPOSE? ▸ Android UI framework never

    changed since 2008 ▸ Need to wait very long time before bug fixes deployed in production ▸ Need to worry about keeping the View hierarchy as flat as possible ▸ Unbundled from OS ▸ Exit View and Fragment ▸ Clarify state ownership and event handling ▸ Writing less code
  129. slack.kotlinlang.org #compose Open Source Project github.com/androidx/androidx

  130. REFERENCES ▸ Discovering Movies, GitHub Repository of Gerard Paligot https://github.com/GerardPaligot/discovering-movies

    ▸ Official documentation about Jetpack Compose, Developer Android Website https://developer.android.com/jetpack/compose/documentation ▸ Pathways Compose https://developer.android.com/courses/pathways/compose ▸ Compose Academy https://compose.academy/ ▸ Samples Compose app by community https://jetpackcompose.app/
  131. THANK YOU! @GerardPaligot