or inflated from XML layouts • Widgets are mutable, can be queried and modified • Attach to parent… ? Compose • UI is described, not instantiated • UI is immutable and cannot be queried • UI widgets are stateless • Compose Compiler builds UI from running composables = The Composition • Composition is a tree-structure of the composables describing the UI 8
also in activity/fragment • Updating the UI requires synchronizing the 2 states: ◦ findViewById() ◦ Change view state or ◦ Data binding Compose: • Will keep track of the composables describing the UI during the initial composition • A state change results in a recomposition being scheduled • Recomposition: re-execution of composables that may have changed in response to state changes • Composition is updated to reflect any changes 11
this problem Compose uses intelligent recomposition. When Compose recomposes due to new input it will: • Only call the functions that might have changed • Skip the rest of the functions Rules & Regulations for Recomposition What to be aware of when writing Compose code 13
when writing Compose code 1. Composable functions can execute in any order 2. Composable functions can execute in parallel 3. Recomposition skips as many composable functions and lambdas as possible 4. Recomposition is optimistic and may be canceled 5. A composable function might be run quite frequently, as often as every frame of an animation 14
fun ButtonRow() { MyFancyNavigation { TCScreen() SecondScreen() EndScreen() } } Compose can recognize that some composable functions are higher priority than others, thus drawing them first The calls to screens might happen in any order: the order in code does not guarantee the same order of execution. Each function needs to be self contained. 15
ListComposable(myList: List<String>) { Column { for (item in myList) { Text("Item: $item") } } Text("Count: ${myList.size}") } Compose can optimize recomposition by running composable functions in parallel. Composable functions might execute on a pool of background threads. Trigger side-effects from callbacks (i.e. onClick) which are guaranteed to run on main thread. 18
why? @Composable fun ListWithBug(myList: List<String>) { var items = 0 Column { for (item in myList) { Text("Item: $item") items++ } } Text("Count: $items") } Not thread safe Forbidden side effects Composables should be side-effects free 20
Text(text = first) Checkbox(checked = second, onCheckedChange = {}) Text(text = third) } 3 - Recomposition skips as many composable functions and lambdas as possible Any one of the Column elements may be recomposed independently of the other 2, if only its corresponding state is what changed. Compose does not need to recompose the full path in the UI tree if nothing has changed. When you need to perform a side-effect, trigger it from a callback. 21
expects to finish recomposition before the parameters change again. If a parameter does change before recomposition finishes, Compose might cancel the recomposition and restart it. When recomposition is canceled, Compose discards the UI tree from the recomposition. If you have any side-effects that depend on the UI being displayed, the side-effect will be applied even if composition is canceled resulting in inconsistent app state. Composable functions should be “idempotent” 22
as often as every frame of an animation Don’t perform expensive operations (ex: reading from local storage) inside a composable If a composable function needs data, you should define it as a parameter Move expensive work outside of the composable and pass the data using mutableStateOf 23
can create your own functions or pass in a custom saver 2. Material components have their own remember functions (ex: rememberTopAppBarScrollState,rememberScaffoldState, etc) for storing their UI state 3. Use remember for transient state (ex: animation) and rememberSaveable for state that needs to be reused across activity and process recreation. 33
to this point in the composition. Use this scope to launch jobs in response to callback events where the response to that event needs to unfold over time and be cancelled if the composable managing that process leaves the composition. 34
property delegation. The 3 declarations are equivalent: val mutableState = remember { mutableStateOf(default) } var value by remember { mutableStateOf(default) } val (value, setValue) = remember { mutableStateOf(default) } 39
state are stateful composables. + The caller does not need to control the state and can use the stateful composable without having to manage the state themselves - Stateful composables are less reusable - Stateful composables are harder to test 40
parameters(*): //Current value to display value: T //event that requests the value to change, T being the new value onValueChange: (T)->Unit (*) YMMV: use more specific event handlers as required. 43
is moved, not duplicated • Encapsulated state is internal to its composable, cannot be modified outside of it • Shareable/reusable • Interceptable callers to stateless composables can decide to ignore or modify events before changing the state (ex: input validation) • Decoupled the state can be stored anywhere 44
UI from the parts in the app that store and change state Composables should receive state as parameters and communicate events up using lambdas AddEditPlantScreen AddEditPlantContent state event 47
higher or lower than you need it for Birds of a feather stick together: If two states change in response to the same event they should be hoisted together Code smells to look out for: unused parameters, too long parameter list • Only pass in the arguments the composable needs • Your composables can have other composables as parameters Write Previews: having previews for your composable is a good litmus test for whether your function is self-contained. 48
compoundable: one state holder can contain another Depending on the scope of the corresponding UI, can have various sizes Can be a plain object, a ViewModel or both There can be multiple state holders for one composable Plain state holders may depend on ViewModel if it is necessary to access business logic or screen state 55
• Composables ◦ Simple UI state management • Plain class state holder: ◦ Complex UI state management ◦ State of UI elements (ex: ScaffoldState, DrawerState, etc…) ◦ Created and remembered in Composition ◦ Follow composable lifecycle 57
ViewModel 58 • Special type of state holder • Provide access to business logic • Longer lifecycle than the composition (survive configuration changes) • Do not keep long-lived references to state that is bound to the lifecycle of the Composition ⇒ Memory Leaks. Use SavedStateHandle to preserve state across process recreation • Operations triggered by ViewModel survive configuration changes
among the elements of a single module (and for object-oriented design, a single class or object) Avoid coincidental cohesion, aim for functional cohesion. Coupling is the dependency among units in different modules and reflects the ways in which parts of one module influence parts of other modules 59
unit should have only limited knowledge about other units: only units "closely" related to the current unit. • Each unit should only talk to its friends; don't talk to strangers. • Only talk to your immediate friends. • You can play by yourself • You can create and play with the toys you make • Share your toys only with close friends and no further. 60
complete critical flows but with a less than optimal user experience. • Apps run full screen (or full window in multi- window mode), but app layout might not be ideal. • Basic support for external input devices (keyboard, mouse, and trackpad) is provided.
a user experience designed for tablets, foldables, and Chrome OS. • Support is provided for multi-tasking, foldable postures, drag and drop, and stylus input.
separately, so at any point in time, your app has two window size classes—one for width and one for height. Available width is usually more important than available height due to the ubiquity of vertical scrolling, so for this case you'll also use width size classes.
WindowWidthSizeClass, ) = Scaffold { padding -> Column( Modifier … .systemBarsPadding() ) { … } } 78 Note: Accommodates both top and bottom insets; navigationBarsPadding() only accommodates the navigation bar.