The goal with the architecture is to end up with something like this:
The main difference is that I’ll be using ObjectBox instead of Room. The architecture doesn’t enforce any specific implementations though. You can always swap out the implementation details. I found ObjectBox to be one of the simplest databases, and it allows for reactive queries without depending on RxJava (though you can use RxJava if you want to).
To see the full example, you can find the GitHub repository here.
To start, I added two models to my project: Zoo and Animal. A Zoo has a one-to-many relation with Animals. You can see their implementations below:
Then I created a simple MainActivity (the View layer), with a RecyclerView to display a list of Zoos, and a FloatingActionButton (FAB) to add new Zoos. I also added in some mock data for this example, so the app would have some data to display. The activity’s code is below:
I create the ViewModel for the MainActivity which is responsible for loading a list of Zoos. The activity observes this data, and when it, changes it lets the ZooAdapter know about it. I’ve left out the adapter’s and layouts’ details as they are just regular implementations. You can view the adapter here if you would like to see it.
You can see the result below:
The ViewModel is essentially just responsible for communicating between the repository and view. It sends requests from the view to the repository, and returns results to the view as well. I try to make one ViewModel per view, but you could use more it if makes sense to do so. All of my ViewModels extend from a BaseViewModel class which manages the ObjectBox subscriptions:
To really follow this type of architecture properly, I shouldn’t be exposing ObjectBox to the ViewModel layer. I left it this way to keep the example simpler, but you could abstract it out with something like RxRelay.
The ViewModel which manages my MainActivity looks like this:
It initializes itself with MutableLiveData to observe List
It also exposes a method to refresh the Zoos. This would trigger the repository to refresh the data from the source. Typically a remote server, so it would send a network request to get the latest data. As a result, the ObjectBox database gets updated and relevant subscriptions will be notified. Resulting in the LiveData in my ViewModel getting updated, passing on the result to its observers, and notifying the view layer.
The repository is responsible for passing requests from the ViewModel to the database and the network, and returning the response. I broke these layers down into API classes (to access remote APIs) and DAO classes (to access the database). You can see my implementation here:
I keep most of the logic in the repository layer. The DAO and API layers only have one responsibility. For the DAO it’s interacting with the database, and for the API it’s interacting with the remote API. They are either passing data on, or returning it, they don’t manipulate it themselves. So when the repository gets a response from the API, it’s responsible for parsing that response and subsequently sending it to the DAO to update the database.
If the repository is given an Observer or LiveData, it sets its value on completion to pass the response back to the ViewModel. These response objects can contain extra information like status codes (success, failure, etc.) and error messages. Since the data models won’t contain this type of information. The implementation of these response objects really depend on what kind of information the ViewModel needs.
To prevent the repository from becoming bloated, I split the response parsing logic into its own set of classes. These parsing classes just take a response String (likely JSON), and convert it into the appropriate model(s). For parsing Zoo responses my class looks like this:
You may notice I included a list of Animals in the parser - this is because it’s possible that a response for a Zoo could contain Animals as well. Although I didn’t actually implement any such responses in this example. The idea here is that a Parser class isn’t directly mapping JSON objects to my data models. It parses the entire response, which may include multiple data models or specific keys that don’t belong to models (like page count).
This layer is responsible for interacting with the database. I would create DAO classes for each model, like my ZooDAO:
Here you can see ObjectBox being accessed directly. It performs any required database operations, and builds query subscriptions. These queries can be observed in the ViewModel, which notifies the LiveData of any data updates.
The API layer is used to make requests to the network and pass those responses back to the repository. The implementation in this example is mocked out, you can see the source here. This could use any type of network requests, you could use Retrofit for example.
The repository observes the response from the API and handles them. Every API request will create a Response object:
This is the relevant information that the repository needs - the response payload and status. More statuses could be added, for example for specific error statuses like authentication failure.
Bringing it all together
In this example, I have my MainActivity observing the Zoos LiveData. It will be notified whenever this value is set. For example, if I add a new Zoo, it will automatically refresh the view. This is because I have set up a DataSubscription with ObjectBox to observe changes. You can see this in action here:
You can find the source code for this DialogFragment here. The important piece of this class is that it’s observing the response here:
When the save action is triggered in the ViewModel, it sets the LiveData’s response with the loading status. Then when it succeeds or fails, it sets that result back as well. Once it returns back to MainActivity, you can see the new Zoo was added without having to explicitly trigger a refresh.
To see the full example, you can find the GitHub repository here. But, you can apply what I have explained here to any new views/models/repositories.
I believe this architecture is a bit easier to understand and apply than the fully Clean Architecture. But, the same principles apply - like isolating unique layers and giving them a single responsibility. I do not consider what I have presented here to be complete, it is meant to be a starting point. You can use it as is, but you should be thinking about how you can build and improve on it. Moving towards something like Buffer’s Clean Architecture Boilerplate is the goal. But, it can be overwhelming to grasp all of these concepts without a solid base to stand on.
Also, in an effort to make it easier to grasp, I tried to keep dependencies to a minimum. I did not include things like Retrofit, OkHttp, Butterknife, RxJava, Dagger, etc. You can add whatever libraries you like to use, you can swap out ObjectBox too. Though I think it works well with this approach.
Let me know what you think of this approach. Also if you have any comments, questions, or suggestions please post them.