diff --git a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt index db274bbbd..72361eeed 100644 --- a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt +++ b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetUserNewsResourcesUseCase.kt @@ -24,7 +24,11 @@ import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.UserData import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import javax.inject.Inject /** @@ -51,6 +55,43 @@ class GetUserNewsResourcesUseCase @Inject constructor( }.mapToUserNewsResources(userDataRepository.userData) } +class GetFollowedUserNewsResourcesUseCase @Inject constructor( + private val userDataRepository: UserDataRepository, + val getUserNewsResources: GetUserNewsResourcesUseCase, +) { + /** + * Returns a list of UserNewsResources which the user is following + */ + operator fun invoke(): Flow> = + userDataRepository.userData.map { userData -> + if (shouldShowEmptyFeed(userData)) { + null + } else { + userData.followedTopics + } + } + .distinctUntilChanged() + .flatMapLatest { followedTopics -> + if (followedTopics == null) { + flowOf(emptyList()) + } else { + getUserNewsResources(filterTopicIds = followedTopics) + } + } + + /** + * If the user hasn't completed the onboarding and hasn't selected any interests + * show an empty news list to clearly demonstrate that their selections affect the + * news articles they will see. + * + * Note: It should not be possible for the user to get into a state where the onboarding + * is not displayed AND they haven't followed any topics, however, this method is to safeguard + * against that scenario in future. + */ + private fun shouldShowEmptyFeed(userData: UserData) = + !userData.shouldHideOnboarding && userData.followedTopics.isEmpty() +} + private fun Flow>.mapToUserNewsResources( userDataStream: Flow, ): Flow> = 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 d75965778..84c4d11a3 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 @@ -21,7 +21,7 @@ import androidx.lifecycle.viewModelScope import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.data.util.SyncStatusMonitor import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase -import com.google.samples.apps.nowinandroid.core.domain.GetUserNewsResourcesUseCase +import com.google.samples.apps.nowinandroid.core.domain.GetFollowedUserNewsResourcesUseCase import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState import dagger.hilt.android.lifecycle.HiltViewModel @@ -29,8 +29,6 @@ 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.stateIn import kotlinx.coroutines.launch @@ -40,7 +38,7 @@ import javax.inject.Inject class ForYouViewModel @Inject constructor( syncStatusMonitor: SyncStatusMonitor, private val userDataRepository: UserDataRepository, - private val getSaveableNewsResources: GetUserNewsResourcesUseCase, + getFollowedUserNewsResources: GetFollowedUserNewsResourcesUseCase, getFollowableTopics: GetFollowableTopicsUseCase, ) : ViewModel() { @@ -55,26 +53,9 @@ class ForYouViewModel @Inject constructor( ) val feedState: StateFlow = - userDataRepository.userData - .map { userData -> - // If the user hasn't completed the onboarding and hasn't selected any interests - // show an empty news list to clearly demonstrate that their selections affect the - // news articles they will see. - if (!userData.shouldHideOnboarding && - userData.followedTopics.isEmpty() - ) { - flowOf(NewsFeedUiState.Success(emptyList())) - } else { - getSaveableNewsResources( - filterTopicIds = userData.followedTopics, - ) - .map, NewsFeedUiState>(NewsFeedUiState::Success) - } - } - // Flatten the feed flows. - // As the selected topics and topic state changes, this will cancel the old feed - // monitoring and start the new one. - .flatMapLatest { it } + getFollowedUserNewsResources().map { + NewsFeedUiState.Success(it) + } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), diff --git a/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt b/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt index 1f6e010d5..c335f7603 100644 --- a/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt +++ b/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt @@ -17,6 +17,7 @@ package com.google.samples.apps.nowinandroid.feature.foryou import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase +import com.google.samples.apps.nowinandroid.core.domain.GetFollowedUserNewsResourcesUseCase import com.google.samples.apps.nowinandroid.core.domain.GetUserNewsResourcesUseCase import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource @@ -60,6 +61,11 @@ class ForYouViewModelTest { newsRepository = newsRepository, userDataRepository = userDataRepository, ) + private val getFollowedUserNewsResourcesUseCase = GetFollowedUserNewsResourcesUseCase( + userDataRepository = userDataRepository, + getUserNewsResources = getUserNewsResourcesUseCase, + ) + private val getFollowableTopicsUseCase = GetFollowableTopicsUseCase( topicsRepository = topicsRepository, userDataRepository = userDataRepository, @@ -71,7 +77,7 @@ class ForYouViewModelTest { viewModel = ForYouViewModel( syncStatusMonitor = syncStatusMonitor, userDataRepository = userDataRepository, - getSaveableNewsResources = getUserNewsResourcesUseCase, + getFollowedUserNewsResources = getFollowedUserNewsResourcesUseCase, getFollowableTopics = getFollowableTopicsUseCase, ) }