Get Room object via companion using reflection and class name - android

As the title stands, I want to get a singleton instance via reflection using the class name. Let's say I have this class:
abstract class MyRoomDatabase : RoomDatabase() {
abstract fun exampleEntityDao() : ExampleEntityDao
companion object {
#Volatile
private var INSTANCE: MyClass? = null
fun getDatabase(context: Context): MyClass {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(context.applicationContext, MyRoomDatabase::class.java, "db").build()
INSTANCE = instance
instance
}
}
}
}
Now, knowing the class name val strClass = MyRoomDatabase::class.qualifiedName.toString()
I would like to achieve:
MyRoomDatabase.getDatabase(this).getExampleEntityDao().doSomeStuff() but via reflection.
I tried this:
val strClass = MyRoomDatabase::class.qualifiedName.toString()
val classFromStr = Class.forName(strClass)
val companion = classFromStr::class.companionObject
in that case, companion is null.
In the other case this: val companion = MyRoomDatabase::class.companionObject gives me the actual companion but I cannot do that because in the place where it should be done the compiler does know only the class name.

classFromStr::class will return the class of classFromStr which is Class and doesn't have a companion object.
val companion = classFromStr.kotlin.companionObject
should work.

Related

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
}
}
}
}

Call Application context in Activity

I'm trying to call my database (made with Room) from an activity on Android, but it needs the application context, and if I passed "application : Application" in the constructor of my Activity, the build crash and tell me :
java.lang.Class<com.exemple.instabus.PhotoActivity> has no zero argument constructor
Here is my code :
class PhotoActivity(application: Application) : AppCompatActivity() {
private val pictureDao = PictureDatabase.getDatabase(app)
//Some code ....
I need a context, i've tried to pass "this" but i got another error
Can someone give me some help please, I'm a beginner in this technology
EDIT:
Here is my database class, just to show you why I need an Application Context
#Database(entities = [Picture::class], version = 1, exportSchema = false)
abstract class PictureDatabase : RoomDatabase(){
abstract fun pictureDao() : PictureDao
companion object{
#Volatile
private var INSTANCE : PictureDatabase? = null
fun getDatabase(context: Context): PictureDatabase {
if(INSTANCE == null){
synchronized(this){
INSTANCE = Room.databaseBuilder(
context.applicationContext,
PictureDatabase::class.java,
"pictures.db"
).build()
}
}
return INSTANCE!!
}
}
}
Activity is something we do declare in the manifest and then start them using intent, However, the creation of an instance of an activity is done by the system and not by us. An instance is created using constructor, but if it is us then we can have any number of overloaded constructors. But the system needs only one constructor which should be a zero parameter constructor and it should be public.
So your activity signature
class PhotoActivity(application: Application) : AppCompatActivity() {
should be changed to
class PhotoActivity() : AppCompatActivity() {
To call the fun getDatabase(context: Context): PictureDatabase you can pass this from the activity. Activity is an indirect child of Context.
You can do it in the following ways,
private val pictureDao by lazy{ PictureDatabase.getDatabase(this) }
private lateinit var pictureDao:PictureDatabase
then in onCreate() initialize it
final override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.your_layout)
PictureDatabase.getDatabase(this)
//your logic goes here
}
First create a ViewModel, and inside the ViewModel you can access the your Dao, pictureDao.
class PictureViewModel(application: Application) :
AndroidViewModel(application) {
val pictureDao: PictureDao
init {
pictureDao =
PictureDatabase.getDatabase(application).pictureDao()
}
}
Then in your activity, initialize the ViewModel. And access your Dao.
class PhotoActivity : AppCompatActivity() {
private lateinit var pictureViewModel: PictureViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_photo)
pictureViewModel =
ViewModelProviders.of(this).get(PictureViewModel::class.java)
//You can now access your Dao class here:
val pictureDao = pictureViewModel.pictureDao
}
}
You should not pass Application to the constructor. You should pass applicationContext to getDatabase(), like private val pictureDao = PictureDatabase.getDatabase(applicationContext)

Room doesn't detect #Typeconverters

This is my only Entity:
#Entity(tableName = "sticker_packs")
class StickerPack(
#PrimaryKey(autoGenerate = true)
val identifier: Int = 0,
var name: String,
var publisher: String,
#TypeConverters(StickerConverter::class)
private var stickers: List<Sticker> = emptyList())
This is my custom Sticker class:
data class Sticker(
val emojis: List<String>,
var imageDir: String,
var size: Long = 0L)
This is my Database class:
#Database(entities = [StickerPack::class], version = 1)
//I get this same error with or without my Typeconverters here
#TypeConverters(StickerConverter::class)
abstract class StickerPacksDatabase: RoomDatabase() {
abstract val stickerPacksDao: StickerPacksDao
companion object{
#Volatile
private var INSTANCE: StickerPacksDatabase? = null
fun getInstance(context: Context): StickerPacksDatabase{
synchronized(this){
var instance = INSTANCE
if(instance == null){
instance =
Room.databaseBuilder(context.applicationContext, StickerPacksDatabase::class.java, "sticker_packs_database")
.build()
INSTANCE = instance
}
return instance
}
}
}
}
And this is my typeconverter
class StickerConverter {
#TypeConverter
fun stickersToJson(stickers: List<Sticker>): String = Gson().toJson(stickers)
#TypeConverter
fun jsonToStickers(json: String): List<Sticker> = Gson().fromJson(json)
private inline fun <reified T> Gson.fromJson(json: String): T = fromJson(json, object: TypeToken<T>() {}.type)}
I'm not trying to do anything fancy just converting a list into a string with Gson, yet when trying to build the project I get the following error:
Cannot figure out how to save this field into database. You can consider adding a type converter for it.
But that's exaclty what I'm trying to do. I may have made a mistake somewhere but I can't seem to find it. I tried moving the #TypeConverters notation everywhere posible and the problem persists
You need to add the #TypeConverters annotation to the AppDatabase class so that Room can use the converter that you've defined for each entity and DAO in that AppDatabase:
Database(entities = arrayOf(Sticker::class), version = 1)
#TypeConverters(StickerConverter::class)
abstract class AppDatabase : RoomDatabase{
}

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

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)

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