Sorry if my title did not match to what my questions is.
I have created a Android Library, in which I have a Room database, As there should be only one instance of Room database, I have OfflineDatabaseManager getInstance method which provides the instance to the Android project which accesses it by passing the context. I have context within the Android project and I can pass it.
I want to listen to changes happening on the database table within the library so I can do something with it, I have written a class OfflineDataChangeListener within the library but to get the instance of the database I need to pass the context, how can I do that within the library please.
Library - OfflineDatabaseManager
class OfflineDatabaseManager private constructor(private val dp: LibraryDatabase) {
fun getOfflineData() : Flow<List<OfflineData>> {
return dp.getOfflineDataDao().getOfflineData()
}
suspend fun insertOfflineData(offlineData: OfflineData) {
dp.getOfflineDataDao().insertOfflineData(offlineData)
}
companion object {
#Volatile
private var INSTANCE: OfflineDatabaseManager? = null
fun getInstance(context: Context): OfflineDatabaseManager {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: run {
val db = Room.databaseBuilder(
context,
LibraryDatabase::class.java, "database-name"
).build()
OfflineDatabaseManager(db).also { INSTANCE = it }
}
}
}
}
}
Library - OfflineDataChangeListener - HOW CAN I PASS CONTEXT TO GET THE INSTANCE OF DB
class OfflineDataChangeListener: CoroutineScope {
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
fun observeOfflineDataChanges() {
launch {
OfflineDatabaseManager.getInstance(HOW TO GET CONTEXT HERE).getOfflineData().collect {
Log.d("dbChangeListener", "I am listening to databas echanges")
}
}
}
}
Android project -
Within my android project this is how I access and listen to changes
fun getOfflineData() {
launch {
OfflineDatabaseManager.getInstance(app.applicationContext).getOfflineData().collect {
Timber.d( "observing offline data" + it.toString())
}
}
}
I want to do the same but within the library.
Thanks
R
You can provide some function to the clients which passes the Context to your library. In your library create an object class:
object Library {
lateinit var context: Context
fun init(ctx: Context) {
context = ctx.applicationContext
}
}
Clients must call your init() function, for example, in their Application's onCreate() method:
Library.init(this)
And in the library you can access to it like this:
OfflineDatabaseManager.getInstance(Library.context)
Related
I'm a beginner to Android development.
When making a singleton in the Application Context, here is my code.
I pass the application context to the instantiation
class Blah{
companion object {
#Volatile
private var INSTANCE: Blah? = null
//Singleton
fun getInstance(applicationContext: Context): Blah =
INSTANCE ?: synchronized(this) {
INSTANCE ?: Blah(applicationContext).also { INSTANCE = it }
}
}
}
Can I pass the application directly to the instantiation? Like so:
class Blah{
companion object {
#Volatile
private var INSTANCE: Blah? = null
//Singleton
fun getInstance(application: Application): Blah =
INSTANCE ?: synchronized(this) {
INSTANCE ?: Blah(application).also { INSTANCE = it }
}
}
}
Will this present memory leaks?
Application is also a singleton. This doesn't cause a memory leak because there is only one instance of Application and when your app is running you have one and when your app isn't running there is nothing there, so go for it.
I am trying to provide different databases based on their logged in User ID, so that each user can have its own database, should you login with a different account. I don't want to provide an "owner" field, I tried it and didn't liked it.
The problem right now is, that the Database is scoped to the Application Scope and is therefore not recreated once the user logs out and another one in. Only when restarting the app the correct database gets created.
How can I achieve such behaviour using Hilt?
This is how it looks right now:
#InstallIn(SingletonComponent::class)
#Module
object ModuleDatabase {
#Provides
#Singleton
fun provideAppDatabase(
#ApplicationContext context: Context,
userManager: UserManager,
): MyRoomDatabase {
return Room.databaseBuilder(context, MyRoomDatabase::class.java, LOCAL_DATABASE_NAME + "_${userManager.loggedInUid()}")
.addMigrations(...)
.fallbackToDestructiveMigration()
.build()
}
}
It turns out that I need to create a custom Hilt Component that meets the requirements:
https://dagger.dev/hilt/custom-components.html
https://medium.com/androiddevelopers/hilt-adding-components-to-the-hierarchy-96f207d6d92d
(keep in mind that this will not work with WorkManager)
UPDATE: It actually works by avoiding Hilt Singleton scoping and just build it yourself. This also works with WorkManager because WorkManager can access dependencies inside SingletonComponents.
It basically works like this:
#InstallIn(SingletonComponent::class)
#Module
object ModuleDatabase {
private var databaseName: String by Delegates.observable(LOCAL_DATABASE_NAME) { _, oldValue, newValue ->
if (oldValue != newValue) {
databaseConnection?.let {
if (it.isOpen) {
it.close()
}
databaseConnection = null
// new connection opens with new name
}
}
}
private var databaseConnection: TemporyRoomDatabase? = null
private fun buildDatabase(context: Context, name: String): TemporyRoomDatabase {
Timber.e("Building database $databaseName")
return Room.databaseBuilder(context, TemporyRoomDatabase::class.java, name)
.addMigrations(*TemporyRoomDatabase_Migrations.build())
.fallbackToDestructiveMigration()
.build()
}
private fun getDatabaseConnection(context: Context, newDatabaseName: String): TemporyRoomDatabase {
return if (databaseName != newDatabaseName) {
databaseName = newDatabaseName
buildDatabase(context, databaseName).also {
databaseConnection = it
}
} else {
// use existing connection
Timber.e("Reusing database $databaseName")
databaseConnection ?: buildDatabase(context, databaseName)
}
}
#Provides
fun provideAppDatabase(
#ApplicationContext context: Context,
userManager: UserManager,
): TemporyRoomDatabase {
return getDatabaseConnection(context, LOCAL_DATABASE_NAME + "_${userManager.loggedInUidOrNull()}")
}
}
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)
Disclaimer: This is not actual code from any app, but an example of the flow and my current understanding on how best to do this. I am looking for help improving upon or what I am doing wrong.
I am trying to figure out the best way to structure an android application using the new jetpack viewModels, realm, and coroutines. I put together a gist of the flow that I have so far, and would love some feedback on how I can improve, what I could change, or what I am doing wrong. Ideally with examples or direct changes to my code.
It works as is, I am just not sure if I am using coroutines correctly or efficiently, and if there is a better way to structure the DAO's so that Realm can be injected for better testability. Someone has already mentioned changing the DAO to extend the LiveData<>, and using onActive() and onInactive() for posting the object. Is that a good idea?
// About Model is the model used by Realm. These models contains realm specific types, like RealmList
open class AboutModel(
var name: String = "",
#PrimaryKey
var version: String = ""
): RealmObject() {
/**
* Conversion function, to convert the view model layer object to the data layer object
*/
companion object {
fun from(about: About): AboutModel = AboutModel(about.name, about.version)
}
fun toObject(): About =
About(
this.name,
this.version
)
}
// About class used everywhere outside of the data/realm layer.
// Lines up with the AboutModel class, but free of realm or any other database specific types.
// This way, realm objects are not being referenced anywhere else. In case I ever need to
// replace realm for something else.
class About (val name: String = "Test", val version: String = "1.0.0") {
override fun toString(): String {
return "author is : $name, version is: $version"
}
}
// Couldn't inject the realm instance because its thread would not match with a suspend function.
// Even if both where background threads. Would be better if I could inject it, but couldn't get
// that to work.
class AboutDao() {
private val _about = MutableLiveData<About>()
init {
val realm = Realm.getDefaultInstance()
val aboutModel = realm.where(AboutModel::class.java).findFirst()
_about.postValue(aboutModel?.toObject() ?: About())
realm.close()
}
suspend fun setAbout(about: About) = withContext(Dispatchers.IO) {
val realm: Realm = Realm.getDefaultInstance()
realm.executeTransaction {
realm.copyToRealmOrUpdate(AboutModel.from(about))
_about.postValue(about)
}
realm.close()
}
fun getAbout() = _about as LiveData<About>
}
// Database is a singleton instance, so there is only ever one instance of the DAO classes
class Database private constructor() {
var aboutDao = AboutDao()
private set
companion object {
// #Volatile - Writes to this property are immediately visible to other threads
#Volatile private var instance: Database? = null
suspend fun getInstance() = withContext(Dispatchers.IO) {
return#withContext instance ?: synchronized(this) {
instance ?: Database().also { instance = it }
}
}
}
}
// Repo maintains the dao access. Is also setup to run as a singleton
class AboutRepo private constructor(private val aboutDao: AboutDao){
// This may seem redundant.
// Imagine a code which also updates and checks the backend.
suspend fun set(about: About) {
aboutDao.setAbout(about)
}
suspend fun getAbout() = aboutDao.getAbout()
companion object {
// Singleton instantiation you already know and love
#Volatile private var instance: AboutRepo? = null
fun getInstance(aboutDao: AboutDao) =
instance ?: synchronized(this) {
instance ?: AboutRepo(aboutDao).also { instance = it }
}
}
}
// Injector is used to help keep the injection in a single place for the fragments and activities.
object Injector {
// This will be called from About Fragment
suspend fun provideAboutViewModelFactory(): AboutViewModelFactory = withContext(Dispatchers.Default) {
AboutViewModelFactory(getAboutRepo())
}
private suspend fun getAboutRepo() = withContext(Dispatchers.IO) {
AboutRepo.getInstance(Database.getInstance().aboutDao)
}
}
// AboutViewModel's Factory. I found this code online, as a helper for injecting into the viewModel's factory.
class AboutViewModelFactory (private val aboutRepo: AboutRepo)
: ViewModelProvider.NewInstanceFactory() {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return AboutViewModel(aboutRepo) as T
}
}
// About Fragments ViewModel
class AboutViewModel(private val aboutRepo: AboutRepo) : ViewModel() {
suspend fun getAbout() = aboutRepo.getAbout()
suspend fun setAbout(about: About) = aboutRepo.set(about)
}
// Fragment's onActivityCreated, I set the viewModel and observe the model from the view model for changes
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
lifecycleScope.launch {
viewModel = ViewModelProviders.of(
this#AboutFragment,
Injector.provideAboutViewModelFactory()
).get(AboutViewModel::class.java)
withContext(Dispatchers.Main) {
viewModel.getAbout().observe(viewLifecycleOwner, Observer { about ->
version_number.text = about?.version
})
}
}
}
This question already has answers here:
Singleton with parameter in Kotlin
(14 answers)
Closed 2 years ago.
The Kotlin reference says that I can create a singleton using the object keyword like so:
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
//
}
}
However, I would like to pass an argument to that object. For example an ApplicationContext in an Android project.
Is there a way to do this?
Since objects do not have constructors what I have done the following to inject the values on an initial setup. You can call the function whatever you want and it can be called at any time to modify the value (or reconstruct the singleton based on your needs).
object Singleton {
private var myData: String = ""
fun init(data: String) {
myData = data
}
fun singletonDemo() {
System.out.println("Singleton Data: ${myData}")
}
}
Kotlin has a feature called Operator overloading, letting you pass arguments directly to an object.
object DataProviderManager {
fun registerDataProvider(provider: String) {
//
}
operator fun invoke(context: ApplicationContext): DataProviderManager {
//...
return this
}
}
//...
val myManager: DataProviderManager = DataProviderManager(someContext)
With most of the existing answers it's possible to access the class members without having initialized the singleton first. Here's a thread-safe sample that ensures that a single instance is created before accessing any of its members.
class MySingleton private constructor(private val param: String) {
companion object {
#Volatile
private var INSTANCE: MySingleton? = null
#Synchronized
fun getInstance(param: String): MySingleton = INSTANCE ?: MySingleton(param).also { INSTANCE = it }
}
fun printParam() {
print("Param: $param")
}
}
Usage:
MySingleton.getInstance("something").printParam()
There are also two native Kotlin injection libraries that are quite easy to use, and have other forms of singletons including per thread, key based, etc. Not sure if is in context of your question, but here are links to both:
Injekt (mine, I'm the author): https://github.com/kohesive/injekt
Kodein (similar to Injekt): https://github.com/SalomonBrys/Kodein
Typically in Android people are using a library like this, or Dagger, et al to accomplish parameterizing singletons, scoping them, etc.
I recommend that you use this form to pass arguments in a singleton in Kotlin debit that the object your constructor is deprived and blocked:
object Singleton {
fun instance(context: Context): Singleton {
return this
}
fun SaveData() {}
}
and you call it this way in the activity
Singleton.instance(this).SaveData()
If you looking for a base SingletonHolder class with more than one argument. I had created the SingletonHolder generic class, which supports to create only one instance of the singleton class with one argument, two arguments, and three arguments.
link Github of the base class here
Non-argument (default of Kotlin):
object AppRepository
One argument (from an example code in the above link):
class AppRepository private constructor(private val db: Database) {
companion object : SingleArgSingletonHolder<AppRepository, Database>(::AppRepository)
}
// Use
val appRepository = AppRepository.getInstance(db)
Two arguments:
class AppRepository private constructor(private val db: Database, private val apiService: ApiService) {
companion object : PairArgsSingletonHolder<AppRepository, Database, ApiService>(::AppRepository)
}
// Use
val appRepository = AppRepository.getInstance(db, apiService)
Three arguments:
class AppRepository private constructor(
private val db: Database,
private val apiService: ApiService,
private val storage : Storage
) {
companion object : TripleArgsSingletonHolder<AppRepository, Database, ApiService, Storage>(::AppRepository)
}
// Use
val appRepository = AppRepository.getInstance(db, apiService, storage)
More than 3 arguments:
To implement this case, I suggest creating a config object to pass to the singleton constructor.