Getting application context for getInstance function of database class (android kotlin) - android

I am writing an android application following the Android Architectural Components design.
This is the database class:
#Database(entities = [Authentication::class],version = 1, exportSchema = false)
abstract class AuthDB: RoomDatabase(){
abstract val authenticationDao :AuthenticationAccessObject
companion object{
#Volatile
private var INSTANCE: AuthDB? = null
fun getInstance(context: Context): AuthDB {
synchronized(this){
var instance = INSTANCE
if(instance == null){
instance = Room.databaseBuilder(
context.applicationContext,
AuthDB::class.java,
"authentication_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
}
return instance
}
}
}
}
This is the Repository class:
class Repository2() {
private val database: AuthDB = AuthDB.getInstance(context = getContext())
private val daoA = database.authenticationDao
//Function to register a new user to system
fun insertAuth(userData: Any){
if (userData is Authentication){
daoA.insertAuth(userData)
} else {
throw IllegalArgumentException()
}
}
My target is that when I write the ViewModel, I want to create instance of Repository2 and call functions for example as follows:
var repo = Repository2()
repo.insertAuth(authenticationObject)
I am having problem giving context to getInstance in the Repository. The context should be such that when I instantiate the repository, it should automatically get the application context and instantiate the AuthDB database.
Until now,
I have tried to create Application class that extends Application and tried to get application context from there as suggested in another stackoverflow solution
Instantiated database with following code and failed:
private val database: AuthDB = AuthDB.getInstance(context = getContext())
Instantiated database with following code and failed:
private val database: AuthDB = AuthDB.getInstance(Application.getApplicationContext())
I have been trying for about two days now and nothing is working, I believe I am missing a major concept here. I hope someone can nudge me in the right direction?
Kind regards,
Salik

try this solution
EDIT:-
use this way to your RoomDatabase
#Database(
entities = [CompaniesModel::class, CompaniesHomeModel::class, UserPoint::class, Image::class, Offer::class, Rewords::class, BranchModel::class, PointsModel::class, RedeemModel::class, MainData::class, SubData::class],
version = 15)
abstract class DataBase : RoomDatabase() {
abstract fun homeDao(): HomeDao
abstract fun menuDao(): MenuDao
abstract fun companiesDao(): CompaniesListDao
abstract fun branchesDao(): BranchesDao
companion object {
#Volatile
private var databaseInstance: DataBase? = null
fun getDatabaseInstance(mContext: Context): DataBase =
databaseInstance ?: synchronized(this) {
databaseInstance ?: buildDatabaseInstance(mContext).also {
databaseInstance = it
}
}
private fun buildDatabaseInstance(mContext: Context) =
Room.databaseBuilder(mContext, DataBase::class.java, "crm")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build()
}
}
and for the getContext(), use Application() class like this:
class App : Application() {
override fun onCreate() {
super.onCreate()
instance = this
}
companion object {
lateinit var instance: App
}
}
and pass it like this
private val database: AuthDB = AuthDB.getInstance(app.instance)

Related

Room.databaseBuilder not running in Android Jetpack

I'm learning how to implement Room from Android jetpack into my android studio app but a message saying "not enough information to infer type variable T" keeps showing up when I try to use Room.databaseBuilder
this is my code:
// if there is no instance, we create a new one
synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
UserDatabase::class.java,
"user_database"
).build()
INSTANCE = instance
return instance
}
I can add the surrounding function or the whole file if that helps. Thanks in advance!
I'm not sure but this is the problem i faced as well and i solved it by downgrading 'implementation 'androidx.core:core-ktx:1.6.0' to 'implementation 'androidx.core:core-ktx:1.5.0' in the build.gradle file
If you have for example:-
#Database(entities = [Objects::class], version = 1)
abstract class MyDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDAO
companion object {
var INSTANCE: MyDatabase? = null
fun getInstance(context: Context): MyDatabase {
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
UserDatabase::class.java,
"user_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
The you get the not enough information to infer type variable T e.g. :-
Changing to use the same class then all is fine, e.g.
#Database(entities = [Objects::class], version = 1)
abstract class UserDatabase: RoomDatabase() { //<<<<<<<<<< CHANGED
abstract fun getAllDao(): AllDAO
companion object {
var INSTANCE: UserDatabase? = null //<<<<<<<<<< CHANGED
fun getInstance(context: Context): UserDatabase { //<<<<<<<<<< CHANGED
if (INSTANCE == null) {
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
UserDatabase::class.java, //<<<<<<<<<< AS WAS/ MATCHES
"user_database"
)
.allowMainThreadQueries() /*<<<<<<<<<< ADDED FOR convenience brevity */
.build()
INSTANCE = instance
return instance
}
}
return INSTANCE!!
}
}
}
Note run on the main thread to just demonstrate that the above runs (see following example run)
As a test/proof consider :-
class MainActivity : AppCompatActivity() {
lateinit var db1: UserDatabase
lateinit var dao1: AllDAO
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db1 = UserDatabase.getInstance(this) //<<<<<<<<<< GET INSTANCE
dao1 = db1.getAllDao()
dao1.getObjects(listOf("nothing to get as no data has been added, just forcing open/create of database")) //<<<<<<<<< as per text USE/OPEN/CREATE Database(first use, once created database is opened as it persists)
// existing app used ....
The result (ignore TheDatabase as existing code was used for the answer) :-

Property delegate must have a 'getValue(InventoryApplication, KProperty<*>)' method. None of the following functions is suitable:

I stuck at this problem
class InventoryApplication : Application() {
val database: ItemDatabase by lazy { ItemDatabase.getDatabase(this) }
}
Error class
Code image
I had the same issue, i was able to resolve it by going to the ItemRoomDatabase.kt file, change the INSTANCE variable in the companion object, to return a non-null type ItemRoomDatabase? and the getDataBase() function to return a type ItemRoomDatabase, rather than a RoomDatabase return type as requested in the codelab example.
Your final code in the ItemRoomDatabase.kt file should look something like this:
#Database(entities = [Item::class], version = 1, exportSchema = false)
abstract class ItemRoomDatabase : RoomDatabase() {
abstract fun itemDao(): ItemDao
companion object {
#Volatile
private var INSTANCE: ItemRoomDatabase? = null
fun getDatabase(context: Context): ItemRoomDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
ItemRoomDatabase::class.java,
"item_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
instance
}
}
}
}

How to init and insert in Room database using Kotlin?

I'm new to kotlin and room, after following an official android guide i ended up by setting my entities, my DAO and my Database,
the issue is i can't understand on how can i use the function from dao in my fragment...
So my Database looks like this:
#Database(entities = [Articolo::class], version = 1, exportSchema = false)
abstract class ArticoliDatabase: RoomDatabase() {
abstract val articoliDao: ArticoliDAO
companion object {
#Volatile
private var INSTANCE: ArticoliDatabase? = null
fun getInstance(context: Context): ArticoliDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
ArticoliDatabase::class.java,
"pdt_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
}
return instance
}
}
}
}
Then in my fragment i've done the following:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
db = ArticoliDatabase.getInstance(requireContext())
}
And in the same fragment in my click function i'm doing the following to insert
db.articoliDao.insert(Articolo(barcode, qta))
But the app even doesn't build correctly by saying that
ArticoliDatabase_Impl does not exist
So what is the right way to initialize and use the room database with kotlin?
I just need to simply insert and show the data from the db in a listview that's it..
The problem is in the build.gradle file. Can you please check if your import of room library looks like:
annotationProcessor "androidx.room:room-compiler:$room"
Since you are using kotlin, you must use kapt for annotation processor dependencies
This is how your room dependencies should look like
implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room"
kapt "androidx.room:room-compiler:$room"
Create Database class AppDatabase and Dao Interface
AppDatabase
#Database(
entities = [User::class, Quote::class],
version = 1
)
abstract class AppDatabase : RoomDatabase() {
abstract fun getUserDao(): UserDao
abstract fun getQuoteDao(): QuoteDao
companion object {
#Volatile
private var instance: AppDatabase? = null
private val LOCK = Any()
operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
instance ?: buildDatabase(context).also {
instance = it
}
}
private fun buildDatabase(context: Context) =
Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"MyDatabase.db"
).build()
}
}
Dao Interface Here
#Dao
interface UserDao{
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(user: User) : Long
#Query("SELECT * FROM user WHERE uid = $CURRENT_USER_ID")
fun getuser() : LiveData<User>
}
Now in your fragment or activity you can insert and get user data by just craeting an object of dabase class as given below
private val db: AppDatabase
db=AppDatabase(context)
fun saveUser(user: User) = db.getUserDao().upsert(user)
fun getUser() = db.getUserDao().getuser()

mydatabase is not created in room database

hi i am writing my database in kotlin and want that room data base to be used in java class .But on running it ,it is showing error that myAppdatabase is not implemented.
this is my Database cls
#Database(entities = arrayOf(Feedback::class),version = 1,exportSchema = true)
abstract class MyDatabase : RoomDatabase(){
abstract fun dao(): Dao
companion object {
private var INSTANCE: MyDatabase? = null
fun getInstance(context: Context): MyDatabase? {
if (INSTANCE == null) {
synchronized(MyDatabase::class) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
MyDatabase::class.java, "feed.db").allowMainThreadQueries()
.build()
}
}
return INSTANCE
}
}
}
this is my java class
context=this.getActivity();
INSTANCE=MyDatabase.Companion.getInstance(context);
myDatabase= Room.databaseBuilder(this.getActivity().getApplicationContext(),MyDatabase.class, "feed.dp").fallbackToDestructiveMigration().build();

Using room as singleton in kotlin

I'm trying to use Room as singleton so I didn't have to invoke Room.databaseBuilder() -which is expensive- more than once.
#Database(entities = arrayOf(
Price::class,
StationOrder::class,
TicketPrice::class,
Train::class,
TrainCategory::class
), version = 2)
#TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun dao(): TrainDao
companion object {
fun createDatabase(context: Context): AppDatabase
= Room.databaseBuilder(context, AppDatabase::class.java, "trains.db").build()
}
}
Note:
Can't use Object because Room requires using abstract class.
singleton must be thread safe because multiple threads might access it at the same time.
must be able to take Context as an argument.
I have looked at all similar StackOverflow questions and none of them satisfy my requirements
Singleton with argument in Kotlin isn't thread-safe
Kotlin - Best way to convert Singleton DatabaseController in Android isn't thread-safe
Kotlin thread save native lazy singleton with parameter uses object
After some research, I found that I have two options.
Double-checked locking
Initialization-on-demand holder idiom
I considered implementing one of them, but this didn't felt right for Kotlin - too much boilerplate code.
After more research, I stumbled upon this great article which provides an excellent solution, which uses Double-checked locking but in an elegant way.
companion object : SingletonHolder<AppDatabase, Context>({
Room.databaseBuilder(it.applicationContext, AppDatabase::class.java, "train.db").build()
})
From the article:
A reusable Kotlin implementation:
We can encapsulate the logic to
lazily create and initialize a singleton with argument inside a
SingletonHolder class. In order to make that logic thread-safe, we
need to implement a synchronized algorithm and the most efficient
one — which is also the hardest to get right — is the double-checked
locking algorithm.
open class SingletonHolder<T, A>(creator: (A) -> T) {
private var creator: ((A) -> T)? = creator
#Volatile private var instance: T? = null
fun getInstance(arg: A): T {
val i = instance
if (i != null) {
return i
}
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = creator!!(arg)
instance = created
creator = null
created
}
}
}
}
Extra:
if you want Singleton with two arguments
open class SingletonHolder2<out T, in A, in B>(creator: (A, B) -> T) {
private var creator: ((A, B) -> T)? = creator
#Volatile private var instance: T? = null
fun getInstance(arg0: A, arg1: B): T {
val i = instance
if (i != null) return i
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = creator!!(arg0, arg1)
instance = created
creator = null
created
}
}
}
}
In this particular case I would resort to using Dagger 2, or some other dependency injection library like Koin or Toothpick. All three libraries allow to provide dependancies as singletons.
Here's the code for Dagger 2 module:
#Module
class AppModule constructor(private val context: Context) {
#Provides
#Singleton
fun providesDatabase(): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"train.db")
.build()
}
}
AppComponent:
#Singleton
#Component(modules = arrayOf(
AppModule::class
))
interface AppComponent {
fun inject(viewModel: YourViewModel)
fun inject(repository: YourRepository)
}
Application class to provide injection:
class App : Application() {
companion object {
private lateinit var appComponent: AppComponent
val component: AppComponent get() = appComponent
}
override fun onCreate() {
super.onCreate()
initializeDagger()
}
private fun initializeDagger() {
component = DaggerAppComponent.builder()
.appModule(AppModule(this))
.build()
}
}
And then inject your database as singleton to wherever you need it (for example in your app's repository):
#Inject lateinit var appDatabase: AppDatabase
init {
App.component.inject(this)
}
Used #Volatile for thread safety.
public abstract class AppDatabase : RoomDatabase() {
abstract fun trainDao(): trainDao
companion object {
#Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): Db = INSTANCE ?: synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase ::class.java,
"train-db"
).build()
INSTANCE = instance
instance
}
}
}
taken from : https://developer.android.com/codelabs/android-room-with-a-view-kotlin#7
You could make use of the Kotlin standard library's
fun <T> lazy(LazyThreadSafetyMode.SYNCHRONIZED, initializer: () -> T): Lazy<T>
companion object {
private lateinit var context: Context
private val database: AppDatabase by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
Room.databaseBuilder(context, AppDatabase::class.java, "trains.db").build()
}
fun getDatabase(context: Context): AppDatabase {
this.context = context.applicationContext
return database
}
}
Personally though, I would normally add ApplicationContext-dependent singletons inside the Application, e.g.
<!-- AndroidManifest.xml -->
<manifest>
<application android:name="MyApplication">
...
class MyApplication : Application() {
val database: AppDatabase by lazy {
Room.databaseBuilder(this, AppDatabase::class.java, "train.db").build()
}
}
You can even define an extension method for easy access as context.database.
val Context.database
get() =
generateSequence(applicationContext) {
(it as? ContextWrapper)?.baseContext
}.filterIsInstance<MyApplication>().first().database
Here's how i figured out...
#Database(entities = [MyEntity::class], version = dbVersion, exportSchema = true)
abstract class AppDB : RoomDatabase() {
// First create a companion object with getInstance method
companion object {
fun getInstance(context: Context): AppDB =
Room.databaseBuilder(context.applicationContext, AppDB::class.java, dbName).build()
}
abstract fun getMyEntityDao(): MyEntityDao
}
// This is the Singleton class that holds the AppDB instance
// which make the AppDB singleton indirectly
// Get the AppDB instance via AppDBProvider through out the app
object AppDBProvider {
private var AppDB: AppDB? = null
fun getInstance(context: Context): AppDB {
if (appDB == null) {
appDB = AppDB.getInstance(context)
}
return appDB!!
}
}
singleton in kotlin is real easy just do this
companion object {
#JvmStatic
val DATABASE_NAME = "DataBase"
#JvmField
val database = Room.databaseBuilder(App.context(), DataBase::class.java, DataBase.DATABASE_NAME).build()
}

Categories

Resources