From 180dbe4c313c450a6b8012ff4f545faaffd6d0f5 Mon Sep 17 00:00:00 2001 From: Jaehwa Noh Date: Thu, 1 Jan 2026 17:20:22 +0900 Subject: [PATCH 01/10] Add new queries: update, delete, insert Change-Id: Iae763a3efc8a00c7ffda252b2d9f8d9f2bf3667b --- .../core/database/dao/TopicFtsDao.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt index 25dea5b83..842fca08d 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt @@ -31,6 +31,43 @@ interface TopicFtsDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAll(topics: List) + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(topic: TopicFtsEntity) + + @Query( + """ + DELETE + FROM topicsFts + WHERE topicId = :topicId + """, + ) + suspend fun deleteById(topicId: String) + + @Query( + """ + SELECT * + FROM topicsFts + WHERE topicId = :topicId + """, + ) + suspend fun getFtsEntityById(topicId: String): List + + @Query( + """ + UPDATE topicsFts + SET name = :name, + shortDescription = :shortDescription, + longDescription = :longDescription + WHERE topicId = :topicId + """, + ) + suspend fun update( + name: String, + shortDescription: String, + longDescription: String, + topicId: String, + ) + @Query("SELECT topicId FROM topicsFts WHERE topicsFts MATCH :query") fun searchAllTopics(query: String): Flow> From 672b4a1470ca557c11f698774c7c1bfbecec48ce Mon Sep 17 00:00:00 2001 From: Jaehwa Noh Date: Thu, 1 Jan 2026 17:29:58 +0900 Subject: [PATCH 02/10] Update an insert logic of TopicFts. Change-Id: Id8fd51a4542e8cb22645d36ec507af4d7f02489d --- .../DefaultSearchContentsRepository.kt | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) 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..9ef262a89 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 @@ -54,7 +54,30 @@ internal class DefaultSearchContentsRepository @Inject constructor( .first() .map(PopulatedNewsResource::asFtsEntity), ) - topicFtsDao.insertAll(topicDao.getOneOffTopicEntities().map { it.asFtsEntity() }) + + + topicDao.getOneOffTopicEntities().forEach { topicEntity -> + val topicFtsEntities = topicFtsDao.getFtsEntityById(topicEntity.id) + + val size = topicFtsEntities.size + if (size == 0) { + topicFtsDao.insert(topicEntity.asFtsEntity()) + return@forEach + } + + if (size > 1) { + topicFtsDao.deleteById(topicEntity.id) + topicFtsDao.insert(topicEntity.asFtsEntity()) + return@forEach + } + + topicFtsDao.update( + name = topicEntity.name, + shortDescription = topicEntity.shortDescription, + longDescription = topicEntity.longDescription, + topicId = topicEntity.id, + ) + } } } From 1c9b04fec732432420b052071e9acf1f3a72ba8e Mon Sep 17 00:00:00 2001 From: Jaehwa Noh Date: Thu, 1 Jan 2026 17:30:45 +0900 Subject: [PATCH 03/10] Remove the unused insertAll function. Change-Id: I25c338839275cd43db92e8a9e0d45bed8776f316 --- .../samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt index 842fca08d..ca9a8d313 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt @@ -28,8 +28,6 @@ import kotlinx.coroutines.flow.Flow */ @Dao interface TopicFtsDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAll(topics: List) @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(topic: TopicFtsEntity) From ce95b9eefcbadc666ed9c5121504e2b90f0211c3 Mon Sep 17 00:00:00 2001 From: Jaehwa Noh Date: Thu, 1 Jan 2026 17:42:27 +0900 Subject: [PATCH 04/10] Fix typo. Change-Id: Ia4cc6dd28b85d7a4ce54e66160a2319d11808047 --- .../core/data/repository/DefaultSearchContentsRepository.kt | 2 +- .../samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 9ef262a89..288ed603d 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 @@ -57,7 +57,7 @@ internal class DefaultSearchContentsRepository @Inject constructor( topicDao.getOneOffTopicEntities().forEach { topicEntity -> - val topicFtsEntities = topicFtsDao.getFtsEntityById(topicEntity.id) + val topicFtsEntities = topicFtsDao.getFtsEntitiesById(topicEntity.id) val size = topicFtsEntities.size if (size == 0) { diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt index ca9a8d313..35bc8ba0c 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/TopicFtsDao.kt @@ -48,7 +48,7 @@ interface TopicFtsDao { WHERE topicId = :topicId """, ) - suspend fun getFtsEntityById(topicId: String): List + suspend fun getFtsEntitiesById(topicId: String): List @Query( """ From 33fb12e83709041128a2f0621dbde7af406a4c7c Mon Sep 17 00:00:00 2001 From: Jaehwa Noh Date: Thu, 1 Jan 2026 17:59:21 +0900 Subject: [PATCH 05/10] Rename to oldTopicFtsEntities Change-Id: I234ecf3b50b8c327d919d0fc4a0d489e5101f690 --- .../core/data/repository/DefaultSearchContentsRepository.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 288ed603d..7cd3a6130 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 @@ -57,9 +57,9 @@ internal class DefaultSearchContentsRepository @Inject constructor( topicDao.getOneOffTopicEntities().forEach { topicEntity -> - val topicFtsEntities = topicFtsDao.getFtsEntitiesById(topicEntity.id) + val oldTopicFtsEntities = topicFtsDao.getFtsEntitiesById(topicEntity.id) - val size = topicFtsEntities.size + val size = oldTopicFtsEntities.size if (size == 0) { topicFtsDao.insert(topicEntity.asFtsEntity()) return@forEach From 4e72fbceae6a1cde065b39d57e096df3505eaa48 Mon Sep 17 00:00:00 2001 From: Jaehwa Noh Date: Thu, 1 Jan 2026 18:07:10 +0900 Subject: [PATCH 06/10] Add queries: insert, update, delete. Change-Id: I68d5a3d77a5f0d1fdc1a84a6fbb1221a674d9793 --- .../core/database/dao/NewsResourceFtsDao.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceFtsDao.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceFtsDao.kt index 86cc5529e..7d61406f4 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceFtsDao.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceFtsDao.kt @@ -31,6 +31,41 @@ interface NewsResourceFtsDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAll(newsResources: List) + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(newsResource: NewsResourceFtsEntity) + + @Query( + """ + DELETE + FROM newsResourcesFts + WHERE newsResourceId = :newsResourceId + """, + ) + suspend fun deleteById(newsResourceId: String) + + @Query( + """ + SELECT * + FROM newsResourcesFts + WHERE newsResourceId = :newsResourceId + """, + ) + suspend fun getFtsEntitiesById(newsResourceId: String): List + + @Query( + """ + UPDATE newsResourcesFts + SET title = :title, + content = :content + WHERE newsResourceId = :newsResourceId + """, + ) + suspend fun update( + title: String, + content: String, + newsResourceId: String, + ) + @Query("SELECT newsResourceId FROM newsResourcesFts WHERE newsResourcesFts MATCH :query") fun searchAllNewsResources(query: String): Flow> From d834525941209909ea0798dcc4878b910118063e Mon Sep 17 00:00:00 2001 From: Jaehwa Noh Date: Thu, 1 Jan 2026 18:07:34 +0900 Subject: [PATCH 07/10] Remove the unused function. Change-Id: I4c3e0c2f2e063dec18c07bf2c81c098787956438 --- .../apps/nowinandroid/core/database/dao/NewsResourceFtsDao.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceFtsDao.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceFtsDao.kt index 7d61406f4..33ae93ace 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceFtsDao.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceFtsDao.kt @@ -28,8 +28,6 @@ import kotlinx.coroutines.flow.Flow */ @Dao interface NewsResourceFtsDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAll(newsResources: List) @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(newsResource: NewsResourceFtsEntity) From b6f2647728f7eaa64a17e0e9e0797224b0175591 Mon Sep 17 00:00:00 2001 From: Jaehwa Noh Date: Thu, 1 Jan 2026 18:08:39 +0900 Subject: [PATCH 08/10] Change an insert logic of newsResourceFts. Change-Id: I09fa6c445b142072e9fba6923fbd51fbe372cacf --- .../DefaultSearchContentsRepository.kt | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) 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 7cd3a6130..b1b3634be 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 @@ -20,7 +20,6 @@ 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 import com.google.samples.apps.nowinandroid.core.database.dao.TopicFtsDao -import com.google.samples.apps.nowinandroid.core.database.model.PopulatedNewsResource 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 @@ -46,14 +45,36 @@ internal class DefaultSearchContentsRepository @Inject constructor( override suspend fun populateFtsData() { withContext(ioDispatcher) { - newsResourceFtsDao.insertAll( - newsResourceDao.getNewsResources( - useFilterTopicIds = false, - useFilterNewsIds = false, - ) - .first() - .map(PopulatedNewsResource::asFtsEntity), + newsResourceDao.getNewsResources( + useFilterTopicIds = false, + useFilterNewsIds = false, ) + .first() + .forEach { populatedNewsResource -> + val oldNewsResourceFtsEntities = + newsResourceFtsDao.getFtsEntitiesById(populatedNewsResource.entity.id) + val size = oldNewsResourceFtsEntities.size + + val newsResourceFtsEntity = populatedNewsResource.asFtsEntity() + + if (size == 0) { + newsResourceFtsDao.insert(newsResourceFtsEntity) + return@forEach + } + + // Do it for migration, multiple same id entity exists in the fts database. + if (size > 1) { + newsResourceFtsDao.deleteById(newsResourceFtsEntity.newsResourceId) + newsResourceFtsDao.insert(newsResourceFtsEntity) + return@forEach + } + + newsResourceFtsDao.update( + title = newsResourceFtsEntity.title, + content = newsResourceFtsEntity.content, + newsResourceId = newsResourceFtsEntity.newsResourceId, + ) + } topicDao.getOneOffTopicEntities().forEach { topicEntity -> @@ -65,6 +86,7 @@ internal class DefaultSearchContentsRepository @Inject constructor( return@forEach } + // Do it for migration, multiple same id entity exists in the fts database. if (size > 1) { topicFtsDao.deleteById(topicEntity.id) topicFtsDao.insert(topicEntity.asFtsEntity()) From 6ab93572fe700445559d21ca15dd100bc9ed27d0 Mon Sep 17 00:00:00 2001 From: Jaehwa Noh Date: Thu, 1 Jan 2026 18:39:53 +0900 Subject: [PATCH 09/10] Fix spotless. Change-Id: I52c1595b039501f9c123dda4425ecba687ad7ac6 --- .../core/data/repository/DefaultSearchContentsRepository.kt | 1 - 1 file changed, 1 deletion(-) 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 b1b3634be..48a5c75a6 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 @@ -76,7 +76,6 @@ internal class DefaultSearchContentsRepository @Inject constructor( ) } - topicDao.getOneOffTopicEntities().forEach { topicEntity -> val oldTopicFtsEntities = topicFtsDao.getFtsEntitiesById(topicEntity.id) From 9b0946ae12fdbad853f647a17e4b3013c1e9c4d1 Mon Sep 17 00:00:00 2001 From: Jaehwa Noh Date: Thu, 1 Jan 2026 22:32:12 +0900 Subject: [PATCH 10/10] Add pass logic. Change-Id: Ibd0a8d67fd3d73435761ca2c495265eaf51a06d5 --- .../repository/DefaultSearchContentsRepository.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) 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 48a5c75a6..8b0650120 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 @@ -69,6 +69,10 @@ internal class DefaultSearchContentsRepository @Inject constructor( return@forEach } + if (oldNewsResourceFtsEntities.firstOrNull() == newsResourceFtsEntity) { + return@forEach + } + newsResourceFtsDao.update( title = newsResourceFtsEntity.title, content = newsResourceFtsEntity.content, @@ -78,20 +82,24 @@ internal class DefaultSearchContentsRepository @Inject constructor( topicDao.getOneOffTopicEntities().forEach { topicEntity -> val oldTopicFtsEntities = topicFtsDao.getFtsEntitiesById(topicEntity.id) - val size = oldTopicFtsEntities.size + + val topicFtsEntity = topicEntity.asFtsEntity() + if (size == 0) { - topicFtsDao.insert(topicEntity.asFtsEntity()) + topicFtsDao.insert(topicFtsEntity) return@forEach } // Do it for migration, multiple same id entity exists in the fts database. if (size > 1) { topicFtsDao.deleteById(topicEntity.id) - topicFtsDao.insert(topicEntity.asFtsEntity()) + topicFtsDao.insert(topicFtsEntity) return@forEach } + if (oldTopicFtsEntities.firstOrNull() == topicFtsEntity) return@forEach + topicFtsDao.update( name = topicEntity.name, shortDescription = topicEntity.shortDescription,