I’ve spent the last few weeks learning about compose and decided to write a post to share my notes. This post is not meant to teach you everything about Compose but rather it’ll be more like a roadmap that you can use to learn Compose or to see what you still don’t know about Compose.
This article will be constantly updated as I learn new things.
State hoisting
State hoisting is a pattern of moving state up to make a component stateless.
When applied to composables, this often means introducing two parameters to the composable.
- value: T – the current value to display
- onValueChange: (T) -> Unit – an event that requests the value to change, where T is the proposed new value
CompositionLocal
Tool for passing data down through the Composition implicitly.
compositionLocalOf
Changing the value provided during recomposition invalidates only the content that reads its current value.
staticCompositionLocalOf
Reads of a staticCompositionLocalOf
are not tracked by Compose. Changing the value causes the entirety of the content lambda where the CompositionLocal is provided to be recomposed.
If the value provided to the CompositionLocal is highly unlikely to change or will never change, use staticCompositionLocalOf
to get performance benefits.
Keeping track of changes
remember
A value computed by remember will be stored in the composition tree, and only be recomputed if the keys to remember change.
When adding memory to a composable, always ask yourself “will some caller reasonably want to control this?”
- If the answer is yes, make a parameter instead.
- If the answer is no, keep it as a local variable.
Remember stores values in the Composition, and will forget them if the composable that called remember is removed. This means you shouldn’t rely upon remember to store important things inside of composables that add and remove children such as LazyColumn
.
rememberSaveable
It behaves similarly to remember, but the stored value will survive the activity or process recreation using the saved instance state mechanism
rememberUpdatedState
In some situations you might want to capture a value in your effect that, if it changes, you do not want the effect to restart. Create a reference to this value which can be captured and updated.
rememberLauncherForActivityResult
rememberCoroutineScope
In order to launch a coroutine outside of a composable, but scoped so that it will be automatically canceled once it leaves the composition
LaunchedEffect
is used for scoping jobs initiated by the composition.rememberCoroutineScope
is for scoping jobs initiated by a user interaction.
LaunchedEffect
Call suspend functions safely from inside a composable.
The coroutine will be cancelled if LaunchedEffect leaves the composition.
If LaunchedEffect is recomposed with different keys, the existing coroutine will be cancelled and the new suspend function will be launched in a new coroutine.
snapshotFlow
Convert Compose State<T> objects into a Flow.
DisposableEffect
DisposableEffect is meant for side effects that need to be cleaned up after the keys change or the composable leaves the Composition
derivedStateOf
derivedStateOf is used when you want a Compose State that’s derived from another State.
State Holder
State holders always need to be remembered in order to keep them in the Composition and not create a new one every time. It’s a good practice to create a method in the same file that does this to remove boilerplate and avoid any mistakes that might occur.
Layouts
LazyColumn and LazyRow
Don’t recycle their children like RecyclerView. It emits new Composables as you scroll through it and is still performant as emitting Composables is relatively cheap compared to instantiating Android Views.
ConstraintLayout
Layout
Instead of controlling how a single composable gets measured and laid out on the screen, you might have the same necessity for a group of composables. For that, you can use the Layout composable to manually control how to measure and position the layout’s children.
Intrinsic measurements
Row(modifier = modifier.height(IntrinsicSize.Min))
Navigation
ViewModel
Animation
You can create an animation value by simply wrapping the changing value with the corresponding variant of animate*AsState
composables.
- Float, Color, Dp, Size, Bounds, Offset, Rect, Int, IntOffset, and IntSize
You can combine multiple transition objects with a + operator.
AnimatedVisibility
- Content within
AnimatedVisibility
(direct or indirect children) can use the animateEnterExit modifier to specify different animation behavior for each of them.
AnimatedContent
SizeTransform
defines how the size should animate between the initial and the target contents.
animateContentSize
- Animates a size change
Crossfade
- Animates between two layouts with a crossfade animation.
Animatable
- Animatable is a value holder that can animate the value as it is changed via
animateTo
. snapTo
sets the current value to the target value immediately.animateDecay
starts an animation that slows down from the given velocity.
InfiniteTransition
- Holds one or more child animations like Transition, but the animations start running as soon as they enter the composition and do not stop unless they are removed.
- Use the
rememberInfiniteTransition
function.
Transition
Transition manages one or more animations as its children and runs them simultaneously between multiple states.
AnimationSpec
Theming
When defining colors, we name them “literally”, based on the color value, rather than “semantically” e.g. Red500 not primary. This enables us to define multiple themes e.g. another color might be considered primary in dark theme or on a differently styled screen.
isSystemInDarkTheme() / MaterialTheme.colors.isLight
color: Color = MaterialTheme.colors.surface, contentColor: Color = contentColorFor(color)
When setting the color of any elements, prefer using a Surface to do this as it sets an appropriate content color CompositionLocal
value, be wary of direct Modifier.background calls which do not set an appropriate content color.
TextFieldDefaults
textFieldColors
outlinedTextFieldColors
ProvideTextStyle
So how do components set a theme typography style? Under the hood they use the ProvideTextStyle
composable (which itself uses a CompositionLocal) to set a “current” TextStyle. The Text composable defaults to querying this “current” style if you do not provide a concrete textStyle parameter.
Resources
Fonts
buildAnnotatedString
Generic Modifiers
Gesture Modifiers
Miscelaneous
LocalSoftwareKeyboardController
LocalOnBackPressedDispatcherOwner and BackHandler
LocalDensity
FocusRequester
Thank you for reading. If you have any suggestions feel free to contact me.
Resources
https://developer.android.com/jetpack/compose/architecture
https://developer.android.com/jetpack/compose/resources
https://developer.android.com/jetpack/compose/animation
https://developer.android.com/jetpack/compose/gestures
https://developer.android.com/jetpack/compose/navigation
https://developer.android.com/jetpack/compose/modifiers-list
https://developer.android.com/jetpack/compose/mental-model
https://developer.android.com/jetpack/compose/compositionlocal
https://developer.android.com/codelabs/jetpack-compose-basics
https://developer.android.com/codelabs/jetpack-compose-layouts
https://developer.android.com/codelabs/jetpack-compose-state
https://developer.android.com/codelabs/jetpack-compose-theming
https://developer.android.com/codelabs/jetpack-compose-animation
https://developer.android.com/codelabs/jetpack-compose-navigation
https://developer.android.com/codelabs/jetpack-compose-advanced-state-side-effects
https://developer.android.com/reference/kotlin/androidx/compose/runtime/saveable/package-summary