Android Room + Kotlin pattern - kotlin

the Android Room documentation says that we should follow the singleton design pattern when instantiating an AppDatabase object.
I was thinking about it, and I would like to know if its recommended to put the AppDatabase class inside my Application class. Or if I can use the Kotlin singleton for that.
Let's say I have a DAO called CarroDAO and class CarrosDatabase that is a RoomDatabase.
Is it ok to create a DatabaseManager class using a Kotlin object/singleton ?
object DatabaseManager {
private var dbInstance: CarrosDatabase
init {
val appContext = MyApplication.getInstance().applicationContext
dbInstance = Room.databaseBuilder(
appContext,
CarrosDatabase::class.java,
"mybd.sqlite")
.build()
}
fun getCarroDAO(): CarroDAO {
return dbInstance.carroDAO()
}
}
So I can get the DAO class like this:
val dao = DatabaseManager.getCarroDAO()

According to Android documentation, we can create a database instance using the singleton design pattern as follows
Create a room database entity
#Entity
data class User(
#PrimaryKey var uid: Int,
#ColumnInfo(name = "first_name") var firstName: String?,
#ColumnInfo(name = "last_name") var lastName: String?
)
Create DAO class
#Dao
interface UserDao {
#Query("SELECT * FROM user")
fun getAll(): List<User>
#Query("SELECT * FROM user WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List<User>
#Query("SELECT * FROM user WHERE first_name LIKE: first AND " +
"last_name LIKE :last LIMIT 1")
fun findByName(first: String, last: String): User
#Insert
fun insertAll(vararg users: User)
#Delete
fun delete(user: User)
}
Create database with singleton pattern
#Database(entities = arrayOf(User::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
#Volatile
private var instance: AppDatabase? = null
fun getInstance(
context: Context
): AppDatabase = instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"database-name"
).build()
}
}
}
You can get database instance by following code
var databaseInstance=AppDatabase.getInstance(context)

Related

Why is my data size zero from repository in Kotlin?

I m trying to pull data from room database by identifier for my dictionary app in Kotlin jetpack compose but when I try to show data by identifier from room database there is no data because data size is coming zero. It may be because I'm pulling the data asynchronously, but shouldn't it still arrive? Is there a place I missed? can you help me ? I share my code below .
My Room Entity class
#Entity(tableName = "GermanDictionary")
data class DictionaryEntity(
#PrimaryKey(autoGenerate = true) val uid: Int? = null,
#ColumnInfo(name = "identifier") val identifier:String,
#ColumnInfo(name = "words") val words:List<Word>
)
My Dao
#Dao
interface DictionaryDao {
#Query("SELECT * FROM GermanDictionary")
suspend fun getAllFromDatabase(): List<DictionaryEntity>
#Query("SELECT * FROM GermanDictionary WHERE uid =:id ")
suspend fun searchWordFromDatabase(id:String):List<DictionaryEntity>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertGermanWords(GermanWords:List<DictionaryEntity>)
#Query("SELECT * FROM GermanDictionary WHERE identifier =:identifier ")
suspend fun getWordsByIdentifier(identifier:String):DictionaryEntity
}
database
#TypeConverters(DictionaryConverter::class)
#Database(entities = [DictionaryEntity::class], version = 1)
abstract class DictionaryDatabase : RoomDatabase(){
abstract val dao: DictionaryDao
}
Domain
Repository
interface DictionaryRepository {
suspend fun getAllFromDatabase(): Flow<List<Word>>
suspend fun searchWordFromDatabase(id:String): Word
suspend fun insertGermanWords()
suspend fun getWordsByIdentifier(identifier:String) : Flow<List<Word>?>
}
Domain
UseCase
class GetWordsByIdentifierUseCase #Inject constructor(
private val repository: DictionaryRepository
){
suspend fun getWordsByIdentifier(identifier : String) : Flow<List<Word>?> {
return repository.getWordsByIdentifier(identifier = identifier)
}
}
ui
View Model
#HiltViewModel
class DisplayScreenViewModel #Inject constructor(
savedStateHandle: SavedStateHandle,
private var getWordsByIdentifierUseCase: GetWordsByIdentifierUseCase
) : ViewModel() {
private var _state = mutableStateOf(WordState())
var state: State<WordState> = _state
private val identifier: String? = savedStateHandle[DISPLAY_ARG_KEY]
init {
identifier?.let { load(identifier = this.identifier) }
}
private fun load(identifier: String) {
viewModelScope.launch {
getWordsByIdentifierUseCase.getWordsByIdentifier(identifier)
.collect { item ->
_state.value = state.value.copy(
words = item
)
}
}
}
}
ui
DisplayWordScreen
#Composable
fun DisplayScreen(
navController: NavController,
viewModel: DisplayScreenViewModel = hiltViewModel()) {
val state = viewModel.state.value
BaseBottomBar(navController = navController){
LazyColumn(modifier = Modifier.fillMaxSize()){
items(state.words?.size!!){index->
Item(word = state.words[index])
}
}
}
}
WordState
data class WordState(
val words : List<Word>? = emptyList()
)
I go to the DisplayScreen when I click identifier tag in first screen and I passed identifier first screen to Display screen and I fetch words data according to identifier in Display Screen ViewModel using this identifier which I passed from first screen.
ERROR
java.lang.NullPointerException
at com.example.almancasozluk.dictionaryfeature.ui.worddisplayscreen.DisplayScreenKt$DisplayScreen$1$1.invoke(DisplayScreen.kt:23)
at com.example.almancasozluk.dictionaryfeature.ui.worddisplayscreen.DisplayScreenKt$DisplayScreen$1$1.invoke(DisplayScreen.kt:22)

how can I convert my room entity to my data class in kotlin?

I have a data class that I pull from internet and I want to save room database but there is a problem like that.
It always gives an error like this, how can I overcome this problem?
my room entity class
#Entity(tableName = "ExchangeValues")
data class ExchangeEntity(
#ColumnInfo(name = "base_code") val base_code: String,
#ColumnInfo(name = "conversion_rates") val conversion_rates: ConversionRates,
#ColumnInfo(name = "result") val result: String,
#PrimaryKey(autoGenerate = true) val uid:Int?=null
)
my dao
#Dao
interface ExchangeDao {
#Query("SELECT * FROM ExchangeValues")
suspend fun getAll() : List<ExchangeEntity>
#Query("UPDATE ExchangeValues SET base_code=:base_code,conversion_rates=:conversion_rates , result=:result")
suspend fun update(base_code:String,conversion_rates:ConversionRates,result:String)
}
my exchange data class
#Serializable
data class Exchange(
val base_code: String,
val conversion_rates: ConversionRates,
val documentation: String,
val result: String,
val terms_of_use: String,
val time_last_update_unix: Int,
val time_last_update_utc: String,
val time_next_update_unix: Int,
val time_next_update_utc: String
) {
fun toEntity() = ExchangeEntity(
base_code = base_code,
conversion_rates = conversion_rates,
result = result
)
}
#Serializable
data class ConversionRates(
val conversionRates : Map<String,Double>
)
I cant use toEntity function in getAll()
exchangeRepositoryImpl
class ExchangeRepositoryImpl #Inject constructor(
private val dao:ExchangeDao
) : ExchangeRepository{
override suspend fun getAll() : Flow<List<Exchange>> {
return flow {
emit(dao.getAll())
}
}
override suspend fun update(exchange: Exchange) {
dao.update(exchange.base_code,exchange.result,exchange.conversion_rates)
}
}
my exchange converter
class ExchangeConverter {
#TypeConverter
fun fromSource(conversionRates: ConversionRates) : String{
val gson = Gson()
return gson.toJson(conversionRates)
}
#TypeConverter
fun toSource(json: String): ConversionRates {
val gson = Gson()
val typeToken = object : TypeToken<List<ConversionRates>>() {}.type
return Gson().fromJson(json, typeToken)
}
}
I wrote a converter like this, but it might not be correct, I'm not so sure. How can I solve this problem?
Inside flow you have created call map function the call to toEntity() eg
flow{
emit (dao.getAll().map{it.toEntity()})
}
Well your flow returns a flow of
List<Exchange>
and your repo returns
List<ExchangeEntity>
and there's nothing in your code to map an ExchangeEntity to an Exchange.
So you need something like:
override suspend fun getAll() : Flow<List<Exchange>> {
return flow {
emit(dao.getAll().map{Exchange(base_code = it.baseCode)})// add in other fields on exchange constructor
}
}

Spring data mongo using CoroutineCrudRepository

I am trying to use new Spring data reactive approach using CoroutineCrudRepository in a Kotlin project.
Github repository link: https://github.com/cristianprofile/spring-data-CoroutineCrudRepository-test
This is my application's code:
#SpringBootApplication
class DemoApplication
data class PersonDto(val age: Int, val name : String)
#RestController
#RequestMapping("/persons")
class PersonController (val personService: PersonService)
{
#GetMapping
suspend fun getPersons() = personService.getAll()
#PostMapping
suspend fun savePerson(#RequestBody personDto: PersonDto) = personService.save(personDto)
}
interface PersonService {
suspend fun getAll(): Flow<Person>
suspend fun getById(id: String): Person?
suspend fun save(personDto: PersonDto): Person
}
#Service
class PersonServiceImpl (private val personRepository: PersonRepository ) : PersonService
{
override suspend fun getAll(): Flow<Person> {
return personRepository.findAll()
}
override suspend fun getById(id: String): Person? {
return personRepository.findById(id)
}
override suspend fun save(personDto: PersonDto): Person
{
return personRepository.save(personDto.convertToPerson())
}
}
fun PersonDto.convertToPerson() =
Person(
name = name,
age = age
)
#Document
data class Person (#Id val id: String? = null, val age: Int, val name : String)
#Repository
interface PersonRepository: CoroutineCrudRepository<Person, String>
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
When I try to start my Spring boot project
Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.lang.Object org.springframework.data.repository.kotlin.CoroutineCrudRepository.count(kotlin.coroutines.Continuation); Reason: No property 'count' found for type 'Person'; nested exception is org.springframework.data.mapping.PropertyReferenceException: No property 'count' found for type 'Person'
at org.springframework.data.repository.query.QueryCreationException.create(QueryCreationException.java:101) ~[spring-data-commons-2.7.3.jar:2.7.3]
I don't know why "No property 'count' found for type" error is thrown. Which would be the way to be able to use CoroutineCrudRepository using Spring data mongo repository?

Room cannot verify the data integrity. Looks like you've changed schema.... What's wrong?

I am using Room in my app with two entities. The whole implementation is below.
The Problem is, the given scheme is fixed, which means I do not change anything regarding DB. When I provide a new version of my app to Users over Google Play Console, I get the following issue in Cryshlytics although I did not change anything for DB, just edited UI or another things, which definetly nothing have to do with DB:
Fatal Exception: java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
at androidx.room.RoomOpenHelper.checkIdentity(RoomOpenHelper.java:154)
at androidx.room.RoomOpenHelper.onOpen(RoomOpenHelper.java:135)
.......
Now I am not sure if I change the version of DB, it would work. What is wrong here?
BTW the DB is called from a Fragment like this
val mainDb: MainRepository by lazy { MainRepository(requireContext()) }
val stateDb: StateRepository by lazy { StateRepository(requireContext()) }
What's wrong here?
AppDatabase:
#Database(entities = [Main::class, State::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract val mainDao: MainDao
abstract val stateDao: StateDao
companion object {
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase? =
INSTANCE ?: synchronized(AppDatabase::class) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
MY_DB
).allowMainThreadQueries()
.build()
return INSTANCE
}
}
}
Dao:
#Dao
interface StateDao {
#Query("SELECT * FROM $STATE")
fun getAll(): List<State>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(state: State)
#Update
fun update(state: State)
#Query("DELETE FROM $STATE")
fun drop()
}
#Dao
interface MainDao {
#Query("SELECT * FROM $MAIN")
fun getAll(): List<Main>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(main: Main)
#Update
fun update(main: Main)
#Query("DELETE FROM $MAIN")
fun drop()
}
Main:
#Entity(tableName = MAIN)
data class Main(
#PrimaryKey #ColumnInfo(name = NUMBER) val number: Int,
#ColumnInfo(name = CARD) val car: String? = EMPTY,
#ColumnInfo(name = MODEL) val model: String? = EMPTY
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString(),
parcel.readString()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(number)
parcel.writeString(car)
parcel.writeString(model)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Main> {
override fun createFromParcel(parcel: Parcel): Main {
return Main(parcel)
}
override fun newArray(size: Int): Array<Main?> {
return arrayOfNulls(size)
}
}
}
State:
#Entity(tableName = STATE)
data class State(
#PrimaryKey #ColumnInfo(name = NUMBER) val number: Int,
#ColumnInfo(name = STATE) val state: String? = EMPTY
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(number)
parcel.writeString(question)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<State> {
override fun createFromParcel(parcel: Parcel): State {
return State(parcel)
}
override fun newArray(size: Int): Array<State?> {
return arrayOfNulls(size)
}
}
}
Repository:
class MainRepository(context: Context) {
private val mainDao = AppDatabase.getInstance(context)?.mainDao
fun getAll(): List<Main>? {
return mainDao?.getAll()
}
fun insert(main: Main) {
AsyncInsert(mainDao).execute(main)
}
fun update(main: Main) {
mainDao?.update(main)
}
fun drop() {
mainDao?.drop()
}
private class AsyncInsert(private val dao: MainDao?) : AsyncTask<Main, Void, Void>() {
override fun doInBackground(vararg p0: Main?): Void? {
p0[0]?.let { dao?.insert(it) }
return null
}
}
}
class StateRepository(context: Context) {
private val stateDao = AppDatabase.getInstance(context)?.stateDao
fun drop() {
stateDao?.drop()
}
fun getAll(): List<State>? {
return stateDao?.getAll()
}
fun insert(state: State) {
AsyncInsert(stateDao).execute(state)
}
fun update(state: State) {
stateDao?.update(state)
}
private class AsyncInsert(private val dao: StateDao?) : AsyncTask<State, Void, Void>() {
override fun doInBackground(vararg p0: State?): Void? {
p0[0]?.let { dao?.insert(it) }
return null
}
}
}
Now I am not sure if I change the version of DB, it would work. What is wrong here?
Changing the version would probably not work as the schema, as far as Room is concerned, has changed.
There is either a bug or the schema has been changed.
However, changing the version, would, with a Migration that does nothing (so as to not get a "no migration specified" error), then fail but importantly with an expected (what Room expects the schema to be according to the Entities) found (the schema that exists) discrepancy. This, if there is no bug, could then be used to ascertain what has been changed.

Dagger 2 with ViewModel, Repository, Room and Coroutines

I' trying to utilize Dagger 2 in a ViewModel + Respository + Room + Retrofit + Coroutines project written in Kotlin.
Currently each ViewModel initializes required repositories and their dependences by itself like so
class HomeViewModel(
application: Application
) : AndroidViewModel(application) {
private val repository: UserRepository =
UserRepository(
Webservice.create(),
AppDatabase.getDatabase(application, viewModelScope).userDao()
)
I would like to get this simplified to this
class HomeViewModel #Inject constructor(
private val repository: UserRepository
): ViewModel()
What I have achieved so far
Created the dagger component and modules
#Singleton
#Component(modules = [
AppModule::class,
NetworkModule::class,
DataModule::class,
RepositoryModule::class
])
interface AppComponent {
val webservice: Webservice
val userRepository: UserRepository
}
#Module
class AppModule(private val app: Application) {
#Provides
#Singleton
fun provideApplication(): Application = app
}
#Module
class DataModule {
#Provides
#Singleton
fun provideApplicationDatabase(app: Application, scope: CoroutineScope) =
AppDatabase.getDatabase(app, scope)
#Provides
#Singleton
fun provideUserDao(db: AppDatabase) = db.userDao()
}
#Module
class NetworkModule {
#Provides
#Singleton
fun provideWebservice() = Webservice.create()
}
#Module
class RepositoryModule {
#Provides
#Singleton
fun provideUserRepository(webservice: Webservice, userDao: UserDao) =
UserRepository(webservice, userDao)
}
Got the AppComponent initilized in the application class
class App : Application() {
companion object {
lateinit var appComponent: AppComponent
}
override fun onCreate() {
super.onCreate()
appComponent = initDagger(this)
}
private fun initDagger(app: App): AppComponent =
DaggerAppComponent.builder()
.appModule(AppModule(app))
.build()
}
And now I'm stuck.
The first question is: How do I make the ViewModel's inject constructor work?
Originally it was initialized from the HomeFragment like so
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
How do I call the initializer now?
The second question is a bit harder.
The database constructor requies a Coroutine scope in order to prepopulate it in a background thread during creation. How do I pass in a scope now?
Here is the definition of the database and the callback
#Database(
entities = [User::class],
version = 1, exportSchema = false
)
#TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
#Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context, scope: CoroutineScope): AppDatabase {
val tempInstance =
INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"database"
)
.fallbackToDestructiveMigration()
.addCallback(AppDatabaseCallback(scope))
.build()
INSTANCE = instance
return instance
}
}
}
private class AppDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { database ->
scope.launch(Dispatchers.IO) {
//inserts
}
}
}
}
}
The second question is a bit harder.
The database constructor requies a Coroutine scope in order to prepopulate it in a background thread during creation. How do I pass in a scope now?
It's actually easier, don't pass in a CoroutineScope, use the GlobalScope for this operation.
The first question is: How do I make the ViewModel's inject constructor work?
You need to obtain the Provider<HomeViewModel> from Dagger, then invoke it inside a ViewModelProvider.Factory to create the instance of HomeViewModel via the provider registered in the Dagger component.
Alternately, if the Activity has its own subcomponent, then you can use #BindsInstance to get the Activity instance into the graph, then move ViewModelProviders.of(activity).get(HomeViewModel::class.java, object: ViewModelProvider.Factory {
...
return homeViewModelProvider.get() as T
...
}) into a module of that subcomponent. Then, from that subcomponent, it would be possible to obtain an actual instance of the HomeViewModel, and still obtain proper scoping + onCleared() callback.
You don't need to pass a coroutine scope just run a coroutine in IO dispacher like:
#Database(
entities = [
Login::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun loginDao(): LoginDao
companion object {
#Volatile private var INSTANCE: AppDatabase? = null
fun getInstance(app: Application): AppDatabase = INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(app).also { INSTANCE = it }
}
private fun buildDatabase(app: Application) =
Room.databaseBuilder(app,
AppDatabase::class.java,
"daily_accountant")
// prepopulate the database after onCreate was called
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
// Do database operations through coroutine or any background thread
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught during database creation --> $exception")
}
CoroutineScope(Dispatchers.IO).launch(handler) {
// Pre-populate database operations
}
}
})
.build()
}
}
And remove coroutineScope from from function parameter.