Getting that offline data right is a tricky thing to do. Especially when you do not have some kind of a pattern in your app. When a new project with offline mode pops up, you are supposed to get the right answer for such requirements. This answer is right here, in this article! What is even better, You can also apply this to an existing project via correct dependency.
Every project needs a plan for how to do it. The more elaborate ones require a pattern, and here, we will discuss the MVP pattern, how it is used, and what the benefits are. The best part will be that it will all be based on the existing KotlinMVP library. So ultimately, all you will need to do is to add a dependency, and the pattern opens itself for your project!
Furthermore, we will also handle the dreaded case of an offline mode in the app here. To constantly remember that there is data flowing from the API in the background, that has to be cached in your app, is a tedious thing to do. With our SHRepository library, which is essentially a Repository pattern implementation, we can provide this offline functionality without such a hassle.
Everything within this article, with examples, can be checked out in MvpPlayground sample app
To start talking about this library, we need to get a little in-depth about how exactly the MVP pattern works. To learn about it, you should read this article https://medium.com/upday-devs/android-architecture-patterns-part-2-model-view-presenter-8a6faaae14a5.
KotlinMVP improves our MVP structure in the following ways:
A big problem for people making new projects is that the MVP structure is not always shared between projects. This library provides a solution to this, by granting the user the `BasePresenter` class. It ensures a one to one relationship with View, even to the point where the `view` variable is `val`.
Not only does this library grant the user the Presenter, but it also provides Presenter-compatible base Views (not to be confused with Android class `View`). These are, namely, `BaseActivity` and `BaseFragment`.
Both `BaseActivity` and `BaseFragment` ensure that Presenter lives as long as View lives, in their respective lifetimes.
`BaseActivity` starts the life of the Presenter during `onCreate`, and ends it ( cleans it up ) during `onDestroy`
`BaseFragment` starts the life of the presenter during `onViewCreated`, because, well, this is when the View is created, right? It ends when the View is destroyed, so the Presenter’s ‘cleanup’ phase is during `onDestroyView`.
What is more, `BaseActivity` grants the user the ability to introduce a `Decorator` for it. This allows the Activity to be customized without further inheritance. So instead of creating `ToolbarActivity`, you get `ToolbarActivityDecorator`, which you can apply to any Activity that supports ‘Decorators’.
What about the Model?
Well, the KotlinMVP library allows you to set up any models you wish. Preferably with ID. That would be for storage in SHRepository.
As a part of the Model role in MVP, the SHRepository serves as the data management layer.
The deal with Params object
The KotlinMVP lib introduces two ways of storing data.
The first one is with SHRepository, we will get into the details of this storage shortly! In general, we will store our business data models there. This is basically everything from the business data layer that is displayed on the screen.
The thing with the Params object is that this is the initial params storage in the app. Imagine a `UserDetailsActivity` that requires an `userId` to request and display the details. This id is what will be held within Params.
These two storages were separated due to the request to have clear navigation to an Activity that specifically requires an id, and now it will ask for it before creating it, assuming that we specify a correct method and correct behaviour when the id is missing inside Params.
This object is serialized inside the Android system via the `Bundle` and `savedInstanceState` methods. This is sufficient for such an object, as it will be required only when a typical View is accessible (Activity or Fragment). No need to keep it even longer via SHRepository.
We’ve already talked about the MVP portion of these libs. Now it is time for the RxJava-powered offline mode.
Imagine a situation that your app is in need of offline mode. Imagine that you’d have to remember to cache requests, interact with a local DB to get cached data, and all that hassle. With SHRepository, you can implement an effective abstraction over the whole process, so when all the above is requested, DB interaction goes unnoticed. All while using a single `request` method.
How it all works is simple. Let’s imagine we’re getting the `User` from a WebService. The SHRepository makes a call through, for example, Retrofit, then as it arrives, the library saves it to the disk. Any subsequent calls will notice that there is a user inside the DB, so it will request from DB first.
SHRepository does not have a mechanism for threading. You need to pick your threads, but this will be easy as it has RxJava in its core!
For now, the library does not have a mechanism to make cached data invalid/out of date.
But before you achieve the above, you need to do some things first:
- Pick the data type – for example `CustomListModel`
- Implement a `diskStorage` for it, our class to handle disk operations like DB
- Get your Retrofit (or something else) going with `networkStorage` for that `CustomListModel`
- Optionally, but suggested, create some wrapping `CustomListModelRepository` that handles all the dirty calls from `SourcedRepository`. This way the code is cleaner.
Disk storage here is handled by a class implementing `ReactiveStorage` for our current `CustomListModel` type. You need to create one of these for each of your data types.
This requires the programmer to get RxJava working and provide the following:
`changes` method, so it fires an item each time the resource changes. “Changing” contract is up to the user, so it may, for example, only fire when a resource is exchanged.
`diskSaver` is a method that needs some logic for saving the data on the disk. Remember about threading so that we won’t get an `OperationOnMainThreadException` or similar!
`readStream` this is a base method for reading the data. `Observable` here should fire the data when ready.
`exists` utility method for checking whether data exists.
You can find an implementation example in MvpPlayground sample app.
This component is easy, all you need to do is implement one method called `readStream`. This will be your `Observable` that fires data when it gets downloaded. As with disk storage, you need to create one of these for each of your data types.
Just like in the case of disk storage, an example is available in MvpPlayground sample app.
We had quite a ride here, alongside nice MVP abstraction, with some offline stuff to do. But in the end, we made it, so now you know how to deal with requirements for a stable, extensible app that handles offline mode effectively.