Change the result type of searchAll* in *FtsDao as Flow to not block the

calling thread
recent_search
Takeshi Hagikura 1 year ago
parent 477838f9da
commit 333a248327

@ -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.dao.TopicFtsDao
import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel 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.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.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flattenConcat
import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
class DefaultSearchContentsRepository @Inject constructor( class DefaultSearchContentsRepository @Inject constructor(
@ -31,11 +36,16 @@ class DefaultSearchContentsRepository @Inject constructor(
private val newsResourceFtsDao: NewsResourceFtsDao, private val newsResourceFtsDao: NewsResourceFtsDao,
private val topicDao: TopicDao, private val topicDao: TopicDao,
private val topicFtsDao: TopicFtsDao, private val topicFtsDao: TopicFtsDao,
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
) : SearchContentsRepository { ) : SearchContentsRepository {
override fun populateFtsData() { override suspend fun populateFtsData() {
newsResourceFtsDao.insertAll(newsResourceDao.getOneOffNewsResources().map { it.asFtsEntity() }) withContext(ioDispatcher) {
topicFtsDao.insertAll(topicDao.getOneOffTopicEntities().map { it.asFtsEntity() }) newsResourceFtsDao.insertAll(
newsResourceDao.getOneOffNewsResources().map { it.asFtsEntity() },
)
topicFtsDao.insertAll(topicDao.getOneOffTopicEntities().map { it.asFtsEntity() })
}
} }
override fun searchContents(searchQuery: String): Flow<SearchResult> { override fun searchContents(searchQuery: String): Flow<SearchResult> {
@ -44,14 +54,16 @@ class DefaultSearchContentsRepository @Inject constructor(
val newsResourceIds = newsResourceFtsDao.searchAllNewsResources("*$searchQuery*") val newsResourceIds = newsResourceFtsDao.searchAllNewsResources("*$searchQuery*")
val topicIds = topicFtsDao.searchAllTopics("*$searchQuery*") val topicIds = topicFtsDao.searchAllTopics("*$searchQuery*")
return combine( return combine(newsResourceIds, topicIds) { news, topics ->
newsResourceDao.getNewsResources(filterNewsIds = newsResourceIds.toSet()), combine(
topicDao.getTopicEntities(topicIds.toSet()), newsResourceDao.getNewsResources(filterNewsIds = news.toSet()),
) { newsResources, topics -> topicDao.getTopicEntities(topics.toSet()),
SearchResult( ) { newsResources, topics ->
topics = topics.map { it.asExternalModel() }, SearchResult(
newsResources = newsResources.map { it.asExternalModel() }, topics = topics.map { it.asExternalModel() },
) newsResources = newsResources.map { it.asExternalModel() },
} )
}
}.flattenConcat()
} }
} }

@ -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 com.google.samples.apps.nowinandroid.core.model.data.Topic
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
/**
* Data layer interface for the search feature.
*/
interface SearchContentsRepository { 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<SearchResult> fun searchContents(searchQuery: String): Flow<SearchResult>
} }

@ -26,7 +26,7 @@ import javax.inject.Inject
*/ */
class FakeSearchContentsRepository @Inject constructor() : SearchContentsRepository { class FakeSearchContentsRepository @Inject constructor() : SearchContentsRepository {
override fun populateFtsData() {} override suspend fun populateFtsData() {}
override fun searchContents(searchQuery: String): Flow<SearchResult> { override fun searchContents(searchQuery: String): Flow<SearchResult> {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

@ -38,5 +38,5 @@ object DatabaseModule {
"nia-database", "nia-database",
// TODO: This is a workaround for executing read query in the main thread for search. // TODO: This is a workaround for executing read query in the main thread for search.
// Figure out how other use cases avoid that // Figure out how other use cases avoid that
).allowMainThreadQueries().build() ).build()
} }

@ -21,6 +21,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceFtsEntity import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceFtsEntity
import kotlinx.coroutines.flow.Flow
/** /**
* DAO for [NewsResourceFtsEntity] access. * DAO for [NewsResourceFtsEntity] access.
@ -28,11 +29,11 @@ import com.google.samples.apps.nowinandroid.core.database.model.NewsResourceFtsE
@Dao @Dao
interface NewsResourceFtsDao { interface NewsResourceFtsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(topics: List<NewsResourceFtsEntity>) suspend fun insertAll(topics: List<NewsResourceFtsEntity>)
@Query("SELECT newsResourceId FROM newsResourcesFts WHERE newsResourcesFts MATCH :query") @Query("SELECT newsResourceId FROM newsResourcesFts WHERE newsResourcesFts MATCH :query")
fun searchAllNewsResources(query: String): List<String> fun searchAllNewsResources(query: String): Flow<List<String>>
@Query("SELECT count(*) FROM newsResourcesFts") @Query("SELECT count(*) FROM newsResourcesFts")
fun getCount(): Int suspend fun getCount(): Int
} }

@ -21,6 +21,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.google.samples.apps.nowinandroid.core.database.model.TopicFtsEntity import com.google.samples.apps.nowinandroid.core.database.model.TopicFtsEntity
import kotlinx.coroutines.flow.Flow
/** /**
* DAO for [TopicFtsEntity] access. * DAO for [TopicFtsEntity] access.
@ -28,11 +29,11 @@ import com.google.samples.apps.nowinandroid.core.database.model.TopicFtsEntity
@Dao @Dao
interface TopicFtsDao { interface TopicFtsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(topics: List<TopicFtsEntity>) suspend fun insertAll(topics: List<TopicFtsEntity>)
@Query("SELECT topicId FROM topicsFts WHERE topicsFts MATCH :query") @Query("SELECT topicId FROM topicsFts WHERE topicsFts MATCH :query")
fun searchAllTopics(query: String): List<String> fun searchAllTopics(query: String): Flow<List<String>>
@Query("SELECT count(*) FROM topicsFts") @Query("SELECT count(*) FROM topicsFts")
fun getCount(): Int suspend fun getCount(): Int
} }

@ -26,6 +26,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import javax.inject.Inject import javax.inject.Inject
/**
* A use case which returns the searched contents matched with the search query.
*/
class GetSearchContentsUseCase @Inject constructor( class GetSearchContentsUseCase @Inject constructor(
private val searchContentsRepository: SearchContentsRepository, private val searchContentsRepository: SearchContentsRepository,
private val userDataRepository: UserDataRepository, private val userDataRepository: UserDataRepository,

@ -28,7 +28,7 @@ class TestSearchContentsRepository : SearchContentsRepository {
private val cachedTopics: MutableList<Topic> = mutableListOf() private val cachedTopics: MutableList<Topic> = mutableListOf()
private val cachedNewsResources: MutableList<NewsResource> = mutableListOf() private val cachedNewsResources: MutableList<NewsResource> = mutableListOf()
override fun populateFtsData() {} override suspend fun populateFtsData() {}
override fun searchContents(searchQuery: String): Flow<SearchResult> { override fun searchContents(searchQuery: String): Flow<SearchResult> {
return flowOf( return flowOf(

Loading…
Cancel
Save