Is Koin singleton creation sync or async? I need t...
# koin
p
Is Koin singleton creation sync or async? I need to load/save data from datastore when creating a Koin singleton because I need to access datastore:
Copy code
single<CustomDatabase> {
    coroutine.launch {
        //use datastore
    }
    Room.databaseBuilder(
        context,
        CustomDatabase::class.java, "database"
    ).createFromAsset("data.db").build()
}
How can I use corutines inside a singleton Koin creation? can I use runBlocking? will it block UI?
p
so for the moment, the solution is to do the suspend functions in a runBlocking{}
will that block the UI ?
f
Yes, it will block the UI
m
I hold a
Deferred
instead and reference it using a
TypeQualifier
l
I dont recommend doing I/O upon creating of objects. Seems like code smell to me
p
@Leo N I don't like it, but then, how whould you check the cached database version before deciding if deleting/repopulating it? take a look here: https://kotlinlang.slack.com/archives/C09222272/p1742238346846899
l
@Pablo Is it important to always be very up-to-date? Can you first load using the old data and then refresh in the background? If data must be fresh, perhaps you can do it after app starts and show an indicator/spinner and call some refresh() method?
p
the data must be updated before the first screen is shown
because the first screen display all the data on the screen
also, I'm using Koin, which initializes the database as a singleton when it's firstly required
l
Initializing is fine, but doing I/O operations here is not a good idea. Perhaps you should add a loading animation just before your first screen and call a loading method
p
the repository is being injected into the viewmodel of the first screen, so I suposse koin is doing all the job before the first screen is displayed, so I don't have the possibility to show a loading element
as far as I know, when you use Koin, it works like that, when it detects that it's required in a constructor, initializes the object for the first and las time, as it is a singleton
so the job is done before the screen is displayed
m
Why not just stick that code in a
CustomDatabaseFactory
class provided via koin? Expose whatever using suspending funs.
p
not sure about what are you proposing
the idea is to make the project the more simple as possible, without complex snippets to make simple things like injecting or initializing repos
l
You can initialize the objects via DI thats fine. But, don't make I/O calls with it in the constructor.
p
yes, but then I can't display a loading element
as I told you previously, because they are being initialized with koin before the screen is displayed
as far as I know
so what to do in this case?
l
Please explain what you mean when you say "they are initialized". because ideally these things should be done like this: 1. Inject DB accessor (Room, JDBC,...) and don't do any I/O 2. In your screens, at the start start doing I/O and show some loading indicator
p
the repository is being injected into the viewmodel of the first screen, so I suposse koin is doing all the job before the first screen is displayed, so I don't have the possibility to show a loading element, as far as I know, when you use Koin, it works like that, when it detects that it's required in a constructor, initializes the object for the first and las time, as it is a singleton, so the job is done before the screen is displayed
l
I am aware of Koin, but this is the problem in your flow "doing all the job before the first screen is displayed". It should not do this. You are doing I/O in your initialization, which is a bad approach
p
but then, how whould you check the cached database version before deciding if deleting/repopulating it? take a look here: https://kotlinlang.slack.com/archives/C09222272/p1742238346846899
the data must be updated before the first screen is shown, it's mandatory, so yes or yes the version of the data must be check before creating the database, becase that is required to delete/reload it if it's outdated
l
This is how I would build your flow: 1. Register what you need in Koin. Including Room, but dont do I/O here, so you dont need coroutines. 2. Use Koin in your view to get Room, and do I/O to check if cache needs to update, while doing this show loading
p
something is wrong there
Koin initializes the database when generates the database singleton
and that is done on the constructor of the viewmodel
and that is done before the screen is displayed
l
Koin does not do this, you tell Koin to do this, this needs to change
How you register initialization in Koin is the key point
p
what? I'm doing the normal room initialization, using the normal koin pattern:
Copy code
single<CustomDatabase> {
        Room.databaseBuilder(
            androidContext(),
            CustomDatabase::class.java, "database"
        )
            .createFromAsset("data.db")
            .build()
    }
that does the IO job
and that is how room is initialized
and that is how koin creates a singleton
I don't know other way to use it, is how the documentation teaches you to use it
l
I dont use Room, but init should not do any data access
p
I don't understand you
Koin creates objects
and the database object requires the data to be created
so the data must be passed when creating it
l
Koin creates objects yes, but you tell Koin how to create these objects.
Let me check Room doc
p
in this case you should check for prepopulated room databases, https://developer.android.com/training/data-storage/room/prepopulate
these are databases with data from a .db file stored in assets for example
Copy code
Room.databaseBuilder(appContext, AppDatabase::class.java, "Sample.db")
    .createFromAsset("database/myapp.db")
    .build()
this is how it works
the build bethod that returns the room database requires a
createFromAsset
call which does the IO job internally and returns the prepopulated database
and also I require to check the version with datastore to decide if delete the cached database before prepopulating it with the room build method
so even more IO is done
l
This is not a Koin issue, but the way Room works, unfortunately it does not provide suspendable method (according to docs). In this case, I would create a factory class, that will call this code and cache the result, when you call myCoolFactory.getRoomInstance() inside your view
Copy code
Room.databaseBuilder(appContext, AppDatabase::class.java, "Sample.db")
    .createFromAsset("database/myapp.db")
    .build()
when you call the factory method, make sure you change dispatcher to Dispatcher.IO to unblock UI thread and show loader screen
p
a factory?
l
In Koin you only register the factory
p
but that wil create a object, not a singleton
I need to use the database in a lot of screens, so I require a koin singleton
l
Factory <-- Singleton Room instance you cache after first creation (inside factory)
p
sorry I don't understand you
supposedly factory generates objects each time you inject them
l
but it can also cache ^
p
how?
A factory component declaration is a definition that will provide you a new instance each time you ask for this definition
if I inject the factory in 10 screens, I will create 10 databases
that is not the desired behaviour
l
No, you can write your own factory logic internally
p
umm, do you have a sample of what are you meaning? that is new for me and didn't found anything similar in Koin docs
l
Copy code
// code inside factory

var cachedInstance: RoomClassOrSomething? = null

fun getInstance() {
  if(cachedInstance == null)
    cachedInstance = InitializeRoom()
  
  return cachedInstance
    
}
something like this
and factory is a Singleton
p
is that a good practice?
doesn't seems to be OK for me
l
Popular pattern in programming
p
supposedly we should avoid the old style singletons, as they are memory leaking
l
This is a recommendation for beginners, you can accidentally leak them. But, very very popular pattern today
p
well, I don't think Koin should be used like that
they offer singleton and factories for one reason
I think is doing things even worse than doing IO on koin singleton
also, I got exactly the same IO before startup issue with your proposal, because when the factory gets injected in the viewmodel constructor, the screen is still not created, so the issue is exactly the same, a loading element can't be displayed
l
You missed the point. The Room is not created in Koin when using a factory. It is created when you call the factory getInstance method inside your view, so you can control this flow and show loading when needed
p
can you expand your sample to show what are you meaning?
Copy code
// code inside factory

var cachedInstance: RoomClassOrSomething? = null

fun getInstance() {
  if(cachedInstance == null)
    cachedInstance = InitializeRoom()
  
  return cachedInstance
    
}
l
Copy code
class RoomFactoryImpl: RoomFactory {

var cachedInstance: RoomClassOrSomething? = null

fun getInstance() {
  if(cachedInstance == null)
    cachedInstance = InitializeRoom()
  
  return cachedInstance    
}

}
Registration in Koin
Copy code
single<RoomFactory> {
  RoomFactoryImpl() // this will be called when you need this injected
}
Usage in view:
Copy code
// get instance in constructor

val room = roomFactory.getInstance()

// turn on loading screen
val stuff = room.getStuff()
// turn off loading screen
p
what is RoomFactory ?
l
The interface of the class in the first snippet, you make that yourself
p
ok thank you for offering me this proposal, it probably will work
but now I have to decide if adding this complexity is better than simply doing that IO on the koin startup
l
Yep. Anyway, my break is done. Going back to work. Good luck
p
thank you!
a
Yes do a factory with cache
First time it will be requested it will create the db
I think also for this kind of things the best way to initilize them on startup
On the splash screen
I know there is at least for koin android such thing