In this post, I will share an example of how to manage data relationship for an Android in Kotlin code instead of solely relying on database relationship management by Room database. I will be using the example of a Blog post app. Managing data relationships is one of those things that is still harder than it has to be in programming especially with Android development. Little wonder no-code tooling is growing in popularity where among other things, handling relationship is relatively easy. Consider a simple use case of an Android app that displays blog posts from local storage. The app has the following basic requirement.
Each post can belong to one category.
Each category can contain more than one post.
Each post can have many tags.
Each tag can be applied to more than one post.
One author can write each post.
Each post can have one image/attachment.
Each attachment can be attached to only one post.
The above requirements represents a combination of classic one to one, one to many and many to many relationship. From a domain standpoint, we only care for a record of type blog post as shown in Example 1
We can have a UI that displays the blog posts, and all it wants is for us to pass it a list of blog posts to display as shown in Example 2 and not worry about the underlying source of the data or how the relationships between the data is structured.
Example 2 – Mock UI for showing list of blog posts
Sample Blog Post Database Schema
For this sample app, we have established that we need the following data classes.
The domain usecase of the app requires that we keep records of these domain objects regardless of how we manage their relationships. For this reason, we first need to persit them locally. We can start by modeling each of these object with a data class as shown in Example 3. This captures our requirement.
These model classes is agnostic of any persistence details. Since I am using Room which decorates its model classes with Room specific annotations, I created different Room specific model classes and then use a mapper class to map between them. These Room model classes have minimal relationship management detail. The relationship will be management with code. Example 4 show the entity classes.
Example 4 – Entity classses for mock Blog Post app
These entity classes are sufficient for standard CRUD operations. It does not have relationship management. Room database standard scaffold entities for dao, repository, etc is still required. And another entity class is also required to capture the many to many relationship between tags and blog post as shown in Example 5.
With this join table, all the tags associated to a blog post can be retrieved with a simple query and then the TagDao can be updated with a query that accepts the list of the tag ids and return the associated tags as shown in Figure 1
Get Blog Post Record with Nested Objects
Now we need to fetch saved blog posts to display in the list. Again, the UI does not care how the lists are modelled or fetched. It just need an observable stream of blog post and an observable instance of a blog post that it can display. Here is an example of a backing ViewModel for the blog post list UI.
Example 6 – Sample ViewModel for mock blog post list UI
To support this contract, a mapper is needed to map between the Room database entity class and domain classes. Example 7 shows an interface for the mappper that can be implemented for each of the entity class.
Example 8 – Example of mapping related entities into domain object
With this mapper in place, a standard retrieve query can be made to the respective repositories to return a flow of the different entities that participate in this relationship. Coroutine FlatMap can be used to chain the queries since some of the queries depend on the result of the preceding query as shown next.
As with most things in this craft, this is an opinionated approach. While I can write SQL statement to create and manage data relationship, I prefer to keep data persistence implementation constrained to CRUD usecases. This way, business rule and domain objects can change without requiring database migration. Not to mention the fact that SQL statements are error prone. To see the skeleton implementation of this data layer checkout this Github repository. Note that it does not contain Compose UI.
I am a family-first Software Engineer and Creator. I create mobile-first apps that grow businesses and increase efficiency. I learn and share my journey figuring out the money side of code. It is written "Man shall not live by code alone"
Never miss a good story!
Subscribe to our newsletter to keep up with the latest trends!