diff --git a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt index 7b1ebf03a..48036df97 100644 --- a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt +++ b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt @@ -22,8 +22,13 @@ import com.google.samples.apps.nowinandroid.core.database.dao.TopicDao import com.google.samples.apps.nowinandroid.core.database.dao.TopicFtsDao 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.network.Dispatcher +import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flattenConcat +import kotlinx.coroutines.withContext import javax.inject.Inject class DefaultSearchContentsRepository @Inject constructor( @@ -31,11 +36,16 @@ class DefaultSearchContentsRepository @Inject constructor( private val newsResourceFtsDao: NewsResourceFtsDao, private val topicDao: TopicDao, private val topicFtsDao: TopicFtsDao, + @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, ) : SearchContentsRepository { - override fun populateFtsData() { - newsResourceFtsDao.insertAll(newsResourceDao.getOneOffNewsResources().map { it.asFtsEntity() }) - topicFtsDao.insertAll(topicDao.getOneOffTopicEntities().map { it.asFtsEntity() }) + override suspend fun populateFtsData() { + withContext(ioDispatcher) { + newsResourceFtsDao.insertAll( + newsResourceDao.getOneOffNewsResources().map { it.asFtsEntity() }, + ) + topicFtsDao.insertAll(topicDao.getOneOffTopicEntities().map { it.asFtsEntity() }) + } } override fun searchContents(searchQuery: String): Flow { @@ -44,14 +54,16 @@ class DefaultSearchContentsRepository @Inject constructor( val newsResourceIds = newsResourceFtsDao.searchAllNewsResources("*$searchQuery*") val topicIds = topicFtsDao.searchAllTopics("*$searchQuery*") - return combine( - newsResourceDao.getNewsResources(filterNewsIds = newsResourceIds.toSet()), - topicDao.getTopicEntities(topicIds.toSet()), - ) { newsResources, topics -> - SearchResult( - topics = topics.map { it.asExternalModel() }, - newsResources = newsResources.map { it.asExternalModel() }, - ) - } + return combine(newsResourceIds, topicIds) { news, topics -> + combine( + newsResourceDao.getNewsResources(filterNewsIds = news.toSet()), + topicDao.getTopicEntities(topics.toSet()), + ) { newsResources, topics -> + SearchResult( + topics = topics.map { it.asExternalModel() }, + newsResources = newsResources.map { it.asExternalModel() }, + ) + } + }.flattenConcat() } } diff --git a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/SearchContentsRepository.kt b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/SearchContentsRepository.kt index c1c20a210..c2dfa043c 100644 --- a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/SearchContentsRepository.kt +++ b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/SearchContentsRepository.kt @@ -20,8 +20,19 @@ import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.Topic import kotlinx.coroutines.flow.Flow +/** + * Data layer interface for the search feature. + */ interface SearchContentsRepository { - fun populateFtsData() + + /** + * Populate the fts tables for the search contents. + */ + suspend fun populateFtsData() + + /** + * Query the contents matched with the [searchQuery] and returns it as a [Flow] of [SearchResult] + */ fun searchContents(searchQuery: String): Flow } diff --git a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeSearchContentsRepository.kt b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeSearchContentsRepository.kt index 55655efd7..271fd2949 100644 --- a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeSearchContentsRepository.kt +++ b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeSearchContentsRepository.kt @@ -26,7 +26,7 @@ import javax.inject.Inject */ class FakeSearchContentsRepository @Inject constructor() : SearchContentsRepository { - override fun populateFtsData() {} + override suspend fun populateFtsData() {} override fun searchContents(searchQuery: String): Flow { TODO("Not yet implemented") } diff --git a/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt b/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt index 891d1f1c1..7a145166b 100644 --- a/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt +++ b/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt @@ -38,5 +38,5 @@ object DatabaseModule { "nia-database", // TODO: This is a workaround for executing read query in the main thread for search. // Figure out how other use cases avoid that - ).allowMainThreadQueries().build() + ).build() } diff --git a/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceFtsDao.kt b/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceFtsDao.kt index 0c4efabf4..bc371fc4c 100644 --- a/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceFtsDao.kt +++ b/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceFtsDao.kt @@ -21,6 +21,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceFtsEntity +import kotlinx.coroutines.flow.Flow /** * DAO for [NewsResourceFtsEntity] access. @@ -28,11 +29,11 @@ import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceFtsE @Dao interface NewsResourceFtsDao { @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertAll(topics: List) + suspend fun insertAll(topics: List) @Query("SELECT newsResourceId FROM newsResourcesFts WHERE newsResourcesFts MATCH :query") - fun searchAllNewsResources(query: String): List + fun searchAllNewsResources(query: String): Flow> @Query("SELECT count(*) FROM newsResourcesFts") - fun getCount(): Int + suspend fun getCount(): Int } diff --git a/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt b/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt index d42c71789..e1b622464 100644 --- a/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt +++ b/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt @@ -21,6 +21,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import com.google.samples.apps.nowinandroid.core.database.model.TopicFtsEntity +import kotlinx.coroutines.flow.Flow /** * DAO for [TopicFtsEntity] access. @@ -28,11 +29,11 @@ import com.google.samples.apps.nowinandroid.core.database.model.TopicFtsEntity @Dao interface TopicFtsDao { @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertAll(topics: List) + suspend fun insertAll(topics: List) @Query("SELECT topicId FROM topicsFts WHERE topicsFts MATCH :query") - fun searchAllTopics(query: String): List + fun searchAllTopics(query: String): Flow> @Query("SELECT count(*) FROM topicsFts") - fun getCount(): Int + suspend fun getCount(): Int } diff --git a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsUseCase.kt b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsUseCase.kt index a593e2071..df8155252 100644 --- a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsUseCase.kt +++ b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsUseCase.kt @@ -26,6 +26,9 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import javax.inject.Inject +/** + * A use case which returns the searched contents matched with the search query. + */ class GetSearchContentsUseCase @Inject constructor( private val searchContentsRepository: SearchContentsRepository, private val userDataRepository: UserDataRepository, diff --git a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestSearchContentsRepository.kt b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestSearchContentsRepository.kt index 44f0234d1..29df1a3b8 100644 --- a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestSearchContentsRepository.kt +++ b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestSearchContentsRepository.kt @@ -28,7 +28,7 @@ class TestSearchContentsRepository : SearchContentsRepository { private val cachedTopics: MutableList = mutableListOf() private val cachedNewsResources: MutableList = mutableListOf() - override fun populateFtsData() {} + override suspend fun populateFtsData() {} override fun searchContents(searchQuery: String): Flow { return flowOf(