@@ -23,12 +23,14 @@ import com.google.samples.apps.nowinandroid.core.data.util.SyncStatusMonitor
2323import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase
2424import com.google.samples.apps.nowinandroid.core.domain.GetUserNewsResourcesUseCase
2525import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource
26+ import com.google.samples.apps.nowinandroid.core.model.data.UserData
2627import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
2728import dagger.hilt.android.lifecycle.HiltViewModel
2829import kotlinx.coroutines.flow.Flow
2930import kotlinx.coroutines.flow.SharingStarted
3031import kotlinx.coroutines.flow.StateFlow
3132import kotlinx.coroutines.flow.combine
33+ import kotlinx.coroutines.flow.distinctUntilChanged
3234import kotlinx.coroutines.flow.flatMapLatest
3335import kotlinx.coroutines.flow.flowOf
3436import kotlinx.coroutines.flow.map
@@ -40,7 +42,7 @@ import javax.inject.Inject
4042class ForYouViewModel @Inject constructor(
4143 syncStatusMonitor : SyncStatusMonitor ,
4244 private val userDataRepository : UserDataRepository ,
43- private val getSaveableNewsResources : GetUserNewsResourcesUseCase ,
45+ getUserNewsResources : GetUserNewsResourcesUseCase ,
4446 getFollowableTopics : GetFollowableTopicsUseCase ,
4547) : ViewModel() {
4648
@@ -55,26 +57,8 @@ class ForYouViewModel @Inject constructor(
5557 )
5658
5759 val feedState: StateFlow <NewsFeedUiState > =
58- userDataRepository.userData
59- .map { userData ->
60- // If the user hasn't completed the onboarding and hasn't selected any interests
61- // show an empty news list to clearly demonstrate that their selections affect the
62- // news articles they will see.
63- if (! userData.shouldHideOnboarding &&
64- userData.followedTopics.isEmpty()
65- ) {
66- flowOf(NewsFeedUiState .Success (emptyList()))
67- } else {
68- getSaveableNewsResources(
69- filterTopicIds = userData.followedTopics,
70- )
71- .map<List <UserNewsResource >, NewsFeedUiState > (NewsFeedUiState ::Success )
72- }
73- }
74- // Flatten the feed flows.
75- // As the selected topics and topic state changes, this will cancel the old feed
76- // monitoring and start the new one.
77- .flatMapLatest { it }
60+ userDataRepository.getFollowedUserNewsResources(getUserNewsResources)
61+ .map(NewsFeedUiState ::Success )
7862 .stateIn(
7963 scope = viewModelScope,
8064 started = SharingStarted .WhileSubscribed (5_000 ),
@@ -116,3 +100,45 @@ class ForYouViewModel @Inject constructor(
116100 }
117101 }
118102}
103+
104+ /* *
105+ * Obtain a flow of user news resources whose topics match those the user is following.
106+ *
107+ * getUserNewsResources: The `UseCase` used to obtain the flow of user news resources.
108+ */
109+ private fun UserDataRepository.getFollowedUserNewsResources (
110+ getUserNewsResources : GetUserNewsResourcesUseCase ,
111+ ): Flow <List <UserNewsResource >> = userData
112+ // Map the user data into a set of followed topic IDs or null if we should return an empty list.
113+ .map { userData ->
114+ if (userData.shouldShowEmptyFeed()) {
115+ null
116+ } else {
117+ userData.followedTopics
118+ }
119+ }
120+ // Only emit a set of followed topic IDs if it's changed. This avoids calling potentially
121+ // expensive operations (like setting up a new flow) when nothing has changed.
122+ .distinctUntilChanged()
123+ // getUserNewsResources returns a flow, so we have a flow inside a flow. flatMapLatest moves
124+ // the inner flow (the one we want to return) to the outer flow and cancels any previous flows
125+ // created by getUserNewsResources.
126+ .flatMapLatest { followedTopics ->
127+ if (followedTopics == null ) {
128+ flowOf(emptyList())
129+ } else {
130+ getUserNewsResources(filterTopicIds = followedTopics)
131+ }
132+ }
133+
134+ /* *
135+ * If the user hasn't completed the onboarding and hasn't selected any interests
136+ * show an empty news list to clearly demonstrate that their selections affect the
137+ * news articles they will see.
138+ *
139+ * Note: It should not be possible for the user to get into a state where the onboarding
140+ * is not displayed AND they haven't followed any topics, however, this method is to safeguard
141+ * against that scenario in future.
142+ */
143+ private fun UserData.shouldShowEmptyFeed () =
144+ ! shouldHideOnboarding && followedTopics.isEmpty()
0 commit comments