9

Repository initialization without StrictMode violations

 3 years ago
source link: https://proandroiddev.com/repository-initialization-without-strictmode-violations-fbbd6e554219
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Repository initialization without StrictMode violations

Initializing your app’s database at startup — easier said than done!

1*CIN_R2whHbzG-KNrRaEXFQ.png?q=20
repository-initialization-without-strictmode-violations-fbbd6e554219
Don’t block the main thead! Photo by amirali mirhashemian on Unsplash

How do you initialize a repository? More precisely, how do you initialize application-scoped classes with database dependencies?

If you’re an iOS developer, the answer to the question about is probably “synchronously on the main thread, in my Application delegate during didFinishLaunchingWithOptions, what’s the big deal?” On iOS, because of the strong guarantees about startup sequencing and fast nominal performance of devices, this answer usually works, even if you are *technically* doing blocking I/O on a UI thread.

If you’re an Android developer, if you provide the same answer, you know that you are committing a Strict Mode Violation, but, given that database initialization happens once at startup, you again may respond, “what’s the big deal?”

In a series of posts about the Clean MVVM Activity lifecycle, we have explored the topic of Android Process Death, and now we know that the startup lifecycle is far more unpredictable and crash-prone than most developers realize. As a result, the correct answer to “how do you initialize a repository?” is “Idempotently and Asynchronously”.

The correct answer to “how do you initialize a repository?” is “Idempotently and Asynchronously”.

Do it all the time

Now that we have learned more about Android process death and been introduced to the concept of the Clean MVVM Activity Lifecycle, we realize that Activities have no guarantees about when initialization may happen. As a result, we need to call our .initialize() method on repositories every single time an Activity or Fragment is created. In order to avoid strict mode violations, these calls need to be asynchronous (and thus Reactive). 90%+ of the time, these calls will be no-ops, but they will save us from problems in the exceptional 10% of cases.

1*zBJXhG9Lo-twh-FSB-0vkA.gif?q=20
repository-initialization-without-strictmode-violations-fbbd6e554219
When do you call .initialize() on a repository?

Building a standard interface

In a modern application, you may have numerous repositories with database-layer dependencies, all of which likely need initialization. As a result, we are going to need to define a common initialization interface. Below are the the collection of interfaces we use when initializing a repository:

CacheLoadResults

An abstract data structure that holds an in-memory copy of the data normally kept on disk. This data structure knows if it is warm, and thus has been initialized already and is ready for use.

IInMemoryCachable

An interface for classes that keep data in memory. It exposes a single method for flushing data out of memory

IInitializable

An interface implemented by all of our repositories to enable asynchronous, idempotent initialization.

RepeatableInitializeStrategy

This is a simple strategy that provides an implementation of an initializer that can be repeatedly called, with no overhead if it’s already warm.

There are two code paths: when cold, and when warm. When cold, we call actionWhenCold, which reads data off of disk and stores it to initializationValue. On subsequent calls, we check initializationValue.isWarm, and if that returns true we return a no-op RxJava signal.

An example initializable repository

Here is an example class that stores frequently used phrases in chat.

We have a data structure, FrequentPhraseCache , that holds a dictionary of phrases.

We have an adapter, FrequentPhraseRepositoryCacheLoadResults , which is an adapter that connects the loaded value of the cache to the isLoaded property of CacheLoadResults .

To conform to the IInitializable protocol, we implement the following:

actionWhenCold: Makes a call to our local database and stores it in our FrequentPhraseCache

initializationValue: Returns the above mentioned adapter between our FrequentPhraseCache and the IInitializable protocol

Combining into logic

Given this pattern, we can combine multiple repositories into a single InitializationLogic class.

In the pattern below, we have a special database initializing repository that knows how to create a database file + tables if they are not present. We have an account repository that reads and provides access to the currently signed-in Profile. And then we have the rest of our repositories that need to be initialized once we are sure that our shared database is ready.

Here is what that class could look like:

Tying the lifecycles together

As a reminder, here is the diagram showing the connection between the standard Android lifecycle and our new Clean MVVM Activity Lifecycle:

Now we can show an example of how we combine onCreate and onResume to our custom lifecycle methods. Below you will find our base PSSAppCompatActivity, which inherits from AppCompatActivity and is the parent class of all of our custom Activities.

onCreate connects to our InitializationLogic class through our runAfterInitializationLogic method, which, once completed, sets up our views and alive subscriptions.

onResume also connects to our InitializationLogic class through our runAfterInitializationLogic method, which, once completed, sets up our visible subscriptions.

Is this architecture overkill?

1*Ksf2ARg_Qbv65jq8a88Nkw.gif?q=20
repository-initialization-without-strictmode-violations-fbbd6e554219
Android lifecycles make my head like…

This is a *lot* of work to setup an Android activity. Developers have asked us, “do I really need to do all of this work?”

What if you don’t care about Strict Mode Violations?

Is it really so bad to synchronously initialize a repository on your main thread, either in Application#onCreate, or in the onCreate method of a common base Activity?

This pattern would block the UI thread. You would not be able to draw any kind of placeholder image, instead you would see a black screen while the app launched. Your app would thus be dependent on the speed of disk i/o on your device.

If you had a large number of repositories, and thus had a large number of disk i/o operations, a slowdown could (in the worst case — 5 seconds of delay or longer) trigger an ANR. On a fresh, modern Android device, this should be uncommon, but as your app is run on more and more devices, the odds of having performance problems from i/o on the main thread increases.

Ultimately you will have to judge if the complexity of your app warrants a more rigorous solution such as the one we have described. As a point of comparison, our app initializes 10 separate repositories at startup, which themselves were split off of a monolithic “god object.” Having this single God Object made our application initialization appear simpler, but that object concealed a great deal of complexity.

Next up

See our Activity Lifecycle Cheat Sheet for answers to common questions and some templates you can use for your own Activities and Fragments.

Further reading

About the authors

Eric Silverberg and Stelios Frantzeskakis are developers for Perry Street Software, publishers of the LGBTQ+ dating apps SCRUFF and Jack’d, with more than 20M members worldwide.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK