diff --git a/JetNews/app/build.gradle.kts b/JetNews/app/build.gradle.kts index 0b7a08cfc..c9ebb46b7 100644 --- a/JetNews/app/build.gradle.kts +++ b/JetNews/app/build.gradle.kts @@ -131,7 +131,7 @@ dependencies { implementation(libs.androidx.lifecycle.viewmodel.savedstate) implementation(libs.androidx.lifecycle.livedata.ktx) implementation(libs.androidx.lifecycle.viewModelCompose) - + implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.window) diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeRoute.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeRoute.kt index b6617d932..cf7907ba7 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeRoute.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeRoute.kt @@ -21,10 +21,11 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.remember +import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.example.jetnews.ui.article.ArticleScreen import com.example.jetnews.ui.home.HomeScreenType.ArticleDetails import com.example.jetnews.ui.home.HomeScreenType.Feed @@ -40,6 +41,7 @@ import com.example.jetnews.ui.home.HomeScreenType.FeedWithArticleDetails * @param openDrawer (event) request opening the app drawer * @param snackbarHostState (state) state for the [Scaffold] component on this screen */ +@OptIn(ExperimentalLifecycleComposeApi::class) @Composable fun HomeRoute( homeViewModel: HomeViewModel, @@ -48,7 +50,7 @@ fun HomeRoute( snackbarHostState: SnackbarHostState = remember { SnackbarHostState() } ) { // UiState of the HomeScreen - val uiState by homeViewModel.uiState.collectAsState() + val uiState by homeViewModel.uiState.collectAsStateWithLifecycle() HomeRoute( uiState = uiState, diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/interests/InterestsScreen.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/interests/InterestsScreen.kt index 20e0e7b8b..00cf3ef3c 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/interests/InterestsScreen.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/interests/InterestsScreen.kt @@ -51,7 +51,6 @@ import androidx.compose.material3.Tab import androidx.compose.material3.TabRow import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable @@ -69,6 +68,8 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.constrainHeight import androidx.compose.ui.unit.constrainWidth import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.example.jetnews.R import com.example.jetnews.data.Result import com.example.jetnews.data.interests.InterestSection @@ -166,15 +167,16 @@ fun InterestsScreen( * Remembers the content for each tab on the Interests screen * gathering application data from [InterestsViewModel] */ +@OptIn(ExperimentalLifecycleComposeApi::class) @Composable fun rememberTabContent(interestsViewModel: InterestsViewModel): List { // UiState of the InterestsScreen - val uiState by interestsViewModel.uiState.collectAsState() + val uiState by interestsViewModel.uiState.collectAsStateWithLifecycle() // Describe the screen sections here since each section needs 2 states and 1 event. // Pass them to the stateless InterestsScreen using a tabContent. val topicsSection = TabContent(Sections.Topics) { - val selectedTopics by interestsViewModel.selectedTopics.collectAsState() + val selectedTopics by interestsViewModel.selectedTopics.collectAsStateWithLifecycle() TabWithSections( sections = uiState.topics, selectedTopics = selectedTopics, @@ -183,7 +185,7 @@ fun rememberTabContent(interestsViewModel: InterestsViewModel): List } val peopleSection = TabContent(Sections.People) { - val selectedPeople by interestsViewModel.selectedPeople.collectAsState() + val selectedPeople by interestsViewModel.selectedPeople.collectAsStateWithLifecycle() TabWithTopics( topics = uiState.people, selectedTopics = selectedPeople, @@ -192,7 +194,8 @@ fun rememberTabContent(interestsViewModel: InterestsViewModel): List } val publicationSection = TabContent(Sections.Publications) { - val selectedPublications by interestsViewModel.selectedPublications.collectAsState() + val selectedPublications by interestsViewModel.selectedPublications + .collectAsStateWithLifecycle() TabWithTopics( topics = uiState.publications, selectedTopics = selectedPublications, diff --git a/Jetcaster/README.md b/Jetcaster/README.md index f85213153..6e52c72bd 100644 --- a/Jetcaster/README.md +++ b/Jetcaster/README.md @@ -64,11 +64,11 @@ Using the example of the home screen in the [`com.example.jetcaster.ui.home`](ap - The ViewModel is implemented as [`HomeViewModel`][homevm], which exposes a `StateFlow` for the UI to observe. - [`HomeViewState`][homevm] contains the complete view state for the home screen as an [`@Immutable`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Immutable) `data class`. - - The Home Compose UI in [`Home.kt`][homeui] uses [`HomeViewModel`][homevm], and observes it's [`HomeViewState`][homevm] as Compose [State](https://developer.android.com/reference/kotlin/androidx/compose/runtime/State), using [`collectAsState()`](https://developer.android.com/reference/kotlin/androidx/compose/package-summary#collectasstate): + - The Home Compose UI in [`Home.kt`][homeui] uses [`HomeViewModel`][homevm], and observes it's [`HomeViewState`][homevm] as Compose [State](https://developer.android.com/reference/kotlin/androidx/compose/runtime/State), using [`collectAsStateWithLifecycle()`](https://developer.android.com/reference/kotlin/androidx/lifecycle/compose/package-summary#(kotlinx.coroutines.flow.StateFlow).collectAsStateWithLifecycle(androidx.lifecycle.LifecycleOwner,androidx.lifecycle.Lifecycle.State,kotlin.coroutines.CoroutineContext)): ``` kotlin val viewModel: HomeViewModel = viewModel() -val viewState by viewModel.state.collectAsState() +val viewState by viewModel.state.collectAsStateWithLifecycle() ``` This pattern is used across the different screens: diff --git a/Jetcaster/app/build.gradle.kts b/Jetcaster/app/build.gradle.kts index 29394e3d2..c55dea024 100644 --- a/Jetcaster/app/build.gradle.kts +++ b/Jetcaster/app/build.gradle.kts @@ -105,6 +105,7 @@ dependencies { implementation(libs.androidx.lifecycle.runtime) implementation(libs.androidx.lifecycle.viewModelCompose) + implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.window) diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt index c4e7683aa..78d3b7ed5 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt @@ -56,7 +56,6 @@ import androidx.compose.material.icons.filled.Search import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -68,6 +67,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import com.example.jetcaster.R @@ -90,12 +91,13 @@ import java.time.Duration import java.time.LocalDateTime import java.time.OffsetDateTime +@OptIn(ExperimentalLifecycleComposeApi::class) @Composable fun Home( navigateToPlayer: (String) -> Unit, viewModel: HomeViewModel = viewModel() ) { - val viewState by viewModel.state.collectAsState() + val viewState by viewModel.state.collectAsStateWithLifecycle() Surface(Modifier.fillMaxSize()) { HomeContent( featuredPodcasts = viewState.featuredPodcasts, diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt index e2cbe2b04..30337a599 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt @@ -49,7 +49,6 @@ import androidx.compose.material.icons.rounded.PlayCircleFilled import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -68,6 +67,8 @@ import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension.Companion.fillToConstraints import androidx.constraintlayout.compose.Dimension.Companion.preferredWrapContent +import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import coil.request.ImageRequest @@ -85,6 +86,7 @@ import com.example.jetcaster.util.viewModelProviderFactoryOf import java.time.format.DateTimeFormatter import java.time.format.FormatStyle +@OptIn(ExperimentalLifecycleComposeApi::class) @Composable fun PodcastCategory( categoryId: Long, @@ -101,7 +103,7 @@ fun PodcastCategory( factory = viewModelProviderFactoryOf { PodcastCategoryViewModel(categoryId) } ) - val viewState by viewModel.state.collectAsState() + val viewState by viewModel.state.collectAsStateWithLifecycle() /** * TODO: reset scroll position when category changes diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/discover/Discover.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/discover/Discover.kt index b2c872f0e..d3fa5ceeb 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/discover/Discover.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/discover/Discover.kt @@ -30,22 +30,24 @@ import androidx.compose.material.Tab import androidx.compose.material.TabPosition import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.example.jetcaster.data.Category import com.example.jetcaster.ui.home.category.PodcastCategory import com.example.jetcaster.ui.theme.Keyline1 +@OptIn(ExperimentalLifecycleComposeApi::class) @Composable fun Discover( navigateToPlayer: (String) -> Unit, modifier: Modifier = Modifier ) { val viewModel: DiscoverViewModel = viewModel() - val viewState by viewModel.state.collectAsState() + val viewState by viewModel.state.collectAsStateWithLifecycle() val selectedCategory = viewState.selectedCategory diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt index f35df88fc..f12f4a0b6 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt @@ -59,7 +59,6 @@ import androidx.compose.material.icons.rounded.PlayCircleFilled import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -77,6 +76,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.layout.FoldingFeature import coil.compose.AsyncImage import coil.request.ImageRequest @@ -94,6 +95,7 @@ import kotlinx.coroutines.flow.StateFlow /** * Stateful version of the Podcast player */ +@OptIn(ExperimentalLifecycleComposeApi::class) @Composable fun PlayerScreen( viewModel: PlayerViewModel, @@ -101,7 +103,7 @@ fun PlayerScreen( onBackPress: () -> Unit ) { val uiState = viewModel.uiState - val devicePostureValue by devicePosture.collectAsState() + val devicePostureValue by devicePosture.collectAsStateWithLifecycle() PlayerScreen(uiState, devicePostureValue, onBackPress) } diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/data/FakeData.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/data/FakeData.kt index fee5f9639..9b85a8cc1 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/data/FakeData.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/data/FakeData.kt @@ -40,7 +40,7 @@ private val initialMessages = listOf( ), Message( "Taylor Brooks", - "@aliconors Take a look at the `Flow.collectAsState()` APIs", + "@aliconors Take a look at the `Flow.collectAsStateWithLifecycle()` APIs", "8:05 PM" ), Message( diff --git a/Jetsnack/app/build.gradle.kts b/Jetsnack/app/build.gradle.kts index b45c904af..4d5a60d58 100644 --- a/Jetsnack/app/build.gradle.kts +++ b/Jetsnack/app/build.gradle.kts @@ -98,6 +98,7 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.activity.compose) implementation(libs.androidx.lifecycle.viewModelCompose) + implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.constraintlayout.compose) diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/Cart.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/Cart.kt index 9a6a63610..3b49a0424 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/Cart.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/Cart.kt @@ -53,7 +53,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.DeleteForever import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -70,6 +69,8 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ChainStyle import androidx.constraintlayout.compose.ConstraintLayout +import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.example.jetsnack.R import com.example.jetsnack.model.OrderLine @@ -86,13 +87,14 @@ import com.example.jetsnack.ui.theme.AlphaNearOpaque import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.utils.formatPrice +@OptIn(ExperimentalLifecycleComposeApi::class) @Composable fun Cart( onSnackClick: (Long) -> Unit, modifier: Modifier = Modifier, viewModel: CartViewModel = viewModel(factory = CartViewModel.provideFactory()) ) { - val orderLines by viewModel.orderLines.collectAsState() + val orderLines by viewModel.orderLines.collectAsStateWithLifecycle() val inspiredByCart = remember { SnackRepo.getInspiredByCart() } Cart( orderLines = orderLines, diff --git a/README.md b/README.md index fb58689e7..26a628fc9 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,12 @@ Looking for a sample that has the following features? * [Jetnews - Window Size Classes](https://github.com/android/compose-samples/blob/69e9d862b5ffb321064364d7883e859db6daeccd/JetNews/app/src/main/java/com/example/jetnews/ui/MainActivity.kt#L36) * [Crane - Window Size Classes](https://github.com/android/compose-samples/blob/e7e8733f9b37d80cdc6e9e05dbabe24ccf20b38f/Crane/app/src/main/java/androidx/compose/samples/crane/home/MainActivity.kt#L72) +## Formatting + +To automatically format all samples: Run `./scripts/format.sh` +To check one sample for errors: Navigate to the sample folder and run `./gradlew --init-script buildscripts/init.gradle.kts spotlessCheck` +To format one sample: Navigate to the sample folder and run `./gradlew --init-script buildscripts/init.gradle.kts spotlessApply` + ## Updates To update dependencies to their new stable versions, run: diff --git a/Reply/app/build.gradle.kts b/Reply/app/build.gradle.kts index de3c145f3..6eee8dc53 100644 --- a/Reply/app/build.gradle.kts +++ b/Reply/app/build.gradle.kts @@ -110,6 +110,7 @@ dependencies { implementation(libs.androidx.lifecycle.runtime) implementation(libs.androidx.lifecycle.viewModelCompose) + implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.activity.compose) diff --git a/Reply/app/src/main/java/com/example/reply/ui/MainActivity.kt b/Reply/app/src/main/java/com/example/reply/ui/MainActivity.kt index 40b53082c..efe42c7ef 100644 --- a/Reply/app/src/main/java/com/example/reply/ui/MainActivity.kt +++ b/Reply/app/src/main/java/com/example/reply/ui/MainActivity.kt @@ -24,11 +24,12 @@ import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSiz import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.window.layout.FoldingFeature @@ -46,7 +47,7 @@ class MainActivity : ComponentActivity() { private val viewModel: ReplyHomeViewModel by viewModels() - @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) + @OptIn(ExperimentalMaterial3WindowSizeClassApi::class, ExperimentalLifecycleComposeApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -79,8 +80,8 @@ class MainActivity : ComponentActivity() { setContent { ReplyTheme { val windowSize = calculateWindowSizeClass(this) - val devicePosture by devicePostureFlow.collectAsState() - val uiState by viewModel.uiState.collectAsState() + val devicePosture by devicePostureFlow.collectAsStateWithLifecycle() + val uiState by viewModel.uiState.collectAsStateWithLifecycle() ReplyApp( windowSize = windowSize,