Added room database in project, and refactored/migrated the data from objectBox to room.

This commit is contained in:
Gouri Panda 2023-04-12 02:40:54 +05:30 committed by MohitMaliFtechiz
parent 0c26f6192d
commit ba5da43c89
11 changed files with 262 additions and 20 deletions

View File

@ -0,0 +1,98 @@
/*
* Kiwix Android
* Copyright (c) 2023 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.dao
import androidx.room.Dao
import androidx.room.Query
import io.objectbox.Box
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchEntity
import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchRoomEntity
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
@Dao
abstract class RecentSearchRoomDao {
@Query(
"SELECT * FROM RecentSearchRoomEntity WHERE zimId LIKE :zimId ORDER BY" +
" RecentSearchRoomEntity.id DESC"
)
abstract fun search(zimId: String?): Flow<List<RecentSearchRoomEntity>>
@Query(
"SELECT * FROM RecentSearchRoomEntity"
)
abstract fun fullSearch(): Flow<List<RecentSearchRoomEntity>>
fun recentSearches(zimId: String? = ""): Flow<List<SearchListItem.RecentSearchListItem>> {
return if (zimId != "") {
search(zimId).map { searchEntities ->
searchEntities.distinctBy(RecentSearchRoomEntity::searchTerm).take(NUM_RECENT_RESULTS)
.map { searchEntity ->
SearchListItem.RecentSearchListItem(
searchEntity.searchTerm,
searchEntity.url
)
}
}
} else {
return fullSearch().map { searchEntities ->
searchEntities.distinctBy(RecentSearchRoomEntity::searchTerm)
.take(NUM_RECENT_RESULTS)
.map { searchEntity ->
SearchListItem.RecentSearchListItem(
searchEntity.searchTerm,
searchEntity.url
)
}
}
}
}
@Query(
"INSERT INTO RecentSearchRoomEntity(searchTerm, zimId, url) VALUES (:title , :zimId, :url)"
)
abstract fun saveSearch(title: String, zimId: String, url: String?)
@Query("DELETE FROM RecentSearchRoomEntity WHERE searchTerm=:searchTerm")
abstract fun deleteSearchString(searchTerm: String)
@Query("DELETE FROM RecentSearchRoomEntity")
abstract fun deleteSearchHistory()
fun migrationToRoomInsert(
box: Box<RecentSearchEntity>
) {
val searchRoomEntityList = box.all
searchRoomEntityList.forEachIndexed { _, recentSearchEntity ->
CoroutineScope(Dispatchers.IO).launch {
saveSearch(recentSearchEntity.searchTerm, recentSearchEntity.zimId, recentSearchEntity.url)
// Todo Should we remove object store data now?
}
}
}
companion object {
private const val NUM_RECENT_RESULTS = 100
}
}

View File

@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.core.dao.entities
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
@Deprecated(message = "Replaced with Room")
@Entity
data class RecentSearchEntity(
@Id var id: Long = 0L,

View File

@ -0,0 +1,29 @@
/*
* Kiwix Android
* Copyright (c) 2023 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.dao.entities
import androidx.room.PrimaryKey
@androidx.room.Entity
data class RecentSearchRoomEntity(
@PrimaryKey var id: Long = 0L,
val searchTerm: String,
val zimId: String,
val url: String?
)

View File

@ -0,0 +1,78 @@
/*
* Kiwix Android
* Copyright (c) 2023 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.data
import android.content.Context
import android.util.Log
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import io.objectbox.BoxStore
import io.objectbox.kotlin.boxFor
import org.kiwix.kiwixmobile.core.BuildConfig
import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao
import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchRoomEntity
@Suppress("UnnecessaryAbstractClass")
@Database(entities = [RecentSearchRoomEntity::class], version = 1)
abstract class KiwixRoomDatabase : RoomDatabase() {
abstract fun recentSearchRoomDao(): RecentSearchRoomDao
companion object {
private var db: KiwixRoomDatabase? = null
fun getInstance(context: Context, boxStore: BoxStore): KiwixRoomDatabase {
return db ?: synchronized(KiwixRoomDatabase::class) {
return@getInstance db
?: Room.databaseBuilder(context, KiwixRoomDatabase::class.java, "KiwixRoom.db")
// We have already database name called kiwix.db in order to avoid complexity we named as
// kiwixRoom.db
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
Log.d("gouri", "onCreate")
}
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
Log.d("gouri", "onOpen")
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
super.onDestructiveMigration(db)
Log.d("gouri", "onDestructiveMigration")
}
})
.build().also {
if (!BuildConfig.BUILD_TYPE.contentEquals("fdroid")) {
it.migrateRecentSearch(boxStore)
}
}
}
}
fun destroyInstance() {
db = null
}
}
fun migrateRecentSearch(boxStore: BoxStore) {
recentSearchRoomDao().migrationToRoomInsert(boxStore.boxFor())
}
}

View File

@ -27,7 +27,7 @@ import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.dao.NewNoteDao
import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao
import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao
import org.kiwix.kiwixmobile.core.di.qualifiers.IO
import org.kiwix.kiwixmobile.core.di.qualifiers.MainThread
import org.kiwix.kiwixmobile.core.extensions.HeaderizableList
@ -57,7 +57,7 @@ class Repository @Inject internal constructor(
private val historyDao: HistoryDao,
private val notesDao: NewNoteDao,
private val languageDao: NewLanguagesDao,
private val recentSearchDao: NewRecentSearchDao,
private val recentSearchRoomDao: RecentSearchRoomDao,
private val zimReaderContainer: ZimReaderContainer
) : DataSource {
@ -101,8 +101,8 @@ class Repository @Inject internal constructor(
override fun clearHistory() = Completable.fromAction {
historyDao.deleteAllHistory()
recentSearchDao.deleteSearchHistory()
}
recentSearchRoomDao.deleteSearchHistory()
}.subscribeOn(io)
override fun getBookmarks() =
libkiwixBookmarks.bookmarks() as Flowable<List<LibkiwixBookmarkItem>>

View File

@ -35,6 +35,7 @@ import org.kiwix.kiwixmobile.core.dao.NewBookmarksDao
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.dao.NewNoteDao
import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao
import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao
import org.kiwix.kiwixmobile.core.data.DataModule
import org.kiwix.kiwixmobile.core.data.DataSource
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
@ -97,6 +98,7 @@ interface CoreComponent {
fun wifiManager(): WifiManager
fun objectBoxToLibkiwixMigrator(): ObjectBoxToLibkiwixMigrator
fun libkiwixBookmarks(): LibkiwixBookmarks
fun recentSearchRoomDao(): RecentSearchRoomDao
fun context(): Context
fun downloader(): Downloader
fun notificationManager(): NotificationManager

View File

@ -31,6 +31,7 @@ import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.dao.NewNoteDao
import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao
import org.kiwix.kiwixmobile.core.dao.entities.MyObjectBox
import org.kiwix.kiwixmobile.core.data.KiwixRoomDatabase
import javax.inject.Singleton
@Module
@ -73,4 +74,19 @@ open class DatabaseModule {
newBookDao: NewBookDao
): FetchDownloadDao =
FetchDownloadDao(boxStore.boxFor(), newBookDao)
@Singleton
@Provides
fun provideYourDatabase(
context: Context,
boxStore: BoxStore
) =
KiwixRoomDatabase.getInstance(
context = context,
boxStore
) // The reason we can construct a database for the repo
@Singleton
@Provides
fun provideNewRecentSearchRoomDao(db: KiwixRoomDatabase) = db.recentSearchRoomDao()
}

View File

@ -36,7 +36,7 @@ import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao
import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ActivityResultReceived
@ -67,7 +67,7 @@ import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class)
class SearchViewModel @Inject constructor(
private val recentSearchDao: NewRecentSearchDao,
private val recentSearchDao: RecentSearchRoomDao,
private val zimReaderContainer: ZimReaderContainer,
private val searchResultGenerator: SearchResultGenerator
) : ViewModel() {
@ -152,7 +152,13 @@ class SearchViewModel @Inject constructor(
}
private fun deleteItemAndShowToast(it: ConfirmedDelete) {
_effects.trySend(DeleteRecentSearch(it.searchListItem, recentSearchDao)).isSuccess
_effects.trySend(
DeleteRecentSearch(
it.searchListItem,
recentSearchDao,
viewModelScope
)
).isSuccess
_effects.trySend(ShowToast(R.string.delete_specific_search_toast)).isSuccess
}

View File

@ -19,15 +19,21 @@
package org.kiwix.kiwixmobile.core.search.viewmodel.effects
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao
import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
data class DeleteRecentSearch(
private val searchListItem: SearchListItem,
private val recentSearchDao: NewRecentSearchDao
private val recentSearchRoomDao: RecentSearchRoomDao,
private val viewModelScope: CoroutineScope
) : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) {
recentSearchDao.deleteSearchString(searchListItem.value)
viewModelScope.launch(Dispatchers.IO) {
recentSearchRoomDao.deleteSearchString(searchListItem.value)
}
}
}

View File

@ -23,12 +23,12 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao
import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao
import org.kiwix.kiwixmobile.core.reader.addContentPrefix
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
data class SaveSearchToRecents(
private val recentSearchDao: NewRecentSearchDao,
private val recentSearchRoomDao: RecentSearchRoomDao,
private val searchListItem: SearchListItem,
private val id: String?,
private val viewModelScope: CoroutineScope
@ -36,7 +36,7 @@ data class SaveSearchToRecents(
override fun invokeWith(activity: AppCompatActivity) {
id?.let {
viewModelScope.launch(Dispatchers.IO) {
recentSearchDao.saveSearch(
recentSearchRoomDao.saveSearch(
searchListItem.value,
it,
searchListItem.url?.addContentPrefix

View File

@ -47,7 +47,7 @@ import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao
import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao
import org.kiwix.kiwixmobile.core.reader.ZimFileReader
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.RecentSearchListItem
@ -79,7 +79,7 @@ import org.kiwix.libzim.SuggestionSearch
@OptIn(ExperimentalCoroutinesApi::class)
internal class SearchViewModelTest {
private val recentSearchDao: NewRecentSearchDao = mockk()
private val recentSearchRoomDao: RecentSearchRoomDao = mockk()
private val zimReaderContainer: ZimReaderContainer = mockk()
private val searchResultGenerator: SearchResultGenerator = mockk()
private val zimFileReader: ZimFileReader = mockk()
@ -105,8 +105,8 @@ internal class SearchViewModelTest {
searchResultGenerator.generateSearchResults("", zimFileReader)
} returns null
every { zimReaderContainer.id } returns "id"
every { recentSearchDao.recentSearches("id") } returns recentsFromDb.consumeAsFlow()
viewModel = SearchViewModel(recentSearchDao, zimReaderContainer, searchResultGenerator)
every { recentSearchRoomDao.recentSearches("id") } returns recentsFromDb.consumeAsFlow()
viewModel = SearchViewModel(recentSearchRoomDao, zimReaderContainer, searchResultGenerator)
}
@Nested
@ -158,7 +158,10 @@ internal class SearchViewModelTest {
val searchListItem = RecentSearchListItem("", "")
actionResultsInEffects(
OnItemClick(searchListItem),
SaveSearchToRecents(recentSearchDao, searchListItem, "id", viewModel.viewModelScope),
SaveSearchToRecents(
recentSearchRoomDao, searchListItem, "id",
viewModel.viewModelScope
),
OpenSearchItem(searchListItem, false)
)
}
@ -168,7 +171,10 @@ internal class SearchViewModelTest {
val searchListItem = RecentSearchListItem("", "")
actionResultsInEffects(
OnOpenInNewTabClick(searchListItem),
SaveSearchToRecents(recentSearchDao, searchListItem, "id", viewModel.viewModelScope),
SaveSearchToRecents(
recentSearchRoomDao, searchListItem, "id",
viewModel.viewModelScope
),
OpenSearchItem(searchListItem, true)
)
}
@ -192,7 +198,7 @@ internal class SearchViewModelTest {
val searchListItem = RecentSearchListItem("", "")
actionResultsInEffects(
ConfirmedDelete(searchListItem),
DeleteRecentSearch(searchListItem, recentSearchDao),
DeleteRecentSearch(searchListItem, recentSearchRoomDao, viewModel.viewModelScope),
ShowToast(R.string.delete_specific_search_toast)
)
}