On app startup (in Application subclass) I launch ...
# koin
m
On app startup (in Application subclass) I launch a coroutine to perform certain I/O checks and possibly (first time install, or some app updates) build some databases, imports or migrations. The main activity waits for this to finish (showing a splash/loading fragment) before navigating to a home fragment when all the viewmodel dependencies will be available via Koin. This works fine, except when the app is resumed after being killed in the background. When this happens, the fragment back stack is automatically (I don’t seem to have any control over this) restored and so the viewmodel dependencies are fetched via Koin before the startup I/O has had a chance to complete. How do you handle this?  Should I be coding everything more lazily so that, for example, the first query (triggered by user interaction) would trigger db creation/migration/import etc?  But then wouldn’t this lead to unexpected delays for the user?
Perhaps instead of a ViewModel having a dependency on a database, it should have a dependency on
Deferred<Database>
? Any thoughts on that approach?
d
A LoadingFragment before the HomeFragment until the database is ready?
m
This is what I do now, but the problem is when Android is automatically restoring fragments following process termination in the background.
d
Do you use scoped deps?
m
Not that I’m aware of
d
Maybe you need to restrict the dependencies on the application level to the bare minimum, and restrict the other ones just to the fragment/viewmodel they're needed in. Sometimes globally scoped dependencies can play funny tricks... I'm not sure this is your problem, but it might be worth looking into. https://insert-koin.io/docs/reference/koin-core/scopes
m
It’s really not a scoping issue unless scopes provides some kind of support for deferred access, which I’m pretty sure it doesn’t.
d
You're sure there's no lingering singletons that might still be accessing the db when initializing (or something similar)?
I've had problems with not-cancelled coroutines in the past too...
m
The problem is that some dependencies are only (and can only be) declared later on (asynchronously) in initialisation.
d
Well, singletons in Koin are supposed to be lazy even if declared in the module... they're only instantiated when they actually need to be injected.
What you could do I certain situations is to pass a
Lazy<...>
by using kotlin's
lazyOf { }
in certain trickier cases...
Like when the init block does too much, or such things... then a
Lazy
will only be initialized when first accessed. But in general, instantiating a class doesn't do anything until first used...
In the lazy block you could always check an atomicboolean that the db is initialized... lazy keeps trying to initialize until it succeeds... 🙈
m
The lazy part needs to be done off the main thread. At the moment I’m trying out using
Deferred<SQLiteDatabase>
(as a dependency) since anyway access to the database is being done on an I/O thread. This deferred would only be ‘completed’ once it is actually ready (e.g. when startup initialisation has completed).
d
Same idea as Deferred, but if you don't need coroutines... (on which coroutinescope would you run async anyways...?) Using a WorkManager in such cases might be better because you have progress success/failure etc... then the lazy could check it's status... I use async only for short things that need to be off the main thread (or things needed in the current fragment only -- within it's activity scope).
m
Deferred (or some other kind of laziness) is enough (i.e. no need for more details of error/progress State) because viewmodels etc don’t want to handle any of that. Everything just waits until dependencies are ready. No need to care about why it is waiting. The main Activity can monitor initialisation showing a fragment to show progress and user prompts as a result of failures (try again etc).
d
What about handling failure? Using Deferred's await throws whatever was throw inside the async call... and that can happen in any of your dependencies relying on it...
m
Just don’t use async. I use
CompletableDeferred
and set the sql database value when initialisation has completed. So anything that is waiting on that database, is automatically ‘unpaused’ when the value is set.
d
Yeah, I guess that could work... I wonder why I never came across this kind of problem... I've had such splash fragments initialize things needed by other fragments before 🤔.
m
Usually, you would just have a SQLiteDatabaseHelper which does all that for you. But this doesn’t really work in my case
BTW, a good test to check if your app has these problems: 1. Launch app 2. Go to Android Home (via system navigation bar) 3. LogCat - terminate app 4. Launch app (using launcher icon)
d
In my case I needed to authenticate with oauth2 and save an access token before going into other parts of the app. With what you mentioned, the other fragments should be trying to access the api without a token in that situation...
Thanks, I'll check that one -- who knows with Android some times 🙃!
m