diff --git a/JetNews/app/build.gradle b/JetNews/app/build.gradle index 6453c74331..f5cbb880de 100644 --- a/JetNews/app/build.gradle +++ b/JetNews/app/build.gradle @@ -85,10 +85,10 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.3.0-alpha02' implementation 'androidx.activity:activity-ktx:1.1.0' - implementation 'androidx.core:core-ktx:1.5.0-alpha02' + implementation 'androidx.core:core-ktx:1.5.0-alpha04' - implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0-alpha07" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-alpha07" + implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0-beta01" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-beta01" androidTestImplementation 'junit:junit:4.13' androidTestImplementation 'androidx.test:rules:1.3.0' diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/JetnewsApp.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/JetnewsApp.kt index 3f60b368ac..c718f42b94 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/JetnewsApp.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/JetnewsApp.kt @@ -192,8 +192,7 @@ private fun DrawerButton( Text( text = label, style = MaterialTheme.typography.body2, - color = textIconColor, - modifier = Modifier.fillMaxWidth() + color = textIconColor ) } } diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/SwipeToRefresh.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/SwipeToRefresh.kt index 5bdf080067..7de3615cde 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/SwipeToRefresh.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/SwipeToRefresh.kt @@ -16,8 +16,7 @@ package com.example.jetnews.ui -import androidx.compose.foundation.Box -import androidx.compose.foundation.layout.Stack +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.offsetPx import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.FractionalThreshold @@ -48,7 +47,7 @@ fun SwipeToRefreshLayout( true } - Stack( + Box( modifier = Modifier.swipeable( state = state, anchors = mapOf( diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt index d3930e57a0..f04bdf49c2 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt @@ -18,9 +18,9 @@ package com.example.jetnews.ui.article import android.content.Context import android.content.Intent +import androidx.compose.foundation.AmbientContentColor import androidx.compose.foundation.Icon import androidx.compose.foundation.Text -import androidx.compose.foundation.contentColor import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -57,12 +57,12 @@ import com.example.jetnews.data.posts.impl.post3 import com.example.jetnews.model.Post import com.example.jetnews.ui.ThemedPreview import com.example.jetnews.ui.home.BookmarkButton -import com.example.jetnews.utils.launchUiStateProducer +import com.example.jetnews.utils.produceUiState import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking /** - * Stateful Article Screen that manages state using [launchUiStateProducer] + * Stateful Article Screen that manages state using [produceUiState] * * @param postId (state) the post to show * @param postsRepository data source for this screen @@ -75,7 +75,7 @@ fun ArticleScreen( postsRepository: PostsRepository, onBack: () -> Unit ) { - val (post) = launchUiStateProducer(postsRepository, postId) { + val (post) = produceUiState(postsRepository, postId) { getPost(postId) } // TODO: handle errors when the repository is capable of creating them @@ -129,7 +129,7 @@ fun ArticleScreen( Text( text = "Published in: ${post.publication?.name}", style = MaterialTheme.typography.subtitle2, - color = contentColor() + color = AmbientContentColor.current ) }, navigationIcon = { diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/article/PostContent.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/article/PostContent.kt index 980b69f8ca..102839619f 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/article/PostContent.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/article/PostContent.kt @@ -16,11 +16,12 @@ package com.example.jetnews.ui.article -import androidx.compose.foundation.Box +import androidx.compose.foundation.AmbientContentColor import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text -import androidx.compose.foundation.contentColor +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -32,8 +33,8 @@ import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.preferredWidth import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.FirstBaseline +import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.Colors -import androidx.compose.material.EmphasisAmbient import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface @@ -81,7 +82,7 @@ fun PostContent(post: Post, modifier: Modifier = Modifier) { Text(text = post.title, style = MaterialTheme.typography.h4) Spacer(Modifier.preferredHeight(8.dp)) post.subtitle?.let { subtitle -> - ProvideEmphasis(EmphasisAmbient.current.medium) { + ProvideEmphasis(AmbientEmphasisLevels.current.medium) { Text( text = subtitle, style = MaterialTheme.typography.body2, @@ -116,19 +117,19 @@ private fun PostMetadata(metadata: Metadata) { Image( asset = Icons.Filled.AccountCircle, modifier = Modifier.preferredSize(40.dp), - colorFilter = ColorFilter.tint(contentColor()), + colorFilter = ColorFilter.tint(AmbientContentColor.current), contentScale = ContentScale.Fit ) Spacer(Modifier.preferredWidth(8.dp)) Column { - ProvideEmphasis(EmphasisAmbient.current.high) { + ProvideEmphasis(AmbientEmphasisLevels.current.high) { Text( text = metadata.author.name, style = typography.caption, modifier = Modifier.padding(top = 4.dp) ) } - ProvideEmphasis(EmphasisAmbient.current.medium) { + ProvideEmphasis(AmbientEmphasisLevels.current.medium) { Text( text = "${metadata.date} • ${metadata.readTimeMinutes} min read", style = typography.caption @@ -216,10 +217,9 @@ private fun BulletParagraph( .alignWithSiblings { // Add an alignment "baseline" 1sp below the bottom of the circle 9.sp.toIntPx() - }, - backgroundColor = contentColor(), - shape = CircleShape - ) + } + .background(AmbientContentColor.current, CircleShape), + ) { /* no content */ } } Text( modifier = Modifier diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeScreen.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeScreen.kt index 5fde32a35f..445a4d02a9 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeScreen.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeScreen.kt @@ -16,22 +16,22 @@ package com.example.jetnews.ui.home -import androidx.compose.foundation.Box import androidx.compose.foundation.Icon import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.ScrollableRow import androidx.compose.foundation.Text import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Divider import androidx.compose.material.DrawerValue -import androidx.compose.material.EmphasisAmbient import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme @@ -45,9 +45,9 @@ import androidx.compose.material.TopAppBar import androidx.compose.material.rememberDrawerState import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedTask import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.launchInComposition import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -67,12 +67,12 @@ import com.example.jetnews.ui.Screen import com.example.jetnews.ui.SwipeToRefreshLayout import com.example.jetnews.ui.ThemedPreview import com.example.jetnews.ui.state.UiState -import com.example.jetnews.utils.launchUiStateProducer +import com.example.jetnews.utils.produceUiState import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking /** - * Stateful HomeScreen which manages state using [launchUiStateProducer] + * Stateful HomeScreen which manages state using [produceUiState] * * @param navigateTo (event) request navigation to [Screen] * @param postsRepository data source for this screen @@ -84,7 +84,7 @@ fun HomeScreen( postsRepository: PostsRepository, scaffoldState: ScaffoldState = rememberScaffoldState() ) { - val (postUiState, refreshPost, clearError) = launchUiStateProducer(postsRepository) { + val (postUiState, refreshPost, clearError) = produceUiState(postsRepository) { getPosts() } @@ -139,7 +139,7 @@ fun HomeScreen( // Show snackbar using a coroutine, when the coroutine is cancelled the snackbar will // automatically dismiss. This coroutine will cancel whenever posts.hasError changes, and // only start when posts.hasError is true (due to the above if-check). - launchInComposition(posts.hasError) { + LaunchedTask(posts.hasError) { val snackbarResult = scaffoldState.snackbarHostState.showSnackbar( message = errorMessage, actionLabel = retryMessage @@ -258,6 +258,9 @@ private fun HomeScreenErrorAndContent( TextButton(onClick = onRefresh, modifier.fillMaxSize()) { Text("Tap to load content", textAlign = TextAlign.Center) } + } else { + // there's currently an error showing, don't show any content + Box(modifier.fillMaxSize()) { /* empty screen */ } } } @@ -310,7 +313,7 @@ private fun FullScreenLoading() { */ @Composable private fun PostListTopSection(post: Post, navigateTo: (Screen) -> Unit) { - ProvideEmphasis(EmphasisAmbient.current.high) { + ProvideEmphasis(AmbientEmphasisLevels.current.high) { Text( modifier = Modifier.padding(start = 16.dp, top = 16.dp, end = 16.dp), text = "Top stories for you", @@ -362,7 +365,7 @@ private fun PostListPopularSection( navigateTo: (Screen) -> Unit ) { Column { - ProvideEmphasis(EmphasisAmbient.current.high) { + ProvideEmphasis(AmbientEmphasisLevels.current.high) { Text( modifier = Modifier.padding(16.dp), text = "Popular on Jetnews", diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardTop.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardTop.kt index 18ec983327..0f44728468 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardTop.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardTop.kt @@ -24,7 +24,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight -import androidx.compose.material.EmphasisAmbient +import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis import androidx.compose.runtime.Composable @@ -54,7 +54,7 @@ fun PostCardTop(post: Post, modifier: Modifier = Modifier) { } Spacer(Modifier.preferredHeight(16.dp)) - val emphasisLevels = EmphasisAmbient.current + val emphasisLevels = AmbientEmphasisLevels.current ProvideEmphasis(emphasisLevels.high) { Text( text = post.title, diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardYourNetwork.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardYourNetwork.kt index 520a861642..c04c08cef1 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardYourNetwork.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardYourNetwork.kt @@ -24,8 +24,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredSize +import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.Card -import androidx.compose.material.EmphasisAmbient import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis import androidx.compose.runtime.Composable @@ -62,7 +62,7 @@ fun PostCardPopular( .fillMaxWidth() ) Column(modifier = Modifier.padding(16.dp)) { - val emphasisLevels = EmphasisAmbient.current + val emphasisLevels = AmbientEmphasisLevels.current ProvideEmphasis(emphasisLevels.high) { Text( text = post.title, diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCards.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCards.kt index 6aa3b31b7d..c161c9fb43 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCards.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCards.kt @@ -25,7 +25,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize -import androidx.compose.material.EmphasisAmbient +import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.IconToggleButton import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis @@ -52,7 +52,7 @@ fun AuthorAndReadTime( modifier: Modifier = Modifier ) { Row(modifier) { - ProvideEmphasis(EmphasisAmbient.current.medium) { + ProvideEmphasis(AmbientEmphasisLevels.current.medium) { val textStyle = MaterialTheme.typography.body2 Text( text = post.metadata.author.name, @@ -79,7 +79,7 @@ fun PostImage(post: Post, modifier: Modifier = Modifier) { @Composable fun PostTitle(post: Post) { - ProvideEmphasis(EmphasisAmbient.current.high) { + ProvideEmphasis(AmbientEmphasisLevels.current.high) { Text(post.title, style = MaterialTheme.typography.subtitle1) } } @@ -118,7 +118,7 @@ fun PostCardHistory(post: Post, navigateTo: (Screen) -> Unit) { modifier = Modifier.padding(end = 16.dp) ) Column(Modifier.weight(1f)) { - ProvideEmphasis(EmphasisAmbient.current.medium) { + ProvideEmphasis(AmbientEmphasisLevels.current.medium) { Text( text = "BASED ON YOUR HISTORY", style = MaterialTheme.typography.overline @@ -130,7 +130,7 @@ fun PostCardHistory(post: Post, navigateTo: (Screen) -> Unit) { modifier = Modifier.padding(top = 4.dp) ) } - ProvideEmphasis(EmphasisAmbient.current.medium) { + ProvideEmphasis(AmbientEmphasisLevels.current.medium) { Icon(asset = Icons.Filled.MoreVert) } } 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 5c45bbddc1..42f321532c 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 @@ -16,13 +16,14 @@ package com.example.jetnews.ui.interests -import androidx.compose.foundation.Box import androidx.compose.foundation.Icon import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.selection.toggleable @@ -59,7 +60,7 @@ import com.example.jetnews.data.interests.impl.FakeInterestsRepository import com.example.jetnews.ui.AppDrawer import com.example.jetnews.ui.Screen import com.example.jetnews.ui.ThemedPreview -import com.example.jetnews.utils.launchUiStateProducer +import com.example.jetnews.utils.produceUiState import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -82,7 +83,7 @@ enum class Sections(val title: String) { class TabContent(val section: Sections, val content: @Composable () -> Unit) /** - * Stateful InterestsScreen manages state using [launchUiStateProducer] + * Stateful InterestsScreen manages state using [produceUiState] * * @param navigateTo (event) request navigation to [Screen] * @param scaffoldState (state) state for screen Scaffold @@ -101,7 +102,7 @@ fun InterestsScreen( // 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 (topics) = launchUiStateProducer(interestsRepository) { + val (topics) = produceUiState(interestsRepository) { getTopics() } // collectAsState will read a [Flow] in Compose @@ -114,7 +115,7 @@ fun InterestsScreen( } val peopleSection = TabContent(Sections.People) { - val (people) = launchUiStateProducer(interestsRepository) { + val (people) = produceUiState(interestsRepository) { getPeople() } val selectedPeople by interestsRepository.observePeopleSelected().collectAsState(setOf()) @@ -126,7 +127,7 @@ fun InterestsScreen( } val publicationSection = TabContent(Sections.Publications) { - val (publications) = launchUiStateProducer(interestsRepository) { + val (publications) = produceUiState(interestsRepository) { getPublications() } val selectedPublications by interestsRepository.observePublicationSelected().collectAsState(setOf()) @@ -356,11 +357,11 @@ private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit Text( text = itemTitle, modifier = Modifier - .weight(1f) .align(Alignment.CenterVertically) .padding(16.dp), style = MaterialTheme.typography.subtitle1 ) + Spacer(Modifier.weight(1f)) SelectTopicButton( modifier = Modifier.align(Alignment.CenterVertically), selected = selected diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/interests/SelectTopicButton.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/interests/SelectTopicButton.kt index 684795ac1d..fcad3396fe 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/interests/SelectTopicButton.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/interests/SelectTopicButton.kt @@ -20,7 +20,7 @@ import androidx.compose.foundation.Icon import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.EmphasisAmbient +import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface @@ -49,7 +49,7 @@ fun SelectTopicButton( shape = CircleShape, modifier = modifier.preferredSize(36.dp, 36.dp) ) { - ProvideEmphasis(EmphasisAmbient.current.high) { + ProvideEmphasis(AmbientEmphasisLevels.current.high) { Icon(icon) } } diff --git a/JetNews/app/src/main/java/com/example/jetnews/utils/LaunchUiStateProducer.kt b/JetNews/app/src/main/java/com/example/jetnews/utils/ProduceUiState.kt similarity index 68% rename from JetNews/app/src/main/java/com/example/jetnews/utils/LaunchUiStateProducer.kt rename to JetNews/app/src/main/java/com/example/jetnews/utils/ProduceUiState.kt index f85dcd21ac..94d6ac1578 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/utils/LaunchUiStateProducer.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/utils/ProduceUiState.kt @@ -18,23 +18,23 @@ package com.example.jetnews.utils import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import androidx.compose.runtime.launchInComposition -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import com.example.jetnews.data.Result import com.example.jetnews.ui.state.UiState import com.example.jetnews.ui.state.copyWithResult import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch /** - * Result object for [launchUiStateProducer]. + * Result object for [produceUiState]. * * It is intended that you destructure this class at the call site. Here is an example usage that * calls dataSource.loadData() and then displays a UI based on the result. * * ``` - * val (result, onRefresh, onClearError) = launchUiStateProducer(dataSource) { loadData() } + * val (result, onRefresh, onClearError) = produceUiState(dataSource) { loadData() } * Text(result.value) * Button(onClick = onRefresh) { Text("Refresh" } * Button(onClick = onClearError) { Text("Clear loading error") } @@ -62,7 +62,7 @@ data class ProducerResult( * calls dataSource.loadData() and then displays a UI based on the result. * * ``` - * val (result, onRefresh, onClearError) = launchUiStateProducer(dataSource) { loadData() } + * val (result, onRefresh, onClearError) = produceUiState(dataSource) { loadData() } * Text(result.value) * Button(onClick = onRefresh) { Text("Refresh" } * Button(onClick = onClearError) { Text("Clear loading error") } @@ -75,10 +75,10 @@ data class ProducerResult( * @return data state, onRefresh event, and onClearError event */ @Composable -fun launchUiStateProducer( +fun produceUiState( producer: Producer, block: suspend Producer.() -> Result -): ProducerResult> = launchUiStateProducer(producer, Unit, block) +): ProducerResult> = produceUiState(producer, Unit, block) /** * Launch a coroutine to create refreshable [UiState] from a suspending producer. @@ -91,7 +91,7 @@ fun launchUiStateProducer( * calls dataSource.loadData(resourceId) and then displays a UI based on the result. * * ``` - * val (result, onRefresh, onClearError) = launchUiStateProducer(dataSource, resourceId) { + * val (result, onRefresh, onClearError) = produceUiState(dataSource, resourceId) { * loadData(resourceId) * } * Text(result.value) @@ -108,42 +108,44 @@ fun launchUiStateProducer( */ @OptIn(ExperimentalCoroutinesApi::class) @Composable -fun launchUiStateProducer( +fun produceUiState( producer: Producer, key: Any?, block: suspend Producer.() -> Result ): ProducerResult> { - val producerState = remember { mutableStateOf(UiState(loading = true)) } - // posting to this channel will trigger a single refresh val refreshChannel = remember { Channel(Channel.CONFLATED) } + // posting to this channel will clear the current error condition (if any) + val errorClearChannel = remember { Channel(Channel.CONFLATED) } - // event for caller to trigger a refresh - val refresh: () -> Unit = { refreshChannel.offer(Unit) } - - // event for caller to clear any errors on the current result (useful for transient error - // displays) - val clearError: () -> Unit = { - producerState.value = producerState.value.copy(exception = null) - } - - // whenever Producer or v1 changes, launch a new coroutine to call block() and refresh whenever - // the onRefresh callback is called - launchInComposition(producer, key) { - // whenever the coroutine restarts, clear the previous result immediately as they are no - // longer valid - producerState.value = UiState(loading = true) - // force a refresh on coroutine restart + val result = produceState(UiState(loading = true), producer, key) { + // whenever the coroutine restarts from producer or key changes, clear the previous result + // immediately and force refresh + value = UiState(loading = true) refreshChannel.send(Unit) - // whenever a refresh is triggered, call block again. This for-loop will suspend when - // refreshChannel is empty, and resume when the next value is offered or sent to the - // channel. - // This for-loop will loop until the [launchInComposition] coroutine is cancelled. + // launch a new coroutine to handle errorClear events async + launch { + // This for-loop will loop until the [produceState] coroutine is cancelled. + for (clearEvent in errorClearChannel) { + // This for-loop will suspend when errorClearChanel is empty, and resume when the + // next value is offered or sent to the chanel. + value = value.copy(exception = null) + } + } + + // This for-loop will loop until the [produceState] coroutine is cancelled. for (refreshEvent in refreshChannel) { - producerState.value = producerState.value.copy(loading = true) - producerState.value = producerState.value.copyWithResult(producer.block()) + // whenever a refresh is triggered, call block again. This for-loop will suspend when + // refreshChannel is empty, and resume when the next value is offered or sent to the + // channel. + value = value.copy(loading = true) + value = value.copyWithResult(producer.block()) } } - return ProducerResult(producerState, refresh, clearError) + return ProducerResult( + result = result, + onRefresh = { refreshChannel.offer(Unit) }, + onClearError = { errorClearChannel.offer(Unit) } + ) } diff --git a/JetNews/build.gradle b/JetNews/build.gradle index 881a790fa5..b96930bbdb 100644 --- a/JetNews/build.gradle +++ b/JetNews/build.gradle @@ -16,7 +16,7 @@ buildscript { ext.kotlin_version = '1.4.10' - ext.compose_version = '1.0.0-alpha03' + ext.compose_version = '1.0.0-SNAPSHOT' repositories { google() @@ -34,7 +34,11 @@ plugins { } subprojects { + def snapshot = "6891475" repositories { + maven { + url "https://androidx.dev/snapshots/builds/${snapshot}/artifacts/ui/repository/" + } google() jcenter() } @@ -50,20 +54,4 @@ subprojects { licenseHeaderFile rootProject.file('spotless/copyright.kt') } } -} - -// JetNews uses Compose alpha03 and Android Gradle Plugin 4.2.0-alpha13 which are incompatible -// TODO: Remove this workaround when updating to >alpha04 -subprojects { - configurations.configureEach { - resolutionStrategy.eachDependency { DependencyResolveDetails details -> - def group = details.requested.group - def module = details.requested.module.name - def version = details.requested.version - - if (group == 'androidx.compose.compiler' && module == 'compiler') { - details.useTarget("androidx.compose:compose-compiler:$version") - } - } - } } \ No newline at end of file