Slide 1

Slide 1 text

Optimizing UI in Jetpack Compose Rivu Chakraborty 🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected]

Slide 2

Slide 2 text

● India’s first GDE (Google Developer Expert) for Kotlin ● More than decade in the Industry ● Android Architect @ Viacom18 ● Previously ○ Byju’s ○ Paytm ○ Gojek ○ Meesho ● Author (wrote multiple Kotlin books) ● Speaker ● Community Person ● YouTuber (?) 🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] Who am I? 󰞦

Slide 3

Slide 3 text

What’s @Composable

Slide 4

Slide 4 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] What’s @Composable? 🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] Not an annotation processor

Slide 5

Slide 5 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] What’s @Composable? Not an annotation processor Works similar to suspend keyword

Slide 6

Slide 6 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] What’s @Composable? @Composable fun BlueText() { Text( text = "https://www.youtube.com/@rivutalks", color = Color.Blue ) } fun BlueText($composer: Composer) { $composer.start(123) Text( text = "https://www.youtube.com/@rivutalks", color = Color.Blue ) $composer.end() }

Slide 7

Slide 7 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] What’s @Composable? @Composable fun RecompositionScreen(viewModel: RecompositionViewModel) { val recomposeState = viewModel.state Column { Text(text = "Recomposition count ${recomposeState.count}") Button(onClick = { viewModel.recompose() }) { Text(text = "Recompose") } } }

Slide 8

Slide 8 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] What’s @Composable? fun RecompositionScreen($composer: Composer, viewModel: MyViewModel) { $composer.start(123) val recomposeState = viewModel.state Column { Text(text = "Recomposition count ${recomposeState.count}") Button(onClick = { viewModel.recompose() }) { Text(text = "Recompose") } } $composer.end() }

Slide 9

Slide 9 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] What’s Composer? A calling context object, responsible for rendering (composing/recomposing) Composables. The implementation contains a data-structure closely related to Gap Buffer.

Slide 10

Slide 10 text

Recomposition

Slide 11

Slide 11 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] What’s Recomposition Recursion with updated parameters

Slide 12

Slide 12 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] What’s Recomposition fun BlueText($composer: Composer) { $composer.start(123) Text( text = "https://www.youtube.com/@rivutalks", color = Color.Blue ) $composer.end()?.updateScope { nextComposer -> BlueText(nextComposer) } }

Slide 13

Slide 13 text

Breaking Down Composables

Slide 14

Slide 14 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] A Screen

Slide 15

Slide 15 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] An Overloaded Composable Function @Composable fun ProfileScreenOverloaded( profileData: ProfileData, postsList: List, modifier: Modifier = Modifier ) { LazyVerticalGrid( modifier = modifier, columns = GridCells.Fixed(2) ) { item(span = { GridItemSpan(2) }) { Column { AsyncImage( model = profileData.profileImageUrl, contentDescription = profileData.name, modifier = Modifier .size(128.dp) .align(Alignment.CenterHorizontally) .clickable { //Handle Profile Photo Click }, ) Text( text = profileData.name, style = MaterialTheme.typography.h2, modifier = Modifier .align(Alignment.CenterHorizontally), ) Text( text = profileData.location, style = MaterialTheme.typography.body1, modifier = Modifier .align(Alignment.CenterHorizontally), ) Button( onClick = { /*Follow Button Action*/ }, modifier = Modifier .align(Alignment.CenterHorizontally) .background(Color.Black), ) { Text( text = "Follow ${profileData.name}", style = MaterialTheme.typography.body2, ) } Button( onClick = { /*Message Button Action*/ }, modifier = Modifier .align(Alignment.CenterHorizontally) .border(1.dp, color = Color.Black, shape = RoundedCornerShape(5.dp)), ) { Text( text = "Message", style = MaterialTheme.typography.body1, ) } } } items(postsList) { AsyncImage( model = profileData.profileImageUrl, contentDescription = profileData.name, modifier = Modifier .width(167.dp).height(220.dp) .clickable { //Handle Post Photo Click }, ) } } }

Slide 16

Slide 16 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] Breaking it down @Composable fun ProfileScreen( profileData: ProfileData, postsList: List, modifier: Modifier = Modifier ) { LazyVerticalGrid( modifier = modifier, columns = GridCells.Fixed(2) ) { item(span = { GridItemSpan(2) }) { ProfileSection(profileData) } items(postsList) { PostItem(post = it) } } }

Slide 17

Slide 17 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] Breaking it down @Composable fun ProfileSection( profileData: ProfileData, modifier: Modifier = Modifier ) { Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { ProfilePhoto( profileImageUrl = profileData.profileImageUrl, contentDesc = profileData.name, profileId = profileData.profileId) Text( text = profileData.name, style = Typography.titleMedium, ) Text( text = profileData.location, style = Typography.bodyMedium, ) FollowButton( profileId = profileData.profileId, name = profileData.name) MessageButton(profileId = profileData.profileId) } }

Slide 18

Slide 18 text

State Hoisting

Slide 19

Slide 19 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] What’s State? data class MoviesState( val query: String = emptyString(), val movies: List = listOf(), val error: Throwable? = null, val isLoading: Boolean = false, val detail: MovieDetail? = null, val searchHistory: List = emptyList(), val skipSplash: Boolean = false )

Slide 20

Slide 20 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] What’s State? data class MoviesState( val query: String = emptyString(), val movies: List = listOf(), val error: Throwable? = null, val isLoading: Boolean = false, val detail: MovieDetail? = null, val searchHistory: List = emptyList(), val skipSplash: Boolean = false ) val moviesState: MoviesState by stateLiveData.observeAsState(initialState())

Slide 21

Slide 21 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] State Hoisting Having UI logic and UI element state in composables is a good approach if the state and logic are simple. You can leave your state internal to a composable or hoist as required.

Slide 22

Slide 22 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] State Hoisting @Composable fun ShowList( state: ListState.Content, modifier: Modifier = Modifier, onRefresh: () -> Unit = {}, ) { val swipeRefreshState = rememberPullRefreshState(refreshing = state.isRefreshing, onRefresh = onRefresh) Box(modifier = modifier.pullRefresh(swipeRefreshState)) { LazyColumn( modifier = Modifier.matchParentSize(), ) { ... } } }

Slide 23

Slide 23 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] State Hoisting fun FormScreen(viewModel: FormViewModel) { val state = viewModel.state var addItemQuery by remember { mutableStateOf("") } LaunchedEffect(key1 = addItemQuery) { ... } Column { Text(text = "Add form items below") if (state is MyFormState.Content) { MyForm( state = state, onAddItem = { details -> addItemQuery = details }, onUpdateItem = { position, item -> viewModel.updateItem(position, item) }, shouldShowAddItem = true, ) } } }

Slide 24

Slide 24 text

Side Effects and Where to use Them

Slide 25

Slide 25 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] What are Side Effects? A side-effect is a change to the state of the app that happens outside the scope of a composable function. Due to composables' lifecycle and properties such as unpredictable recompositions, executing recompositions of composables in different orders, or recompositions that can be discarded, composables should ideally be side-effect free.

Slide 26

Slide 26 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] LaunchedEffect @Composable fun FormScreen(viewModel: FormViewModel) { val state = viewModel.state var addItemQuery by remember { mutableStateOf("") } LaunchedEffect(key1 = addItemQuery) { if (addItemQuery.isBlank()) return@LaunchedEffect delay(500) viewModel.addItem( MyFormItem(addItemQuery) ) }

Slide 27

Slide 27 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] DisposableEffect @Composable fun ListScreen(viewModel: ListViewModel) { val state = viewModel.myListState Log.d(LogTag, "ListScreen Composed") DisposableEffect(key1 = Unit) { val job = viewModel.loadItems() onDispose { job.cancel() } } ... }

Slide 28

Slide 28 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] SideEffect @Composable fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { FirebaseAnalytics() } SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics }

Slide 29

Slide 29 text

remember

Slide 30

Slide 30 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] remember @Composable fun ShowList( state: ListState.Content, modifier: Modifier = Modifier, onRefresh: () -> Unit = {}, ) { val sortedItems = state.items.let { Log.d(LogTag, "Sorted") it.sortedBy { it.itemId }.toImmutableList() } ... }

Slide 31

Slide 31 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] remember @Composable fun ShowList( state: ListState.Content, modifier: Modifier = Modifier, onRefresh: () -> Unit = {}, ) { Log.d(LogTag, "List Composed isRefreshing ${state.isRefreshing}") val sortedItems by remember(state.items) { mutableStateOf( state.items.let { Log.d(LogTag, "Sorted") it.sortedBy { it.itemId }.toImmutableList() } ) } ... }

Slide 32

Slide 32 text

@Stable and @Immutable

Slide 33

Slide 33 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] @Stable Stable is used to communicate some guarantees to the compose compiler about how a certain type or function will behave.

Slide 34

Slide 34 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] @Immutable Immutable can be used to mark class as producing immutable instances. The immutability of the class is not validated and is a promise by the type that all publicly accessible properties and fields will not change after the instance is constructed. This is a stronger promise than val as it promises that the value will never change not only that values cannot be changed through a setter.

Slide 35

Slide 35 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] @Immutable @Immutable data class MyListItem( val itemId: Int, val itemColor: Color, val backgroundColor: Color )

Slide 36

Slide 36 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] @Stable @Stable data class MyListItem( val itemId: Int, val itemColor: Color, var backgroundColor: Color by mutableStateOf(Color.Blue) )

Slide 37

Slide 37 text

ImmutableList

Slide 38

Slide 38 text

🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected] ImmutableList @Composable fun ShowList( state: ListState.Content, modifier: Modifier = Modifier, onRefresh: () -> Unit = {}, ) { val sortedItems by remember(state.items) { mutableStateOf( state.items.let { Log.d(LogTag, "Sorted") it.sortedBy { it.itemId }.toImmutableList() } ) } ... }

Slide 39

Slide 39 text

Resources ● https://developer.android.com/jetpack/compose/state ● https://github.com/androidx/androidx/blob/androidx-main/compose/docs/compose- component-api-guidelines.md#last-updated-july-19-2023 ● https://youtube.com/@rivutalks

Slide 40

Slide 40 text

Thank You Rivu Chakraborty 🌐https://www.rivu.dev/ youtube.com/@rivutalks @rivuchakraborty @[email protected]