# Google I/O 2021 ###### tags: `google-io`, `google-io-2021` ## Keynote ### Android 12 - Biggest Design change ever - User Safety and Privacy - Bluetooth Permision without Location - Widgets - Material You Component ### Android Jetpack - Modern Android Development - Android Studio - Android Gradle plugin 7.0.0 and new DSL - Arctic Fox - Kotlin - Kotlin Symbol Processing (2x faster than kapt) - Jetpack - Macrobenchmark - DataStore - Compose Support - Compose - Modern UI toolkit for Android - Declarative - Compatible with View System (ComposeView) - 1.0 at July ### Building Across Screens - Slidingpane Layout - Constraint Layout 2.1 - Wear OS - Jetpack Libraries for Wear - Tiles - Health Services - Ongoing Activity - Curved Text - Watch Faces - Complications - Remote Interactions - Input ## What's new in Android ### [Android 12](https://developer.android.com/about/versions/12) - Color & Motion - Richer Palette - Color Scheme - 2 Natural + 3 Accent colors - Widgets - Refreshing widgets - Launch animations - New Splash Screen - `Activity.getSplashScreen()` - Notifications - Templates redesign - Navigation from notification to Activity smoothly - Toast - Length limitation & number at stack - Picture in picture - Aplicar efectos sobre View (Blur) ```kotlin imageView.setRenderEffect( RenderEffect.createBlurEffect( 20.0f, 20.0f, SHADER_TILE_MODE ) ) ``` - New ripple effect - Edge effect - Graphics - AVIF - Video - H.264 - Media capabilities ```xml <media-capabilities> <format android:name="HEVC" supported="true"/> <format android:name="HDR10" supported="false"/> <format android:name="HDR10Plus" supported="false"/> </media-capabilities> <application> <property android:name="android.media.PROPERTY_MEDIA_CAPABILITIES" android:resource="@xml/media_capabilities" /> </application> ``` - Audio - Audio-coupled Haptic Playback ```kotlin if(HapticGenerator.isAvailable()){ val audioSession = audioTrack.audioSessionId generator = HapticGenerator.create(audioSession) } ``` - Privacy - More control to the user - Location - Bluetooth Permission - Clipboard access - Foreground restrictions - Expedited Jobs ```kotlin WorkRequest.Builder.setExpedited() // Before Android 12 -> Foregorund service // From Android 12 -> JobInfo.Builder.setExpedited() ``` - OnReveive ContentListener - Before Android 12 - Drag & Drop ```kotlin view.setOnDragListener { view, event -> // react to drag event, get ClipData } ``` - Copy & Paste ```kotlin override fun onTextContextMenuItem(id: Int) { // get ClipData from clipboard } ``` - Keyboard Stickets ```kotlin InputConnectionCompat.OnCommitContentListener{ } ``` - Android 12 - Drag & Drop, Copy & Paste, Keyboard Stickers ```kotlin setOnReceiveContentListener(mimeTypes) { view, payload -> // get data from ContentInfo payload } ``` - Performance - Measuring Jank and Startup with Macrobenchmark - Wear - Now it´s de time - Android Tools - Android Development Tools - Design Tools - Android Gradle plugin - Jetpack: Going Stable - CameraX - Paging 3 - Hilt - beta DataStore - alpha WorkManage 2.7 - alpha Navigation - alpha Macrobenchmark - Compose ## Top 3 things to know in Modern Android Development - Jetpack Libraries - Stable - Compose (july) - Camera X - Hilt - Paging 3.0 - Constraint & MotionLayout - Security Crypto - Fragment - Beta - DataStore - Alpha - Macrobenchmark - EmojiCompat - AppSearch - Google Shortcuts - Room - WorkManager - Tooling - Database Inspector - WorkManager Inspector - Layout Inspector - Kotlin - [Kotlin Processing Symbol](goo.gle/ksp) - All Kotlin Symbols - Faster Builds - Rich APIs ## [What's new in Jetpack](developers.android.com/jetpack) ### Versioning - Alpha - Active development - APIs may be added, changeg, removed - Tested and highly-functional - Beta - Feature stabilization - APIs change onlin in response to **critical** issues or community feedback ### Experimental Annotations (stable)(androidx.annotation:annotation-experimental) - Metadata than helps tools and other developers understand your code ```kotlin @ExperimentalRoomApi or @OptIn(ExperimentalRoomAPI::class) [UnsageOptInUsageError] builder.setAutocloseTimeout(1000, SECONDS) ``` ### CameraX (stable)(androidx.camera:camera) - Unified API surface for controlling and displaying cameras across a variety of Android OS versions and devices - Addressing common feature request and bugs - Exposure compensation - Access to information about camera state and features - Camera2 settings inter-op - Support for the latest device and OS features - HDR Preview - Zoom ratio control - Do-Not-Disturb - Performance enhancements to provide a better experience - Photo capture up to 15% faster - Faster initialization (up to 25% on legacy devices) ```kotlin // Set up the CameraController val cameraController = LifecycleCameraController(context) cameraController.bindToLifecycle(lifecycleOwner) // Attach the CameraController to PreviewView val previewView = findViewById(R.id.preview_view) previewView.setController(cameraController) // Use the CameraController cameraController.takePicture(...) ``` ### AppSearch (alpha) (androidx.appsearch:appsearch) - High-performance full-text search for app-local and device-wide use cases - Full-featured versus SQL - Prefix indexing - Relevance ranking - Usage scoring - Advanced full-text search - Fuzzy matching - Query expansion - Languaje support - Includes schemas for common object types based on schemas.org - Centralized storage on Android S+ for integrating into device-wide search ### DataStore (beta) (androidx.datastore:datastore) - Data storage solution tha provides a simple, robust alternative to SharedPreferences - Replacement for SharedPreferences - Fully asynchronous with Kotlin coroutines / Flow support - Multible backing implementations - Proto DataStore (strongly typed) - Preference DataStore (key-value pairs) - Plug in your own ```kotlin val Context.prefStore: DataStore<Preferences> by preferencesDataStore( name = USER_PREFERENCES_NAME, produceMigrations = { context -> // Since we're migrating from SharedPreferences, // add a migration based on the SharedPreferences name. listOf(SharedPreferencesMigration(context, USER_PREFERENCES_NAME)) } ) ``` ### Security Crypto (stable) (androidx.security:security-crypto) - Safely and easily encrypt Files and SharedPreferences ```kotlin val prefs: SharedPreferences = EncryptedSharedPreferences.create( context, "prefs_file_name", mainKey, prefKeyEncryptionScheme = AES256_SIV, prefValueEncryptionScheme = AES256_GCM, ) // Use the resulting SharedPreferences object as usual prefs.edit() .putBoolean("show_completed", true) .apply() ``` ### Hilt (stable) (androidx.hilt:hilt) - Simplified dependendy injection solution based on Dagger, built for Jetpack ```kotlin @HiltViewModel class MyViewModel @Inject constructor( val handle: SavedStateHandle, val repository: Repository, ) : ViewModel { //... } @AndroidEntryPoint class MyFragment : Fragment() { val viewModel: MyViewModel by viewModels() val navigationGraphScoped: MyViewMode by hiltNavGraphViewModels(R.id.nav_graph) } @HiltWorker class MyWorker @AssistedInject constructor( @Assisted context: Context, @Assisted params: WorkerParameters, repo: Repository, ) : Worker { //... } ``` - Testing anotations ```kotlin @Module @TestInstallIn( components = SingletonComponent::class, replaces = RepositoryModule::class, ) object FakeRepositoryModule { @Provides fun provideRepo() = FakeRepository() } ``` ### WorkManager (stable) (androidx.work:work) - Schedule deferrable, asynchronous tasks thant must be run reliably - 2.5+ -> Multiple processes support - 2.7 -> Handles Android S foreground restrictions - WorkManager Inspector available in Android Studio Arctic Fox ### Room (stable) (androidx.room:room) - Database persistence layer designed for usability, safety and testability - Experimental support for Kotlin Symbol Processing (200% over kapt) - Built-in support for enums - QueryCallback to simplify tasks like logging after execution - ProvidedTypeConverter ```kotlin @ProvidedTypeConverter class TimeStampConverter(timeformat: String) { @TypeConverter fun fromTimestamp(value: Long) { //... } } Room.inMemoryDatabaseBuilder(context, CacheDatabase::class) .addTypeConverter(TimeStampConverter(getPreferredTimeFormat())) .build ``` ### ConstraintLayout (stable) (androidx.constraintlayout:constraintlayout) - Flexible, intuitive system for positioning and animating user interfaces - version 2.0 with MotionLayout ### Fragment (stable) (androidx.fragment:fragment) - Abstraction fo segmentin a user interface into reusable components - Under-the-hood improvements to increase stability an reduce undocumented behavior - `ActivityResult` integration - FragmentOnAttachListener ```kotlin // Obtain the fragment manager. May be a childFragmentManager, // if in a fragment, to observe child attachment val fragmentManager = supportFragmentManager val listener = FragmentOnAttachListener { fragmentManager, fragment -> // Respond to the fragment being attached. } fragmentManager.addFragmentOnAttachListener(listener) ``` ### Navitagion (alpha) (androidx.navigation:navigation) - Fragmework for navigating between destinations within an app, now with multiple backstacks ### Google Shortcuts (alpha) (androidx.core:core-google-shortcuts) - Dynamic shortcuts for Google Assistant and other Google products ```kotlin ShortcutInfoCompat siCompat = ShortcutInfoCompat.Builder(context, "id_cappuccino") .setShortLabel("Capuccino") .setIntent(Intent(context. OrderCappuccion::class.java)) .addCapability( "actions.intent.ORDER_MENU:ITEM", "menuItem.name", asList("cappuccino") ) .build() ShortcutManagerCompat.pushDynamicShortcut(context, siCompat) ``` ### EmojiCompat (alpha) (androidx.emoji2:emoji2-views) - All the emoji, everywere - Included by default in AppCompat 1.4.0+ ### Paging 3.0 (stable) (androidx.paging.paging) - Load and display small chunks of data to improve network and system resource consumption - First-class support for Kotlin coroutines and Flow - Improvements to repository and presentation layers - Support for headers, footers, separators and loading state ### Macrobenchmark (alpha) (androidx.benchmark.macro:macro) - Accurately measure app startup and scrolling performance, both locally and in CI - View performance profiling results directly in Android Studio - Check for regressions in CI before they hit users ### Compose integrations - Hilt, Paging, Navigation, Activity, ViewModel, ConstraintLayout ### Form factors - androidx.window - androidx.slidingpanlayout - androidx.wear ## State of Kotlin on Android ### Momentum - Usage - 80% Top 1000 apps - 60% professional developers - Kotlin on services - Benefits - Expressivenes - Safety - Interroperability - Structured Concurrency - Null Safety - >10% reduction of user crashed - Kotlin Google Developer Experts (50)s ### Tools updates - Gradle DSL with Android Gradle Plugin 7.0.0 (alpha) - Kotlin Symbol Processing (beta) - First-class Symbol Processing in Kotlin - All Kotlin Symbols - Faster Builds (2x) - Rich APIs - Rewrite Compiler (from Kotlin 1.5) ### Libraries APIs - Android KTX - Play Core - Maps & Places - Rich APIs - New libraries with Kotlin - Compose - Kotlin gRPC (1.0 December 2020) - Kotlin Protobuf bindings (1.0 May 2021) - Coroutines - Flows - StateFlow - SharedFlow - LiveData to UIs, but StateFlows - Coroutines Debugger - Deprecation - Synthetics for Views -> Migrate to ViewBinding - Parcelize: change `plugin "kotlin-parcelize"` and import `import kotlinx.parcelize.Parcelize` ### Modern languaje features - Operator overloaging - Extension functions - Named parameters - Coroutines - Data classes ## What's new in Android development tools ### Roadmap - Android Studio 4.1 - Database Inspector - Tensorflow lite models - Native memory profiler - Android Studio 4.2 - Project upgrade assistant - Safe Args support - Apply changes update - Versioning Schema - Artic Fox (2020.3.1) - 2020 -> Year of Intellij Version - 3 -> Intellij Major Version - Studio Major Version ### Design - Jetpack Compose - Live literals - Preview View - Constraint Preview - Interactive preview - Animated drawables - Motion editor update - Layout validation & inspector - Accesible scanner ### Devices - Different Screen Sizes - Tablets/Foldables - WearOS - Android TV - Automotive OS - General - Device manager - Wireless ADB ### Productivity - Test retention - Android emulator snapshots wich can capture test state failures - Instrumentation test - Profiling - WorkManager Inspector - IDE Refactoring tool - Migrate to Non-Transitive R Classes ## Design Tools News ### Design Tools - Layout Editor - Design surface improvements - Support for ConstraintLayout 2.1 - Accessibility Scanner - Layout Validation - Performance improvements - Support for Locales - Wear & Tablet - Layout Inspector - Stability and performance improvements - Compose support - Animated Vector Drawable - Preview Animated Vector Drawable ### ConstraintLayout 2.0 - VirtualLayout: Flow - Performance improvements - MotionLayout - Build in ConstraintLayout 2.0 - Coordinate easily animations of multiple views - Seekable, reversible animations - Motion Editor - Android Studio 4.0+ - Build, edit and preview animations - Convert from ConstraintLayout in 4.1 & Artic Fox ### ConstraintLayout 2.1 - Constraints - Flow improvements - Carousel - VirtualLayout to create Carousels - Easily build complex effects - Rotation support - Rotation paths - Rotation gestures - Motion Effect - Motion Helper. - Apply effectos on a set of views depending on their movement - ViewTransitions - Specify reusable single view transitions - Trigger them as neede or use them with MotionEffect - Motion Label - Motion helper - Animate labels easily - Live resize, texture - Foldable - Inject fold position in your layout using a ReactiveGuide helper - With MotionLayout, updates to the fold can be animated automatically - MotionLayout can alse be used to model different device states (open, fold, etc) and animate the transitions between them ```xml <androidx.constraintlayout.widget.ReactiveGuide android:id="@+id/fold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:reactiveGuide_animateChange="true" app:reactiveGuide_valueID="@id/fold" ``` ```kotlin if(foldFeature.isSeparating){ val foldPosition = //get position of the fold ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold) myMotionLayout.transitionToState(R.id.halfopen) } else { ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, 0) myMotionLayout.transitionToState(R.id.open) } ``` ### ConstraintLayout in Compose - ConstraintLayout-Compose 1.0 - Same ConstraintLayout engine - Relative positioning - DSL/JSON - MotionLayout ### Compose Tools - Compose Preview ```kotlin @Preview @Composable fun PostPreview() { ThemedPreview { GenericProvider() PostCartTop(post = post1) } } ``` - Live Literals - Sample Data ```kotlin class PostProvider: CollectionPreviewPArameterProvider<Post>( listOf( post1, post2, post3, post4, post5, ) ) @Preview @Composable fun PostPreview(@PreviewParameter(provider = PostProvider::class) previewPost: Post) { ThemedPreview { GenericProvider() PostCartTop(post = previewPost) } } ``` - Interactive Preview - Anitation Preview ## Macrobenchmark: Measuring Jank and Startup ### Measuring performance is important - Metrics from the field are not enough - Performance testing is hard - Want local measurement - Existing Benchmark library is limited - Stable measurement is not easy ### Introducing Macrobenchmark (androidx.benchmark.macro) - Measures real performance on real device - Captures startup and jank metrics and traces - Enables custom scenarios - Metrics - StartupTimingMetric: App launch time - FrameTimingMetric: Cost of producing each frame - CompilationMode - None: Worst case - SpeedProfile: Common case - Speed: Best case - StartupMode - Hot: Activity.onResume - Warm: Activity.onStart - Cold: Process Init ```kotlin @get:Rule val benchmarkRule = MacrobenchmarkRule() @Test fun startup() = benchmarkRule.measureRepeated( packageName = "mypackage.myapp", metrics = listOf(StartupTimingMetric()), compilationMode = CompilationMode.SpeedProfile(), startupMode = StartupMode.COLD, iterations = 5, ) { pressHome() startActivityAndWait(Intent().apply { action = "mypackage.myapp.myaction" }) } ``` ### How does it work - Setup -> Test -> Process -> Present Results - Metrics: results.json - Traces: iter0.trace, iter1.trace, etc... - Studio Summary ### Benchmarking With Studio - At Android Studio Artic Fox - activityStart - resource loading - view inflation - loading icons ### Tracing (androix.tracing.trace) ```kotlin import androidx.tracing.trace; fun loadIcon() = trace("LoadingIcon"){ // ... } ``` - d.android.com/benchmark - github.com/android/performance-samples/ ## What's new in Android testing tools ### How we're making testing more - Reliable - All unit and instrumentation test run throgh a unified Android Gradle test runner, learning to more consistent results - Scalable - Parallel device testing streaming result to all-new Test Matrix. Integration of UTP enables functionality for more scalable testing, like Gradle Managed Devices - Productive - Rich test results enable by UTP, such as Emulator Snapshots for test failures, streamed back to the Test Matrix ## Better experience - Use Android Gradle Plugin to execute Android Test since Artic Fox - Enable Run Android Instrumented Tests using Gradle - Under the hood, is delegating to Android Test Runner ### What is UTP - A Unified Test Platform - Extensible platform that abstracts the testing environment from feature development. - Modular vía plugins - Functionality is added to test execution via modular plugins, allowing features to be developed and updated independently - Shorter release cycles - Modular development than only needs to be implemented one way for all testing environment leads to more features, sooner ### Gradle Manageg Devices - Define virtual devices and device groups using Gradle DSL - Device lifecycles fully managed by Android Gradle plugin (AGP) - AGP downloads any required images and SDK components - Caches test results and utilizes emulator snapshots for faster continuous testing ```groovy android { testOptions { devices { pixel2api29(com.android.build.api.dsl.ManagedVirtualDevice) { device = "Pixel 2" apiLevel = 29 systemImageSource = "google" abi = "x86" } nexus9api30(com.android.build.api.dsl.ManagedVirtualDevice) { device = "Nexus 9" apiLevel = 30 systemImageSource = "google" abi = "x86" } } deviceGroups { phoneAndTablet { targetDevices.addAll(devices.pixel2api29, devices.nexus9api30) } } } } ``` ```terminal //Execute at expecific device gradlew -Pandroid.experimental.androidTest.useUnifiedTestPlatform=true [device-name][BuildVariant]AndroidTest gradlew -Pandroid.experimental.androidTest.useUnifiedTestPlatform=true pixel2api29DebugAndroidTest //Execute in group gradlew -Pandroid.experimental.androidTest.useUnifiedTestPlatform=true [group-name]Group[BuildVariant]AndroidTest gradlew -Pandroid.experimental.androidTest.useUnifiedTestPlatform=true phoneAndTabletGroupDebugAndroidTest ``` - Enable snapshots for Intrumentation Test ```groovy android { testOptions { emulatorSnapshots { enableForTestFailures = true maxSnapshotsForTestFailures = 2 } } } ``` - Resources - g.co/androidstudio/preview - d.android.com/io-21/android-testing ## What's new in Jetpack Compose ### Compose 1.0 at July ### Declarative UI Toolkit #### Actual Visual System - Complex to mantain UI state - XML Layout (state) - TextView (state) - Button (state) - LinearLayout (state) - Activity/Fragment (state) - findViewById() - label.setText() - button.setVisibility() - linearContainer.addView() ### Declarative System State1 -> UI1 State2 -> UI2 ```kotlin @Composable fun ConversationScreen() { val viewModel: ConversationViewModel = viewModel() val messages by viewModel.messages.observeAsState() MessagesList(messages) } @Composable fun MessageList(messages: List<String>) { Column { if (messages.size == 0) { Text("No messages") } else { messages.forEach { message -> Text(text = message) } } } } ``` ```kotlin // Manage state internally @Composable fun MessageList(messages: List<String>) { Column { var selectAll by remember { mutableStateOf(false) } Checkbox( checked = selectAll, onCheckChange = { checked -> selectAll = checked } ) } } // or externally @Composable fun MessageList( messages: List<String>, selectAll: Boolean, onSelectAll: (Boolean) -> Unit, ) { Column { Checkbox( checked = selectAll, onCheckChange = onSelectAll ) } } ``` - You completely descrube your UI for a given state - The framework updates your UI when the state changes ![Compose ViewModel with State Architecture](https://i.imgur.com/l8IX6QL.png) ### UI Toolkit - Button - Text - Card - FloatingActionButton - TopAppBar - MaterialTheme: colors, typography, shapes - Row -> Horizontal LinearLayout - Column -> Vertical LinearLayout - Box -> FrameLayout - ConstraintLayout ```kotlin ConstraintLayout { OutlinerAvatar(modifier = Modifier.constrainAs(avatar){ centerHorizontallyTo(parent) centerAround(image.bottom) }) } ``` - Layout ```kotlin Layout( content = content, modifier = modifier, ) { measurables, constrains -> // Do some calculations layout(width, height) { placeables.forEach { placeable -> placeable.place(x, y) } } } ``` ### Animation System ```kotlin val radius by animateDpAsState( if(selected) 28.dp else 0.dp ) Surface( shape = RoundedCornerShape( topStart = radius ) ) { ... } ``` ### Accesibillity ```kotlin @Composable fun ShareButton(onClick: () -> Unit) { IconButton(onClick = onClick) { Icon( imageVector = Icons.Filled.Share, contentDescription = stringResource(R.string.label_share) ) } } ``` ### Testing ```kotlin @Test fun circleAnimation_midAnimation_screenshot() { // Test setup // Advance clock composeTestRule.mainClock.advbanceTimeBy(600) // Assertions } ``` ### Made over Kotlin ```kotlin @Composable fun MessageList( messages: List<Message>, onClick: () -> Unit, ) { val selectAll by remember { mutableStateOf(false) } LazyList { items(messages) { message -> MessageItem(onClick) } } } ``` - Coroutines ```kotlin Box( modifier = Modifier.pointerInput { coroutineScope{ while (true) { val offset = awaitPointerEventScope { awaitFirstDown().position } launch { animatedOffset.animateTo(offset) } } } } ) { } ``` ### Owl App Example ```kotlin @Composable fun YellowTheme( content: @Composable () -> Unit ){ val colors = if(isSystemInDarkTheme()){ YellowThemeDark } else { YellowThemeLight } MaterialTheme(colors, content) } @Composable fun TopicsScreen(){ YellowTheme { Scaffold( backgroundColor = MaterialTheme.colors.surface ) { TopicsList() } } } @Composable fun TopicsList(topics: List<Topic>) { LazyColumn(modifier = modifier) { items(topics) { topic -> TopicChip(topic) } } } @Composable fun TopicChip(topic: Topic, selected: Boolean) { val radius by abunateDpAsState( if (selected) 20.dp else 0.dp ) Card( shape = RoundedCornerShape( topStart = radius ) ){ Row { Box{ Image(topic.image) if(selected){ Icon(Icons.Filled.Done) } } Text(text = topic.name) } } } ``` ### Build to Interop Compose <-> View ## Using Jetpack libraries in Compose ### Jetpack Libraries - Room - Paging - Coroutines/Flow - ViewModel - Hilt ### HomeViewModel implementation ```kotlin data class HomeUiState( val plantCollections: List<Collection<Plant>> = emptyList(), val loading: Boolean = false, val refreshError: Boolean = false, ) @HiltViewModel class HomeViewModel @Inject constructor( private val plantsRepository: PlantsRepository ) : ViewModel() { private val _uiState = MutableStateFlow(HomeUiState(loading = true)) val uiState: StateFlow<HomeUiState> = _uiState val pagedPlants: Flow<PagingData<Plant>> = plantsRepository.plants init { viewModelScope.launch { val collections = plantsRepository.getCollections() _uiState.value = HomeUiState(plantCollections = collections) } } } ``` ### Home Screen Implementation ```kotlin @AndroidEntryPoint class HomeFragment : Fragment(){ override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ) = ComposeView(requireContext()).apply { setContent { val homeViewModel: HomeViewModel = viewModel() val uiState by hombeViewModel.uiState.collectAsState() val plantList = homeViewModel.pagedPlants // HomeScreen is now totally independent of the HomeViewModel HomeScreen(uiState, plantList, onPlantClick = { // TODO: Navigate to Plant Detail screen }) } } } @Composable fun HomeScreen( uiState: HomeUiState, plants: Flow<PagedData<Plant>>, onPlantClick: (Plant) -> Unit, ) { if(uiState.loading) { } else { SearchPlants() CollectionsCarousel( carouselState = uiState.carouselState, onPlantClick = onPlantClick ) PlantList(plants) } } @Composable fun PlantList(plants: Flow<PagingData<Plant>>) { val pagedPlantItem if(pagedPlantItems.loadState.refresh == LoadState.Loading) { item { LoadingIndicator() } } itemsIndexed(pagedPlantItems) { index, plant -> if(plant != null){ PlantItem(plant) } else { PlantPlaceholder() } } if(pagedPlantItems.loadState.append == LoadingState.Loading) { item { LoadingIndicator() } } } } ``` ### How to reuse a Composable ```kotlin @Composable fun CollectionsCarousel( //State in, //Event out carouselState: CollectionsCarouselState, onPlantClick: (Plant) -> Unit, ) { //... } data class PlantCollection( val name: String, @IdRes val asset: Int, val plants: List<Plant> ) class CollectionsCarouselState( private val collections: List<PlantCollection> ){ var selectedIndex: Int? by mutableStateOf(null) privateSet val isExpanded: Boolean get() = selectedIndex != null var plants by mutableStateOf(emptyList<Plant>()) private set fun onCollectionClick(index: Int) { if(index >= collections.size || index < 0) return if(index == selectedIndex) { selectedIndex = null } else { plants = collections[index].plants selectedIndex = index } } // ... } ``` ### State Holders - ViewModel type: High level screen composables - first composable used in an Activity, Fragment or destination of Navigation graph - For example: HomeScreen or LoginScreen - Regular class: Reusable composables that could be potentially used multiple times on the same screen - For example: Carousel or ExpandableCard ### ViewModel - Benefits as state holder of a screen level composable - Survives configuration changes - Data can survive process death via SavedStateHandle - Better place for state whose lifecycle matches the screen - Built-in coroutine scope: viewModelScope ### Navigation with Compose - Migration steps - Navigation with Fragments -> Navigation with Fragments with Compose code -> Navigation Compose - Using Fragments with View and XML -> Using Fragments with Compose code -> Using Navigation Compose - Navigation component: A generic runtime with: - Kotlin DSL - Automatic scoping of Lifecycle, ViewModels and Saved State - Deep linking - Returning results - System back button integration ```kotlin setContent { val navController = rememberNavController() Scaffold( bottomBar = { /* Can use the NavController */ } ) { innerPadding -> NavHost( navController, startDestination = "home", modifier = Modifier.padding(innerPadding) ) { // ... } } } ``` ```kotlin NavHost(navController, startDestination = "home") { composable(route = "home") { val homeViewModel: HomeViewModel = hiltNavGraphViewModel() val uiState by hombeViewModel.uiState.collectAsState() val plantList = homeViewModel.pagedPlants // HomeScreen is now totally independent of the HomeViewModel HomeScreen(uiState, plantList, onPlantClick = { plant -> navController.navigate("plant/${plant.id}") }) } composable( route = "plant/{id}", arguments = listOf(navArgument("id") { type = NavType.IntType }), ) { backstackEntry -> val plantViewModel: PlantViewModel = hiltNavGraphViewModel() val plant by plantViewModel.plantDetails.collectAsState() PlantDetailScreen(plant) } } ``` ```kotlin @HiltViewModel class PlantViewModel @Inject constructor( plantsRepository: PlantsRepository, savedStateHandle: SavedStateHandle, ) : ViewModel() { val plantDetails: Flow<Plant> = plantsRepository.getPlantDetails( savedStateHandle.get<In>("id") ) } ```