How to create a generic Repository class? (Android) - android

in my Android app I use MVVM model with repositories
most of my DAO and Repositories classes are almost identical, so I figured I could reuse some code by creating generic equivalents that are later extended
this worked fine for my DAO classes:
#Dao
interface BaseDao<T> {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(vararg entity: T)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertList(entities: List<T>)
#Update
suspend fun update(entity: T)
#Delete
suspend fun delete(entity: T)
}
#Dao
interface SettingDao : BaseDao<Setting> {
#Query("SELECT value FROM settings WHERE id = :settingName")
fun get(settingName: String): LiveData<String>
#Query("SELECT * FROM settings ORDER BY id ASC")
fun getAll(): LiveData<List<Setting>>
#Query("DELETE FROM settings")
suspend fun deleteAll()
}
now I want to do something similar for repository, but I cannot figure out how
this is what I've tried (I get error java.lang.ClassCastException: java.lang.Object[] cannot be cast to: Setting[]) in BaseRepository$insert$2.invokeSuspend
abstract class BaseRepository<T>(private val baseDao: BaseDao<T>) {
suspend fun update(entity: T) {
withContext(Dispatchers.IO) { baseDao.update(entity) }
}
suspend fun insert(entity: T) {
withContext(Dispatchers.IO) { baseDao.insert(entity) }
}
suspend fun delete(entity: T) {
withContext(Dispatchers.IO) { baseDao.delete(entity) }
}
}
class SettingRepository(private val settingDao: SettingDao) : BaseRepository<Setting>(settingDao) {
val company = settingDao.get("company")
val ip = settingDao.get("ip")
suspend fun deleteAll() {
withContext(Dispatchers.IO) { settingDao.deleteAll() }
}
}
My intuition is that I have to adjust this bit private val baseDao: BaseDao<T> in BaseRepository, but I cannot figure out how
And 2nd question - is there a way to get a generic table name in DAO, so I could put
#Query("DELETE FROM settings")
suspend fun deleteAll()
in the generic interface as well (in this case replace settings with entity's tableName

Related

How to set getAll and nukeData as inheritance in Android Room?

I have
#Dao
interface ContactDao {
#Query("SELECT * FROM ContactItem")
fun getAll(): Flow<List<ContactItem>>
#Insert
fun insertAll(vararg todos: ContactItem)
#Delete
fun delete(todo: ContactItem)
#Update
fun update(note: ContactItem)
#Query("DELETE FROM ContactItem")
fun nukeTable()
}
Dao
interface TodoDao {
#Query("SELECT * FROM TodoItem")
fun getAll(): Flow<List<TodoItem>>
#Insert
fun insertAll(vararg todos: TodoItem)
#Delete
fun delete(todo: TodoItem)
#Update
fun update(note: TodoItem)
#Query("DELETE FROM TodoItem")
fun nukeTable()
}
And I can make some of them in base interface to shrink both of them
interface BaseDao<T> {
#Insert
fun insertAll(vararg obj: T)
#Delete
fun delete(obj: T)
#Update
fun update(obj: T)
}
#Dao
interface ContactDao: BaseDao<ContactItem> {
#Query("SELECT * FROM ContactItem")
fun getAll(): Flow<List<ContactItem>>
#Query("DELETE FROM ContactItem")
fun nukeTable()
}
#Dao
interface TodoDao: BaseDao<TodoItem> {
#Query("SELECT * FROM TodoItem")
fun getAll(): Flow<List<TodoItem>>
#Query("DELETE FROM TodoItem")
fun nukeTable()
}
But you can see that both getAll and nukeTable is still there. I hope to also make it into the BaseDao. Is there any way to do that?

Android Room - Trying to query a single row based on Primary ID

What am I trying to achieve ?
Get a single row of data which has the id I need. The SQL equivalent of SELECT * FROM favs WHERE link='link'. I have written a fun named getOneFav() which for this. I am following the tutorial https://developer.android.com/codelabs/android-room-with-a-view-kotlin#0 and code from https://github.com/android/sunflower
What have I setup so far ?
Entity
#Entity(tableName = "favs")
data class Favorite(
#PrimaryKey #ColumnInfo(name = "link") val link : String,
#ColumnInfo(name = "keywords") val keywords : String
)
DAO
#Dao
interface FavDAO {
#Query("SELECT * FROM favs")
fun getAllFavsLive(): Flow<List<Favorite>>
#Query("SELECT * FROM favs WHERE link = :link")
fun getOneFav(link: String): Favorite
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(link: Favorite)
}
Repository
class FavRepo (private val favDao: FavDAO) {
val allFavs: Flow<List<Favorite>> = favDao.getAllFavsLive()
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun insert(link: Favorite) {
favDao.insert(link)
}
fun getOneFav(link: String) = favDao.getOneFav(link)
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun delete(link: String) {
favDao.delete(link)
}
}
ViewModel
class FavViewModel (private val repository: FavRepo) : ViewModel() {
val allFavs: LiveData<List<Favorite>> = repository.allFavs.asLiveData()
fun insert(link: Favorite) = viewModelScope.launch {
repository.insert(link)
}
fun getOneFav(link: String) = repository.getOneFav(link)
}
class FavViewModelFactory(private val repository: FavRepo) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(FavViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return FavViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
What problems am I facing ?
I am receiving an error saying
java.lang.RuntimeException: Unable to start activity ComponentInfo{[package name removed].MainActivity}: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
What have I tried so far ?
I have tried -
Adding suspend in front of the function getOneFav in DAO and Repository
Made the function run inside viewModelScope. It gave the same error as above. Also, this way the function returned a Job instead of the 'Favorite' data class object.
fun getOneFav(link: String) = viewModelScope.launch {
repository.getOneFav(link)
}
Followed this method here - How to implement a Room LiveData filter which even though worked, which seemed like an overkill for something so simple. Also despite the fact that the code is using MutableLiveData, I wasn't able to see any triggers when the insert happened.
You should run your queries in a different context:
class FavRepo (private val favDao: FavDAO) {
val allFavs: Flow<List<Favorite>> = withContext(Dispatchers.IO) {
favDao.getAllFavsLive()
}
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun insert(link: Favorite) = withContext(Dispatchers.IO) {
favDao.insert(link)
}
fun getOneFav(link: String) = withContext(Dispatchers.IO) {
favDao.getOneFav(link)
}
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun delete(link: String) = withContext(Dispatchers.IO) {
favDao.delete(link)
}
}

Not sure how to handle insert method's return type. public abstract java.lang.Object insert(#org.jetbrains.annotations.NotNull()

I am complete new to kotlin, and trying to build a notes app using MVVM architecture. But somewhere I go wrong and end up with this error. My kotlin version is 1.5.31
NoteDao.java:11: error: Not sure how to handle insert method's return type.
public abstract java.lang.Object insert(#org.jetbrains.annotations.NotNull()
NoteDao.kt
#Dao
interface NoteDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(note:Note): Note
#Delete
suspend fun delete(note: Note): Note
#Query("SELECT * FROM notes_table ORDER BY primary_key ASC")
fun getAllNotes(): LiveData<List<Note>>
}
Note.kt
#Entity(tableName = "notes_table")
class Note(#ColumnInfo(name = "note_text", typeAffinity = TEXT) val text: String){
#PrimaryKey(autoGenerate = true) #ColumnInfo(name="primary_key", typeAffinity = INTEGER) var id = 0
}
I am not sure how to handle this.
Try doing this:
#Dao
abstract class BaseDao<T : BaseEntity>(private val tableName: String) {
#Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insert(entity: T): Long
#Update
abstract suspend fun update(entity: T)
#Delete
abstract suspend fun delete(entity: T)
}
abstract class BaseEntity {
abstract val id: Long
}

Kotlin observeForever every time is null

i'm trying realise some global object for my app and observe inside to some LiveData from my DAO. Here is how it looks:
object appCommon {
#Volatile
lateinit var config: Config
suspend fun initAppCommon() {
GlobalScope.launch(Dispatchers.Main) {
Database_mW.getDBInstance(AppController.appInstance).ConfigDAO().getLive()
.observeForever(Observer {
config = it
})
}
initConfig()
}
private suspend fun initConfig() {
withContext(Dispatchers.IO) {
if (Database_mW.getDBInstance(AppController.appInstance).ConfigDAO().get() == null) {
Database_mW.getDBInstance(AppController.appInstance).ConfigDAO().insert(Config(getDeviceSerialNumber(), "", "", ""))
}
}
}
}
Here is DAO:
#Dao
interface Config_dao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(config: Config)
#Update
fun update(config: Config)
#Query("DELETE FROM Config")
fun clear()
#Query("SELECT * from Config LIMIT 1")
fun get(): Config
#Query("SELECT * from Config LIMIT 1")
fun getLive(): LiveData<Config>
#Query("update Config set device_secret = :secret")
fun updateSecret(secret: String)
}
So when i'm calling ConfigDAO().insert, observer is invoking but it = null.
Can somebody explain how to get latest inserted object inside observer for to update my global var?

Count rows Android Room on Kotlin

learning Kotlin, proper app structure and stuff... Again... :)
So i have the following:
Dao
Repository
ViewModel
Dao Code
#Dao
interface ItemDao {
#Query("SELECT * from item_table ORDER BY Name ASC")
fun getSortedItems(): LiveData<List<Item>>
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: Item)
#Query("DELETE FROM item_table")
suspend fun deleteAll()
}
Repository
class ItemRepository (private val itemDao: ItemDao) {
val allItems: LiveData<List<Item>> = itemDao.getSortedItems()
suspend fun insert(item: Item) {
itemDao.insert(item)
}
}
ViewModel
class ItemViewModel(application: Application) : AndroidViewModel(application) {
private val repository: ItemRepository
val allItems: LiveData<List<Item>>
init {
val itemsDao = ItemDataBase.getDatabase(application, viewModelScope).itemDao()
repository = ItemRepository(itemsDao)
allItems = repository.allItems
}
fun insert(item: Item) = viewModelScope.launch {
repository.insert(item)
}
}
I need to get current amount of rows in a table and i was thinking of something like this:
add to Dao
#Query("SELECT COUNT(name) FROM item_table")
fun getAmount(): LiveData<Int>
and then in Repo
fun getAmount(): LiveData<Int>{
return itemDao.getAmount()
}
and the same in ViewModel
fun getAmount(): LiveData<Int> {
return repository.getAmount()
}
But again, should i also use an observer in Activity to get this value? Or, on the other hand, can i use just Int, while getting the table from DB as LiveData?

Categories

Resources