diff --git a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt index 810b77cf0..f696f2b9a 100644 --- a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt +++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt @@ -24,6 +24,7 @@ import com.google.samples.apps.nowinandroid.core.navigation.NavigationState import com.google.samples.apps.nowinandroid.core.navigation.Navigator import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository +import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import com.google.samples.apps.nowinandroid.core.testing.util.TestNetworkMonitor import com.google.samples.apps.nowinandroid.core.testing.util.TestTimeZoneMonitor import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksNavKey @@ -51,16 +52,19 @@ import kotlin.test.assertEquals @HiltAndroidTest class NiaAppStateTest { - @get:Rule + @get:Rule(0) val composeTestRule = createComposeRule() + @get:Rule(1) + val mainDispatcherRule = MainDispatcherRule() + // Create the test dependencies. private val networkMonitor = TestNetworkMonitor() private val timeZoneMonitor = TestTimeZoneMonitor() private val userNewsResourceRepository = - CompositeUserNewsResourceRepository(TestNewsRepository(), TestUserDataRepository()) + CompositeUserNewsResourceRepository(TestNewsRepository(), TestUserDataRepository(), mainDispatcherRule.testDispatcher) // Subject under test. private lateinit var state: NiaAppState diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/CompositeUserNewsResourceRepository.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/CompositeUserNewsResourceRepository.kt index 64e02e7d9..776548e79 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/CompositeUserNewsResourceRepository.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/CompositeUserNewsResourceRepository.kt @@ -18,11 +18,15 @@ package com.google.samples.apps.nowinandroid.core.data.repository import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource import com.google.samples.apps.nowinandroid.core.model.data.mapToUserNewsResources +import com.google.samples.apps.nowinandroid.core.network.Dispatcher +import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.Default +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import javax.inject.Inject @@ -33,8 +37,8 @@ import javax.inject.Inject class CompositeUserNewsResourceRepository @Inject constructor( val newsRepository: NewsRepository, val userDataRepository: UserDataRepository, + @Dispatcher(Default) private val defaultDispatcher: CoroutineDispatcher, ) : UserNewsResourceRepository { - /** * Returns available news resources (joined with user data) matching the given query. */ @@ -44,7 +48,7 @@ class CompositeUserNewsResourceRepository @Inject constructor( newsRepository.getNewsResources(query) .combine(userDataRepository.userData) { newsResources, userData -> newsResources.mapToUserNewsResources(userData) - } + }.flowOn(defaultDispatcher) /** * Returns available news resources (joined with user data) for the followed topics. diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt index 3bacb8a14..c355e07c4 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt @@ -16,6 +16,7 @@ package com.google.samples.apps.nowinandroid.core.data.repository +import androidx.tracing.trace import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceDao import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceFtsDao import com.google.samples.apps.nowinandroid.core.database.dao.TopicDao @@ -25,6 +26,7 @@ import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel import com.google.samples.apps.nowinandroid.core.database.model.asFtsEntity import com.google.samples.apps.nowinandroid.core.model.data.SearchResult import com.google.samples.apps.nowinandroid.core.network.Dispatcher +import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.Default import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow @@ -32,6 +34,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.withContext import javax.inject.Inject @@ -42,6 +45,7 @@ internal class DefaultSearchContentsRepository @Inject constructor( private val topicDao: TopicDao, private val topicFtsDao: TopicFtsDao, @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, + @Dispatcher(Default) private val defaultDispatcher: CoroutineDispatcher, ) : SearchContentsRepository { override suspend fun populateFtsData() { @@ -75,11 +79,13 @@ internal class DefaultSearchContentsRepository @Inject constructor( .distinctUntilChanged() .flatMapLatest(topicDao::getTopicEntities) return combine(newsResourcesFlow, topicsFlow) { newsResources, topics -> - SearchResult( - topics = topics.map { it.asExternalModel() }, - newsResources = newsResources.map { it.asExternalModel() }, - ) - } + trace("DefaultSearchContentsRepository.searchContents") { + SearchResult( + topics = topics.map { it.asExternalModel() }, + newsResources = newsResources.map { it.asExternalModel() }, + ) + } + }.flowOn(defaultDispatcher) } override fun getSearchContentsCount(): Flow = @@ -87,6 +93,8 @@ internal class DefaultSearchContentsRepository @Inject constructor( newsResourceFtsDao.getCount(), topicFtsDao.getCount(), ) { newsResourceCount, topicsCount -> - newsResourceCount + topicsCount - } + trace("DefaultSearchContentsRepository.getSearchContentsCount") { + newsResourceCount + topicsCount + } + }.flowOn(defaultDispatcher) } diff --git a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/CompositeUserNewsResourceRepositoryTest.kt b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/CompositeUserNewsResourceRepositoryTest.kt index 05811f4be..b1920db4f 100644 --- a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/CompositeUserNewsResourceRepositoryTest.kt +++ b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/CompositeUserNewsResourceRepositoryTest.kt @@ -24,20 +24,26 @@ import com.google.samples.apps.nowinandroid.core.model.data.mapToUserNewsResourc import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository import com.google.samples.apps.nowinandroid.core.testing.repository.emptyUserData +import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import kotlinx.datetime.Instant +import org.junit.Rule import org.junit.Test import kotlin.test.assertEquals class CompositeUserNewsResourceRepositoryTest { + @get:Rule + val dispatcherRule = MainDispatcherRule() + private val newsRepository = TestNewsRepository() private val userDataRepository = TestUserDataRepository() private val userNewsResourceRepository = CompositeUserNewsResourceRepository( newsRepository = newsRepository, userDataRepository = userDataRepository, + defaultDispatcher = dispatcherRule.testDispatcher, ) @Test diff --git a/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetFollowableTopicsUseCase.kt b/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetFollowableTopicsUseCase.kt index 0167a3192..82057234f 100644 --- a/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetFollowableTopicsUseCase.kt +++ b/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetFollowableTopicsUseCase.kt @@ -16,13 +16,18 @@ package com.google.samples.apps.nowinandroid.core.domain +import androidx.tracing.trace import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.domain.TopicSortField.NAME import com.google.samples.apps.nowinandroid.core.domain.TopicSortField.NONE import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic +import com.google.samples.apps.nowinandroid.core.network.Dispatcher +import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.Default +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOn import javax.inject.Inject /** @@ -31,6 +36,7 @@ import javax.inject.Inject class GetFollowableTopicsUseCase @Inject constructor( private val topicsRepository: TopicsRepository, private val userDataRepository: UserDataRepository, + @Dispatcher(Default) private val defaultDispatcher: CoroutineDispatcher, ) { /** * Returns a list of topics with their associated followed state. @@ -41,18 +47,20 @@ class GetFollowableTopicsUseCase @Inject constructor( userDataRepository.userData, topicsRepository.getTopics(), ) { userData, topics -> - val followedTopics = topics - .map { topic -> - FollowableTopic( - topic = topic, - isFollowed = topic.id in userData.followedTopics, - ) + trace("GetFollowableTopicsUseCase.invoke") { + val followedTopics = topics + .map { topic -> + FollowableTopic( + topic = topic, + isFollowed = topic.id in userData.followedTopics, + ) + } + when (sortBy) { + NAME -> followedTopics.sortedBy { it.topic.name } + else -> followedTopics } - when (sortBy) { - NAME -> followedTopics.sortedBy { it.topic.name } - else -> followedTopics } - } + }.flowOn(defaultDispatcher) } enum class TopicSortField { diff --git a/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsUseCase.kt b/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsUseCase.kt index d1065e87c..2e8db5f0e 100644 --- a/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsUseCase.kt +++ b/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsUseCase.kt @@ -23,8 +23,12 @@ import com.google.samples.apps.nowinandroid.core.model.data.SearchResult import com.google.samples.apps.nowinandroid.core.model.data.UserData import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource import com.google.samples.apps.nowinandroid.core.model.data.UserSearchResult +import com.google.samples.apps.nowinandroid.core.network.Dispatcher +import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.Default +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOn import javax.inject.Inject /** @@ -33,16 +37,20 @@ import javax.inject.Inject class GetSearchContentsUseCase @Inject constructor( private val searchContentsRepository: SearchContentsRepository, private val userDataRepository: UserDataRepository, + @Dispatcher(Default) private val defaultDispatcher: CoroutineDispatcher, ) { operator fun invoke( searchQuery: String, ): Flow = searchContentsRepository.searchContents(searchQuery) - .mapToUserSearchResult(userDataRepository.userData) + .mapToUserSearchResult(userDataRepository.userData, defaultDispatcher) } -private fun Flow.mapToUserSearchResult(userDataStream: Flow): Flow = +private fun Flow.mapToUserSearchResult( + userDataStream: Flow, + defaultDispatcher: CoroutineDispatcher, +): Flow = combine(userDataStream) { searchResult, userData -> UserSearchResult( topics = searchResult.topics.map { topic -> @@ -58,4 +66,4 @@ private fun Flow.mapToUserSearchResult(userDataStream: Flow