diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt index 885fb6c3d5..c5368d0f13 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt @@ -22,12 +22,11 @@ import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading import com.google.samples.apps.nowinandroid.MainActivityUiState.Success import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.model.data.UserData +import com.google.samples.apps.nowinandroid.core.ui.stateInScope import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn @HiltViewModel class MainActivityViewModel @Inject constructor( @@ -35,11 +34,7 @@ class MainActivityViewModel @Inject constructor( ) : ViewModel() { val uiState: StateFlow = userDataRepository.userDataStream.map { Success(it) - }.stateIn( - scope = viewModelScope, - initialValue = Loading, - started = SharingStarted.WhileSubscribed(5_000) - ) + }.stateInScope(viewModelScope, initialValue = Loading) } sealed interface MainActivityUiState { diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt index bc5724c022..a56bbc895a 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt @@ -36,6 +36,7 @@ import androidx.navigation.navOptions import androidx.tracing.trace import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.ui.TrackDisposableJank +import com.google.samples.apps.nowinandroid.core.ui.stateInScope import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.bookmarksRoute import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.navigateToBookmarks import com.google.samples.apps.nowinandroid.feature.foryou.navigation.forYouNavigationRoute @@ -47,9 +48,7 @@ import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.BOOKM import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.FOR_YOU import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.INTERESTS import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn @Composable fun rememberNiaAppState( @@ -95,11 +94,7 @@ class NiaAppState( val isOffline = networkMonitor.isOnline .map(Boolean::not) - .stateIn( - scope = coroutineScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = false - ) + .stateInScope(coroutineScope, initialValue = false) /** * Map of top level destinations to be used in the TopBar, BottomBar and NavRail. The key is the diff --git a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/Extenstions.kt b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/Extenstions.kt new file mode 100644 index 0000000000..dc848c1a54 --- /dev/null +++ b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/Extenstions.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.core.ui + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +/** + * This is a helper extension function for making it easier to use [Flow.stateIn] within a ViewModel or etc. + * @Returns a [StateFlow] that shares the latest value emitted by the original [Flow] and starts + * with the [initialValue]. + */ +fun Flow.stateInScope( + coroutineScope: CoroutineScope, + started: SharingStarted = SharingStarted.WhileSubscribed(5_000), + initialValue: T +): StateFlow = stateIn(coroutineScope, started, initialValue) diff --git a/feature/author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModel.kt b/feature/author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModel.kt index 2f63dbc11e..f73cf818ce 100644 --- a/feature/author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModel.kt +++ b/feature/author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModel.kt @@ -27,16 +27,18 @@ import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource import com.google.samples.apps.nowinandroid.core.model.data.Author import com.google.samples.apps.nowinandroid.core.result.Result +import com.google.samples.apps.nowinandroid.core.result.Result.Error +import com.google.samples.apps.nowinandroid.core.result.Result.Loading +import com.google.samples.apps.nowinandroid.core.result.Result.Success import com.google.samples.apps.nowinandroid.core.result.asResult +import com.google.samples.apps.nowinandroid.core.ui.stateInScope import com.google.samples.apps.nowinandroid.feature.author.navigation.AuthorArgs import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @HiltViewModel @@ -54,20 +56,11 @@ class AuthorViewModel @Inject constructor( authorId = authorArgs.authorId, userDataRepository = userDataRepository, authorsRepository = authorsRepository - ) - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = AuthorUiState.Loading - ) + ).stateInScope(viewModelScope, initialValue = AuthorUiState.Loading) - val newsUiState: StateFlow = - getSaveableNewsResourcesStream.newsUiStateStream(authorId = authorArgs.authorId) - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = NewsUiState.Loading - ) + val newsUiState: StateFlow = getSaveableNewsResourcesStream + .newsUiStateStream(authorId = authorArgs.authorId) + .stateInScope(viewModelScope, initialValue = NewsUiState.Loading) fun followAuthorToggle(followed: Boolean) { viewModelScope.launch { @@ -80,49 +73,54 @@ class AuthorViewModel @Inject constructor( userDataRepository.updateNewsResourceBookmark(newsResourceId, bookmarked) } } -} -private fun authorUiStateStream( - authorId: String, - userDataRepository: UserDataRepository, - authorsRepository: AuthorsRepository, -): Flow { - // Observe the followed authors, as they could change over time. - val followedAuthorIdsStream: Flow> = - userDataRepository.userDataStream - .map { it.followedAuthors } - - // Observe author information - val authorStream: Flow = authorsRepository.getAuthorStream( - id = authorId - ) - - return combine( - followedAuthorIdsStream, - authorStream, - ::Pair - ) - .asResult() - .map { followedAuthorToAuthorResult -> - when (followedAuthorToAuthorResult) { - is Result.Success -> { - val (followedAuthors, author) = followedAuthorToAuthorResult.data - val followed = followedAuthors.contains(authorId) - AuthorUiState.Success( - followableAuthor = FollowableAuthor( - author = author, - isFollowed = followed - ) - ) - } - is Result.Loading -> { - AuthorUiState.Loading - } - is Result.Error -> { - AuthorUiState.Error - } + private fun authorUiStateStream( + authorId: String, + userDataRepository: UserDataRepository, + authorsRepository: AuthorsRepository, + ): Flow { + // Observe the followed authors, as they could change over time. + val followedAuthorIdsStream: Flow> = + userDataRepository.userDataStream.map { it.followedAuthors } + + // Observe author information + val authorStream: Flow = authorsRepository.getAuthorStream( + id = authorId + ) + + return combine( + followedAuthorIdsStream, + authorStream, + ::Pair + ) + .asResult() + .map { followedAuthorToAuthorResult -> + handleToAuthorResult(followedAuthorToAuthorResult, authorId) } - } + } + + private fun handleToAuthorResult( + followedAuthorToAuthorResult: Result, Author>>, + authorId: String + ) = when (followedAuthorToAuthorResult) { + is Success -> onSuccessResult(followedAuthorToAuthorResult, authorId) + is Loading -> AuthorUiState.Loading + is Error -> AuthorUiState.Error + } + + private fun onSuccessResult( + followedAuthorToAuthorResult: Success, Author>>, + authorId: String + ): AuthorUiState.Success { + val (followedAuthors, author) = followedAuthorToAuthorResult.data + val followed = followedAuthors.contains(authorId) + return AuthorUiState.Success( + followableAuthor = FollowableAuthor( + author = author, + isFollowed = followed + ) + ) + } } private fun GetSaveableNewsResourcesStreamUseCase.newsUiStateStream( @@ -134,9 +132,9 @@ private fun GetSaveableNewsResourcesStreamUseCase.newsUiStateStream( ).asResult() .map { newsResult -> when (newsResult) { - is Result.Success -> NewsUiState.Success(newsResult.data) - is Result.Loading -> NewsUiState.Loading - is Result.Error -> NewsUiState.Error + is Success -> NewsUiState.Success(newsResult.data) + is Loading -> NewsUiState.Loading + is Error -> NewsUiState.Error } } } diff --git a/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksViewModel.kt b/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksViewModel.kt index 1b9efc6aa9..da86508482 100644 --- a/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksViewModel.kt +++ b/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksViewModel.kt @@ -23,14 +23,13 @@ import com.google.samples.apps.nowinandroid.core.domain.GetSaveableNewsResources import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Loading +import com.google.samples.apps.nowinandroid.core.ui.stateInScope import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @HiltViewModel @@ -44,11 +43,7 @@ class BookmarksViewModel @Inject constructor( .map { newsResources -> newsResources.filter(SaveableNewsResource::isSaved) } // Only show bookmarked news resources. .map, NewsFeedUiState>(NewsFeedUiState::Success) .onStart { emit(Loading) } - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = Loading - ) + .stateInScope(viewModelScope, initialValue = Loading) fun removeFromSavedResources(newsResourceId: String) { viewModelScope.launch { diff --git a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModel.kt b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModel.kt index 599bfd8688..1f92b1e986 100644 --- a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModel.kt +++ b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModel.kt @@ -25,17 +25,16 @@ import com.google.samples.apps.nowinandroid.core.domain.GetSaveableNewsResources import com.google.samples.apps.nowinandroid.core.domain.GetSortedFollowableAuthorsStreamUseCase import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState +import com.google.samples.apps.nowinandroid.core.ui.stateInScope import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @HiltViewModel @@ -50,12 +49,9 @@ class ForYouViewModel @Inject constructor( private val shouldShowOnboarding: Flow = userDataRepository.userDataStream.map { !it.shouldHideOnboarding } - val isSyncing = syncStatusMonitor.isSyncing - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = false - ) + val isSyncing = syncStatusMonitor + .isSyncing + .stateInScope(viewModelScope, initialValue = false) val feedState: StateFlow = userDataRepository.userDataStream @@ -79,11 +75,7 @@ class ForYouViewModel @Inject constructor( // As the selected topics and topic state changes, this will cancel the old feed // monitoring and start the new one. .flatMapLatest { it } - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = NewsFeedUiState.Loading - ) + .stateInScope(viewModelScope, initialValue = NewsFeedUiState.Loading) val onboardingUiState: StateFlow = combine( @@ -99,12 +91,7 @@ class ForYouViewModel @Inject constructor( } else { OnboardingUiState.NotShown } - } - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = OnboardingUiState.Loading - ) + }.stateInScope(viewModelScope, initialValue = OnboardingUiState.Loading) fun updateTopicSelection(topicId: String, isChecked: Boolean) { viewModelScope.launch { diff --git a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt index 01d8b0d6e0..b4a7050fe7 100644 --- a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt +++ b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt @@ -24,14 +24,13 @@ import com.google.samples.apps.nowinandroid.core.domain.GetSortedFollowableAutho import com.google.samples.apps.nowinandroid.core.domain.TopicSortField import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic +import com.google.samples.apps.nowinandroid.core.ui.stateInScope import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -54,11 +53,7 @@ class InterestsViewModel @Inject constructor( getSortedFollowableAuthorsStream(), getFollowableTopicsStream(sortBy = TopicSortField.NAME), InterestsUiState::Interests - ).stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = InterestsUiState.Loading - ) + ).stateInScope(viewModelScope, initialValue = InterestsUiState.Loading) fun followTopic(followedTopicId: String, followed: Boolean) { viewModelScope.launch { diff --git a/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsViewModel.kt b/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsViewModel.kt index fa159b5550..4d24665d95 100644 --- a/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsViewModel.kt +++ b/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsViewModel.kt @@ -21,6 +21,8 @@ import androidx.lifecycle.viewModelScope import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand +import com.google.samples.apps.nowinandroid.core.model.data.UserData +import com.google.samples.apps.nowinandroid.core.ui.stateInScope import com.google.samples.apps.nowinandroid.feature.settings.SettingsUiState.Loading import com.google.samples.apps.nowinandroid.feature.settings.SettingsUiState.Success import dagger.hilt.android.lifecycle.HiltViewModel @@ -28,7 +30,6 @@ import javax.inject.Inject import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @HiltViewModel @@ -37,26 +38,28 @@ class SettingsViewModel @Inject constructor( ) : ViewModel() { val settingsUiState: StateFlow = userDataRepository.userDataStream - .map { userData -> - Success( - settings = UserEditableSettings( - brand = userData.themeBrand, - darkThemeConfig = userData.darkThemeConfig - ) - ) - } - .stateIn( - scope = viewModelScope, - // Starting eagerly means the user data is ready when the SettingsDialog is laid out - // for the first time. Without this, due to b/221643630 the layout is done using the - // "Loading" text, then replaced with the user editable fields once loaded, however, - // the layout height doesn't change meaning all the fields are squashed into a small - // scrollable column. - // TODO: Change to SharingStarted.WhileSubscribed(5_000) when b/221643630 is fixed - started = SharingStarted.Eagerly, - initialValue = Loading + .map { userData -> mapToSuccess(userData) } + .stateInScope( + viewModelScope, + /** + * Starting eagerly means the user data is ready when the SettingsDialog is laid out + * for the first time. Without this, due to b/221643630 the layout is done using the + * "Loading" text, then replaced with the user editable fields once loaded, however, + * the layout height doesn't change meaning all the fields are squashed into a small + * scrollable column. + * TODO: Change to SharingStarted.WhileSubscribed(5_000) when b/221643630 is fixed + */ + SharingStarted.Eagerly, Loading ) + private fun mapToSuccess(userData: UserData) = + Success( + settings = UserEditableSettings( + brand = userData.themeBrand, + darkThemeConfig = userData.darkThemeConfig + ) + ) + fun updateThemeBrand(themeBrand: ThemeBrand) { viewModelScope.launch { userDataRepository.setThemeBrand(themeBrand) diff --git a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt index 61bd13aa09..68acb05110 100644 --- a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt +++ b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt @@ -119,7 +119,7 @@ internal fun TopicScreen( uiState = topicUiState.followableTopic, ) } - TopicBody( + topicBody( name = topicUiState.followableTopic.topic.name, description = topicUiState.followableTopic.topic.longDescription, news = newsUiState, @@ -134,7 +134,7 @@ internal fun TopicScreen( } } -private fun LazyListScope.TopicBody( +private fun LazyListScope.topicBody( name: String, description: String, news: NewsUiState, @@ -204,7 +204,7 @@ private fun LazyListScope.TopicCards( private fun TopicBodyPreview() { NiaTheme { LazyColumn { - TopicBody( + topicBody( "Jetpack Compose", "Lorem ipsum maximum", NewsUiState.Success(emptyList()), "", { _, _ -> } ) diff --git a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt index 160bcfb3ba..e2b0b4f197 100644 --- a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt +++ b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt @@ -27,16 +27,18 @@ import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.result.Result +import com.google.samples.apps.nowinandroid.core.result.Result.Error +import com.google.samples.apps.nowinandroid.core.result.Result.Loading +import com.google.samples.apps.nowinandroid.core.result.Result.Success import com.google.samples.apps.nowinandroid.core.result.asResult +import com.google.samples.apps.nowinandroid.core.ui.stateInScope import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicArgs import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @HiltViewModel @@ -45,7 +47,6 @@ class TopicViewModel @Inject constructor( stringDecoder: StringDecoder, private val userDataRepository: UserDataRepository, topicsRepository: TopicsRepository, - // newsRepository: NewsRepository, getSaveableNewsResourcesStream: GetSaveableNewsResourcesStreamUseCase ) : ViewModel() { @@ -55,23 +56,13 @@ class TopicViewModel @Inject constructor( topicId = topicArgs.topicId, userDataRepository = userDataRepository, topicsRepository = topicsRepository - ) - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = TopicUiState.Loading - ) + ).stateInScope(viewModelScope, initialValue = TopicUiState.Loading) val newUiState: StateFlow = newsUiStateStream( topicId = topicArgs.topicId, userDataRepository = userDataRepository, getSaveableNewsResourcesStream = getSaveableNewsResourcesStream - ) - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = NewsUiState.Loading - ) + ).stateInScope(viewModelScope, initialValue = NewsUiState.Loading) fun followTopicToggle(followed: Boolean) { viewModelScope.launch { @@ -92,9 +83,9 @@ private fun topicUiStateStream( topicsRepository: TopicsRepository, ): Flow { // Observe the followed topics, as they could change over time. - val followedTopicIdsStream: Flow> = - userDataRepository.userDataStream - .map { it.followedTopics } + val followedTopicIdsStream: Flow> = userDataRepository + .userDataStream + .map { it.followedTopics } // Observe topic information val topicStream: Flow = topicsRepository.getTopic( @@ -108,27 +99,28 @@ private fun topicUiStateStream( ) .asResult() .map { followedTopicToTopicResult -> - when (followedTopicToTopicResult) { - is Result.Success -> { - val (followedTopics, topic) = followedTopicToTopicResult.data - val followed = followedTopics.contains(topicId) - TopicUiState.Success( - followableTopic = FollowableTopic( - topic = topic, - isFollowed = followed - ) - ) - } - is Result.Loading -> { - TopicUiState.Loading - } - is Result.Error -> { - TopicUiState.Error - } - } + handleTopicResult(followedTopicToTopicResult, topicId) } } +private fun handleTopicResult( + followedTopicToTopicResult: Result, Topic>>, + topicId: String +) = when (followedTopicToTopicResult) { + is Success -> { + val (followedTopics, topic) = followedTopicToTopicResult.data + val followed = followedTopics.contains(topicId) + TopicUiState.Success( + followableTopic = FollowableTopic( + topic = topic, + isFollowed = followed + ) + ) + } + is Loading -> TopicUiState.Loading + is Error -> TopicUiState.Error +} + private fun newsUiStateStream( topicId: String, getSaveableNewsResourcesStream: GetSaveableNewsResourcesStreamUseCase, @@ -152,16 +144,12 @@ private fun newsUiStateStream( .asResult() .map { newsToBookmarksResult -> when (newsToBookmarksResult) { - is Result.Success -> { + is Success -> { val (news, bookmarks) = newsToBookmarksResult.data NewsUiState.Success(news) } - is Result.Loading -> { - NewsUiState.Loading - } - is Result.Error -> { - NewsUiState.Error - } + is Loading -> NewsUiState.Loading + is Error -> NewsUiState.Error } } }