Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Android Data Repository — A simple pattern that we often misunderstand

Sign upSign InSign upSign InMarcin PiekielnyFollowITNEXT--ListenShareIf you develop Android applications, then I’m pretty sure that you have some sort of Repository classes in your codebase. This pattern has been with us for quite a long time. It is strongly promoted by Google itself as a main building block of the Data Layer in their guide to app architecture.developer.android.comAfter some time spent working with different mobile applications in different teams and companies, I started noticing that many of us don’t have a clear understanding of how a proper Repository should look like. When we are asked what the Repository actually is we often answer:It is a class for accessing data from a database or APIThis is partly true, but it definitely doesn’t show the full picture. When we implement Repositories, we tend to take inspiration from the API structure or the database schema. Going this way, we can easily create Repositories which are nothing more than just wrappers around the backend endpoints or database tables. And the truth is …Not everyone knows that but, this pattern isn’t something specific only to the Android or mobile applications. The concept of the Repository is widely used in many different tech stacks, both backend and frontend.It was described by Martin Fowler in his book Patterns of Enterprise Application Architecture. The author says that:Repository mediates between the domain and data mapping layers, acting like an in-memory domain Object collection.We can also find a good explanation of this pattern in the Domain Driven Design book by Eric Evans:Repository provides a simple interface to receive domain objects and manage their lifecycle.As we can see in the definitions above, there is nothing about any sort of a database or an API. And this is right because the purpose of the Repository is not to perform SQL queries or fetch data from the HTTP endpoints.At the same time, both definitions have an important common part — the domain objects. In the Clean Architecture and the Domain Driven Design a domain model is the center of each application. This model is built of objects which we usually call Entities.In order to perform some business logic, we need to somehow get these objects. Very often, our business logic also needs to modify these objects, add new ones or delete the existing ones. Here we see the true responsibility of Repositories — they provide us with domain objects and manage their lifecycle.Now let’s say that our API defines the following JSON object which we use in our Retrofit Service.We know that accessing a Retrofit Service from other parts of the app is not recommended so we decide to implement a Repository. We create a separate domain model — Book and a corresponding BooksRepository . We also implement two extension functions to map between Book and ApiBook .Our goal was to be Clean and SOLID. We wanted to separate data model from the domain model, build a mapping and hide it inside the Repository.But we did all of it, still looking at the original API definition. We end up with a domain model which is just a copy of the API model, the mapping functions which just copy paste the same data back and forth and the Repository class which brings no extra value to the project.Instead of just blindly copying a structure of the JSON/SQL model we should think of how to represent our objects in a way which has more meaning and brings more value to the mobile application we build.Objects which we keep in the database or which we exchange with the API are just a simplified representation of data which can be persisted or sent. In the domain model we don’t have such limitations and we can look for better object modelling options.The above domain model is not as raw as a JSON one. It uses more specific types like BookId , LocalDate , Price or Currency which come with more meaning and additional functionalities. Some of them are part of the language itself while others can be defined by us.And domain model doesn’t have to be limited only to storing the data. We can add additional business rules to it and benefit even more. If you want to know more checkout one of my previous stories ⬇️itnext.ioAn application doesn’t have just one Repository for accessing all the domain objects. We usually create multiple Repositories to split the implementation and organize it in a better way. During the development we may encounter many situations when we need to decide in which Repository to put a particular operation.When we have some decision to make we try to look for some suggestion or an inspiration. Our first place to check is the backend API documentation. Let’s say that our backend has the following endpoints:By looking at the URL paths we can quickly notice that we have two groups of endpoints: /api/categories/* and /api/books/* . We decide to create two Repositories, one for each group.Until we take a look at the getCategoryBooks method. We decided to put it in the CategoriesRepository because the endpoint belongs to the /api/categories/* path. But on the other hand, this method returns a List so now the Book objects can be get from two different Repositories.It might be confusing and the problem grows with the number of the Repositories in the project. When we write business logic we shouldn’t care about the endpoints URLs. But with this approach we have to. We need to know an exact URL path to guess in which Repository we can find a method we need.Then, instead of the endpoint …… we have the following SQL query.With Room we don’t have nested paths. Database schema is flat and all the books are kept inside the books table. Now this operation doesn’t belong to the /api/categories/* group but to the BooksDao .Does it mean we should split Repositories differently depending on the technology which we use under the hood? Of course not.In the Google’s guide to app architecture we can find a following suggestion:You should create a repository class for each different type of data you handle in your app. For example, you might create a MoviesRepository class for data related to movies, or a PaymentsRepository class for data related to payments.A collection which is very similar to other collections which we know very well. For example a MutableSet type. We can add objects, remove them, get a single one or filter objects to get just a subset of them.But there is one important constraint — each collection can manage just a single type of objects. The generic T can’t be a Category and a Book at the same time. Of course unless they implement or extend some other common type but this is not the case here.When we want to manage our objects using a MutableSet we need two of them: MutableSet and MutableSet . And exactly the same rule applies to the Repositories. When we treat Repositories as collections of domain objects it becomes much easier to make decisions and avoid confusion in the project.Some other technologies, like backend framework Spring Boot, offer a generic Repository abstractions which even better illustrates its similarities to collections.Methods of the Repository can of course accept other objects as parameters. For example BooksRepository.getCategoryBooks(...) which accepts a CategoryId . It can even accept a whole Category object.But this Category is not kept in the collection. We use it only as a parameter to filter Book objects. Once again, think about the MutableSet and it will become much cleaner to understand.So we know that looking from the API perspective is not a good idea. But looking from the database perspective can bring some serious problems as well. Imagine that our book also has chapters now.We don’t use any API and we want to implement a Room database for our application. The database code might look like this:It might be our first idea. We have different Daos so we can have corresponding Repositories, right?.Now the complex part begins. How to create a Book object if we need data from two different Repositories? A strange solution which I saw in some projects is to make chapters: List = emptyList() by default or even make it nullable chapters: List? = null . Then chapters are loaded and attached to the book in some sort of a Use Case.When we get a Book object we expect it to be complete. When we see that book.chapters.isEmpty == true or book.chapters == null we have the right to think that this book has no chapters. But the reality is slightly different. This book may contain chapters, but those chapters may not be loaded yet.As a consequence, when we write a business logic we need to remember about some implementation details of the database. We need to remember that chapters of the book are stored in a separate table even though in our domain model chapters are always a part of a book.Repositories exist to provide us with domain objects. But what if some domain object doesn’t need to be provided independently? What if it is always a part of some other domain object, like chapters are always a part of a book?In that case we don’t need a Repository at all. The fact that objects are stored in a separate database tables is just an implementation detail. This is due to the fact that we are using an SQLite database. With other technology this constraint may not be present and it should not affect a structure of the Repositories.So it is perfectly fine to keep one Repository which accesses different Daos to construct full books with all the nested chapters. Of course you can also use a more advanced Room relationships mechanism but this is a topic for different story 😄Now imagine that we need to implement a shopping cart feature. User selects products, enters a delivery address, provides an ATM cart data for payment and sends all these information to the backend to create a new order.Each step of the flow is handled by a different screen with a separate View Model. Only the last screen needs the full NewOrderRequest so it can submit it to the OrdersRepository.We often choose to rely on the navigation in such a case. Each screen creates an object it is responsible for and then sends it forward to the next screen.When we look at the Repository from the wrong perspective we miss a lot of great possibilities to use it. Our thinking is usually dead simple — no API endpoint or no database table means no Repository.But who said that Repository needs to access some endpoint or a database? Let me once again refer to the Google’s guide to app architecture. You can read here that:The data layer is made of repositories that each can contain zero to many data sourcesSo yes, our Repository can contain ZERO data sources. It can just keep a collection of objects in memory and not store it in any database or send it to any endpoint.Passing data through the navigation is not a good solution for building complex flows. Each object which we want to pass has to be Serializable or Parcelable what puts some limitations on us. When we rely on the Repository these limitations are gone and our implementation can become much simpler.The above solution is way more scalable than the navigation based one. If for some reason we need to dynamically update the same data from several different screens then we don’t need to pass these objects back and forth through the navigation graph. We can just change the Repository internals and use a MutableStateFlow instead of a simple var .When our Repository has no data source we can easily add more of them without rewriting a half of the app. All the View Models which access the ShoppingCart can remain unchanged and we only modify the ShoppingCartRepository internally. Very simple, right?As we can see, the Repository class is much more than just a simple wrapper for accessing a database or API. Repositories exists to provide and manage domain objects so we should implement them based on the domain model, nothing else. Four simple rules which we can keep in mind are:----ITNEXTMarcin PiekielnyinITNEXT--1Jacob FerusinITNEXT--14Seifeddine RajhiinITNEXT--1Marcin PiekielnyinITNEXT--6Nishchal VisavadiyainSimform Engineering--Aldi Kitta--Mirzamehdi Karimov--Hussain Abbas--amol pawar--Uğur AtçıinTrendyol Tech--HelpStatusWritersBlogCareersPrivacyTermsAboutText to speechTeams



This post first appeared on VedVyas Articles, please read the originial post: here

Share the post

Android Data Repository — A simple pattern that we often misunderstand

×

Subscribe to Vedvyas Articles

Get updates delivered right to your inbox!

Thank you for your subscription

×