Unable to build application with Room in my test App - kotlin

When I try to build the app it gives me an error
Execution failed for task ':app:kaptDebugKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction
> java.lang.reflect.InvocationTargetException (no error message)
In build.gradle I specified
apply plugin: 'kotlin-kapt'
and
implementation "android.arch.persistence.room:runtime:1.1.1"
kapt 'android.arch.persistence.room:compiler:1.1.1'
My database class
#Database(entities = [WeatherOfCities::class], version = 1, exportSchema = false)
public abstract class AppDatabase : RoomDatabase(){
public abstract fun weatherOfCitiesDao(): WeatherOfCitiesDao
companion object {
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
if (INSTANCE == null) {
synchronized(this) {
INSTANCE =
Room.databaseBuilder(context, AppDatabase::class.java, "database")
.build()
}
}
return INSTANCE!!
}
}
}
My entity class
#Entity
data class WeatherOfCities (
#PrimaryKey(autoGenerate = true)
val id: Long,
val city: String,
val weather: Int
)
My Dao interface
#Dao
interface WeatherOfCitiesDao {
#Query("SELECT * FROM weatherOfCities")
fun getAll(): List<WeatherOfCities>
#Insert
fun insert(weatherOfCities: WeatherOfCities)
#Update
fun update(weatherOfCities: WeatherOfCities)
}
And Build db in MainActivity
class MainActivity : AppCompatActivity(), MainView {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var presenter = (application as MVPApplication).mainPresenter
presenter?.attachView(this)
var db = AppDatabase.getDatabase(this)
var weatherOfCitiesDao = db.weatherOfCitiesDao()
}
}
Why is the application not building and is it due to errors in the application code?

You need to add the ktx dependency e.g.
implementation 'androidx.room:room-ktx:1.1.1'
I'd also suggest using 2.4.1 for all the room dependencies rather than 1.1.1
So using 2.4.1, .allowMainThreadQueries() (in the AppDatabase before .build()
And
//var presenter = (application as MVPApplication).mainPresenter
//presenter?.attachView(this)
var db = AppDatabase.getDatabase(this)
var weatherOfCitiesDao = db.weatherOfCitiesDao()
weatherOfCitiesDao.insert(WeatherOfCities(0,"London",10))
weatherOfCitiesDao.insert(WeatherOfCities(0,"Paris",20))
weatherOfCitiesDao.update(WeatherOfCities(2,"New York", 30))
for(woc: WeatherOfCities in weatherOfCitiesDao.getAll()) {
Log.d("DBINFO","ID = ${woc.id} City is ${woc.city} Weather is ${woc.weather}")
}
The result written to the log is :-
D/DBINFO: ID = 1 City is London Weather is 10
D/DBINFO: ID = 2 City is New York Weather is 30
Via App Inspection :-

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)

Reading data from SQLCipher without Creating DBHelper Class; Error: Could not open database

I build two apps with the same package name:
In the first app, I use SQLiteCipher using "Pass_Phrase" (Version_1 DB) for CRUD operations using Java.
In the second app, I just wanted to read data (without making a DBHelper class) from SQLiteCipher and put it into Room (Version_2 DB) while Migrating using Kotlin.
Note: Changes would be affected on App Update.
I completed the first app and was stuck at sqliteCipherDataList() in the second app. Need your help. Thanks a lot!
Code: UserDB.kt
#Database(entities = [User::class], version = 2, exportSchema = false)
abstract class UserDB : RoomDatabase() {
abstract fun userDAO(): UserDAO
companion object {
private var instance: UserDB? = null
private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
val db = SQLiteDatabase.openOrCreateDatabase("User.db", passphrase, null)
db.rawExecSQL("ATTACH DATABASE '${encryptedDbPath}' AS encrypted KEY '${passphrase}'")
db.rawExecSQL("SELECT sqlcipher_export('encrypted')")
db.rawExecSQL("DETACH DATABASE encrypted")
db.close()
val version = database.version
Log.v("Old version", version.toString() + "")
}
}
// return userList
fun sqliteCipherDataList(): List<User> {
val userList: MutableList<User> = ArrayList()
val db = SQLiteDatabase.openDatabase(encryptedDbPath, passphrase, null, SQLiteDatabase.OPEN_READONLY)
val cursor: Cursor = db.query("Contacts", null, null, null, null, null, null)
if (cursor.moveToFirst()) {
do {
#SuppressLint("Range")
val emails = User(cursor.getString(cursor.getColumnIndex("Email")))
userList.add(emails)
} while (cursor.moveToNext())
}
cursor.close()
db.close()
return userList
}
fun getDatabase(context: Context): UserDB? {
if (instance == null) {
synchronized(this) {
Log.d("UserDB", "Creating Database")
instance = Room.databaseBuilder(
context.applicationContext,
UserDB::class.java, "RoomDB"
)
.addMigrations(MIGRATION_1_2)
.allowMainThreadQueries()
.build()
}
}
return instance!!
}
}
}
Code: MainAcitvity.kt
class MainActivity : AppCompatActivity() {
private var userDB: UserDB? = null
private var userList: List<User>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
SQLiteDatabase.loadLibs(this)
val roomTextView = findViewById<TextView>(R.id.roomText)
userList = UserDB.sqliteCipherDataList()
Log.d("SQLiteCipherData", userList.toString())
userDB = UserDB.getDatabase(this)
userDB?.userDAO()!!.insertProductListToRoom(userList)
val u: User = userDB?.userDAO()!!.getProductFromRoom()[0]
Log.v("RoomDBData", u.Email)
roomTextView.text = u.Email
}
}
Exception:
2022-07-22 16:24:21.085 18889-18889/com.example.sqlitecipherjava E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.sqlitecipherjava, PID: 18889
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.sqlitecipherjava/com.example.sqlitecipherjava.MainActivity}: net.sqlcipher.database.SQLiteDiskIOException: error code 10: Could not open database
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3114)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3257)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1948)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7050)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965)
Caused by: net.sqlcipher.database.SQLiteDiskIOException: error code 10: Could not open database
at net.sqlcipher.database.SQLiteDatabase.dbopen(Native Method)
at net.sqlcipher.database.SQLiteDatabase.openDatabaseInternal(SQLiteDatabase.java:2412)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1149)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1116)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1065)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1019)
at com.example.sqlitecipherjava.UserDB$Companion.sqliteCipherDataList(UserDB.kt:41)
at com.example.sqlitecipherjava.MainActivity.onCreate(MainActivity.kt:22)
at android.app.Activity.performCreate(Activity.java:7327)
at android.app.Activity.performCreate(Activity.java:7318)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3094)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3257) 
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1948) 
at android.os.Handler.dispatchMessage(Handler.java:106) 
at android.os.Looper.loop(Looper.java:214) 
at android.app.ActivityThread.main(ActivityThread.java:7050) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965) 

Observer pattern is not working in Android MVVM

I am trying to update my view according to my data in my ViewModel, using MVVM
I need in the method onCacheReception to update my map whenever zones is changing
ViewModel
class MainViewModel constructor(application: Application) : AndroidViewModel(application),
CacheListener {
private val instance = Initializer.getInstance(application.applicationContext)
private val _zones = MutableLiveData<List<Zone>>()
val zones: LiveData<List<Zone>>
get() = _zones
init {
CacheDispatcher.addCacheListener(this)
}
override fun onCacheReception() {
val zonesFromDB: List<Zone>? = instance.fetchZonesInDatabase()
_zones.value = zonesFromDB
}
}
MainActivity
class MainActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks, OnMapReadyCallback {
private val mainViewModel: MainViewModel = ViewModelProvider(this).get(MainViewModel(application)::class.java)
private lateinit var initializer: Initializer
private lateinit var map: GoogleMap
private val REQUEST_CODE_LOCATIONS: Int = 100
private val permissionLocationsRationale: String = "Permissions for Fine & Coarse Locations"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (checkForLocationsPermission()) {
setUp()
mapSetUp()
}
mainViewModel.zones.observe(this, Observer { zones ->
zones.forEach {
Log.i("YES DATA", "Data has been updated")
val latLng = it.lat?.let { it1 -> it.lng?.let { it2 -> LatLng(it1, it2) } }
val markerOptions = latLng?.let { it1 -> MarkerOptions().position(it1) }
map.addMarker(markerOptions)
}
})
}
My Log is never displaying and it doesn't seem while debugging that mainView.zones.observe { } is called when I receive some new data in my ViewModel
In the onCacheReception(), replace:
_zones.value = zonesFromDB
by:
_zones.postValue(zonesFromDB)
in case your onCacheReception() function is called from a worker thread.

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.

Android Room + Kotlin pattern

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)