In this post, as the title suggests, I'll discuss why one shouldn't emit View States (or as some people prefer to call it Resource nowadays) from Repository, and why is it Anti-Pattern IMO. While discussing that, this post will also address why returning
LiveData from Repository doesn't make sense.
So let us get started by understanding each of the layers in an Android app, and their purposes/function.
Note: I'm not advocating structuring your project by layer here, in-fact I prefer structuring by feature. However, the layers are still there within feature-based modules or packages (in case of monolithic apps).
Layers in a typical Android App
There can be multiple layers in an Android app, however, 3 of them are most significant.
Now, we can divide each of these layers into smaller sub-layers, for example, data layer can be divided into cache and remote sub-layers, but let's focus into these 3 major layers, and try to understand their purpose and functionality.
As the name suggests, this layer is supposed to be dealing with all data fetching, caching, storing, manipulating, and all other data related operations. In the earlier days of Android, we used to have
DataModels in this layer, which in most cases were a single class, dealing directly with DB or DAO and Remote / Network. As we, Android Developers worldwide started becoming more and more cautious about the separation of concern and clean code, we realized that we need to separate out cache and remote.
That's when the concept of Repository came up. Yes, the concept of Repository isn't tied to MVVM architecture, and you can still use it (and you should) with any other arch pattern you use, including MVP.
So, what is the responsibility of a Repository? Simple, manage all data transactions, decide on when to fetch the data from remote and when to fetch it from the cache, etc. Ideally, a Repository should come with
DataStores, based on your requirement, you can have RemoteDataStore or CacheDataStore or both. A RemoteDataStore should contain all the logic to fetch data from remote APIs, if you're using Retrofit in your app, RemoteDataStore is where you call the API interface. Similarly in CacheDataStore, you put all local storage related logic. One thing to note here is that the names are nonsubstantial IMO, I know a lot of good people who prefers to call it NetworkManager instead of RemoteDataStore, and in my opinion, there's nothing wrong with it.
TLDR about data layer: It contains Repository, which should contain only data-transaction related code. Now Repository in turn might have dependencies on Remote/Network and Cache/Local DataStore(s).
Now let us discuss the next layer - the presentation layer.
Presentation is where you keep your business, i.e. the business logic. If you follow MVP, then you have your presenter here, if you follow MVVM, then you have your ViewModel here, one thing to note here is that ViewModels are also used multiple other architectures, including MVI, etc. So, as the goal of this post is to discuss ViewState and LiveData, let us assume, you have
So, what is the purpose of ViewModels? a common answer is to hold the business logic, but actually, that's not all, if you ever think, why is it called ViewModel, instead of Presenter with Stream or something like that, then it becomes clear; along with a place to keep your Business logic, a ViewModel should also maintain the ViewState or State (or as some people prefer to call it - Resource).
Now, what is a ViewState? A View state is nothing but a simple POJO or data class, which will hold the current state of the View, and ideally, a View should render the exact ViewState.
The UI layer is where you should keep your UI designs and ViewState rendering logic. Ideally, the UI layer should subscribe to the presentation layer, to get updated ViewState, and render it, and pass the UI events (such as click) to the presentation layer. This layer consists of Activities, Fragments, custom Views, etc.
What's wrong in emitting State from Repository
The are multiple reasons why you shouldn't emit States from Repository, we'll look at the two most important points here (IMO).
- As we discussed above, it is ViewModel's job to maintain the State. The data layer should never be aware of anything related to the view or ViewState (for eg. whether view state is loading or showing error or success etc.) the data layer should only be caring about fetching and storing data, nothing more, nothing less. Moreover, if ViewModels emit State / ViewState / Resource, then it's more likely that you might end up having at least some part of business logic in the Repository, as you'd generally want the states to be based on business logic.
So, it's evident enough that Repository, which is part of the data layer, should never be emitting ViewState.
- As your app grows and becomes more complex, you might separate the data layer as a separate module. Also, you might wanna re-use a Repository from multiple ViewModels, for example, you have a Repository, say
UserProfilewhich is mostly used in the profile page, now you have a subscription page, where you need
UserProfileagain, so if you copy the same code to
SubscriptionRepositorythen there'll be redundancy, rather you might wanna call both
SubscriptionViewModeland map/flatMap the data according to your business logic. Now, if you have Repositories emitting States / Resources / ViewState, using multiple Repositories would get more complex for you.
Why LiveData in Repository doesn't make Sense
To understand why LiveData in Repository or the data layer doesn't make sense, we need to understand what is LiveData and why was it created. LiveData wasn't created to replace Rx or Coroutines, it was created as a simple reactive state-change wrapper. You'd publish updates from one end, and would subscribe to it from the other end. The main reason why they created LiveData was to remove subscribing and cleanup complexities from View/UI layer. Generally, we want to subscribe to Stream in Activity/Fragment/View, and we need to remember to dispose of the subscriptions when the View is destroyed. Sometimes we also want to avoid receiving updates when our View (Activity/Fragment/View) is in background. Now all these are possible with Rx or Flow or any other full-fledged streaming mechanism, but that was a little complex and a lot of boilerplate, which can lead to human mistakes. Moreover, generally in these cases, we don't want heavy modifications/operations on the data (such as flatMap/map, etc.), since the data here is basically the state, and it's ready to be rendered. So, Google created a lightweight reactive stream called LiveData. Now, if you return/use LiveData in the data layer, that means you're restricting the possibility to map/flatMap the data at the presentation layer, unless you use MutableLiveData at multiple layers, and over-complicate your codebase. Also, if you ever have written unit tests with LiveData, then you are probably aware, that test classes you either need to implement a
TaskExecutor or you need to use
InstantTaskExecutorRule, which tells the LiveData that the observation is in Main Thread. This clearly indicates that LiveData observations are always in Main Thread. That's expected behavior when you're subscribing to the LiveData from your UI/View layer, but do you really want that in your presentation/data layers? Yes, there can be hacks or even ad-hoc mechanisms to have the observation run on other threads, but does it make sense to overcomplicate things when there's plenty of other options?
Don't use LiveData in the data layer, which includes Repository, and don't emit ViewState/State/Resource from Repository.