# 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

### 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")
)
}
```