There are different Architecture approaches in Android. The ways you can structure your code and organize the project. In each approach, there are tonnes of variations of how things can be provided/organized/transferred or shown. Moreover, from time to time you may mix different approaches and result in a half-blood architecture that is fitted for your needs, for your team, for your way of work.
One of the examples of such thing is to unite different observable fields in View Model into the one observable model, which is a state, so your MVVM will go more in the direction of MVI. And that’s fine. The more you know the approaches, the more you understand that each of them is not a win itself. The win is a team and contribution of each in the project.
Presenters and States
The whole above prelude was to prepare you to some realities we have and for some perfectionists, it can be shocking ;) In our project, we split domain view and presentation logic with the help of MVP architecture pattern. But! With slight differences. You see in the classical implementation, presenter works with view interface directly and tells it what to do: showUserName
, hideOrderId
and etc. The set of simple atomic commands, that gives to the presenter control over the view. Which actually needed for providing easy unit tests, for instance.
This approach is completely fine, but from the beginning, we decided to build a view as a list of different items. We decided to do that because it gives you two good advantages:
- Reduce the time of inflation and view preparation. Since that is not needed to inflate the whole content, the time will be reduced by the number of hidden items of the screen.
On one of my previous projects we had a screen with detailed information about the car. Price; basic info like brand, number of owners and etc; and a lot of interactable widgets. Cost of ownership, the graph of all costs of all similar cars, reviews, articles about model, similar cars and etc. As you may understand we had a lot of content, that were placed off the screen until user will scroll to it. Because we used everything in one screen, it took 1 second to inflate and prepare everything. And because we had no time to refactor this screen, we had to use ViewStubs
as a workaround for that. (you can read here how I made AsyncViewStub
)
- Increase the pluggability of each view item. Since each part of the screen now is an item of the list, it is easier to take this item and plug it into another screen.
Although all of those advantages, we not using it for all screens. Everything in life should be balanced and software engineering is not an exception.
So the above approach with splitting the layout into atomic items in some generic list and the unavailability to provide that for all screens in a strict way led us to remain presenters and keep the interaction with a view through the interface. However, in those cases we needed to provide proper control over the formulating, adjusting, and sending the items to the view.
For that, we decided to keep the list of items inside the presenter and in cases, we need to update the view, send this list over view.showContent(list: List<Item>)
So in the presenter, we keep the list of states. Each state (item) is associated with a list item on the screen.
Immutability as good guys teach us
So we still got all the advantages of the classical approach with unit tests and etc. But moreover, we got the aforementioned advantages of structuring the layout and the possibility, for instance, of easy snapshotting the state of the whole list (for instance to be able to track down some bug or send some extended analytics).
Please note: that all of those things can be resolved with a classic MVP. For instance, you can have an intermediate layer between Fragment and Presenter, that acts as a view. This decorator intercepts all calls to the actual view and constructs the internal state, which can be sent for analytical purposes. This approach can be also used to combinate more convenient communication with a view through the atomic methods list showChatUnreadCount(count: Int)
and etc. The intermediate decorator will convert all that calls into item’s states and send the constructed list to the view, which will use it for its list adapter. So as I’m saying all depends on the situation.
To work with that list of the items we decided to have all items as an immutable data class
-es and reconstruct the list all the time when something is changed. So the list would be also immutable.
This approach gives us:
- The availability to use
DiffUtils
and less pain on updating the list. Just a pass a new one to change the old one, andDiffUtil
will do everything for you. - Easy to track down the problems. When you see, that the list or a particular item is in the incorrect state it is easy to track down, what did go wrong, compare to the mutable item, that can be updated and easily forgotten to notify the list view to re-render it.
But all come with a price. The price of immutability is object recreation. On every symbol entered from the keyboard, on every number updating the whole item will be recreated and, thus, the whole list will be restructured and a new list will be built.
This seems to be a lot of pain. But…
We work in an object-oriented language
The language we work on is the OOP language. Yes, Kotlin made some language lexical changes compare to Java which made it act more like Procedure language (extensions, inline methods, inline classes) but still we work in the OOP world.
I’m not telling, that this is a bad thing. On the contrary! The good synergy of those two approaches (with functional programming on the top) can give you really easy and nice solution for a lot of problems, which can have each of them separately.
So giving the fact we work in OOP it is normal to suppose that we will work with object, thus, we will create them and we will release them, so the GC will be able to remove them and clean the memory. Over time GC is improving for JVMs. Today we have multithreading algorithms (tri-color algorithm, for example), that are not blocking the main thread. We have much more memory and CPU performance.
Well, all the time you use the application, a lot of strings/numbers and etc are recreated over and over again. So when in most cases you have item with a couple of fields, there is no harm to recreate it over and over again.
Here is an example of how it can be done:
Here you can see, that inside the presenter when we get the result we can create our list all over again with up-to-date data and pass it to the view layer.
Step aside of academic examples
The first thing is that example will not work in a real-life example, because of the next factors:
- Generally, we have multiple places of view change
- In most of the cases, each place update only some part of the view
Giving that I will adjust the example a little bit. Right now it will look a little bit too much, but just consider we have more complex screen ;)
Now we can update each item separately and then we can just call view?.showContent(constructList())
. Basically this is the way, how you can work with immutable states and lists, so you will be always sure and be able to track down, from where the change could happen.
But there are four things I personally don’t like in this example:
1. Duplication of mapping payment code
Just consider something more complex, consider it is not just a mapping but a couple of more lines related to payment. We have that duplication in mapPayment
& mapUser
. As we see, users also have some kind of its own payment, so it logical to apply such presentation logic for his/her payment, too.
That’s better. Do not repeat please yourself. But this improvement leads us to the second thing I don’t like.
2. Multiple list recreation when user mapping is happening.
Now if we will replace mapPayment
with what lies inside, we will see the next picture:
private fun mapUser(user: User) { userItem = mapper.mapUser(user) payItem = mapper.mapPay(user.defaultPayment) view?.showContent(constructList()) view?.showContent(constructList()) }
So we calling showContent
two times, and, thus, reconstructing the list two times, which is not an optimized thing. It is acceptable, but deep inside you start crying ;(
The temporary solution would be to remove showContent
calling from mapUser
method:
private fun mapUser(user: User) { userItem = mapper.mapUser(user) payItem = mapper.mapPay(user.defaultPayment) }
And we all do such things from time to time. But this leads to the third thing I don’t like:
3. Error-prone code
This workaround requires from your side (and the side of all members of the team) be careful with this code, because, if for instance one day somebody changes the ordering:
private fun mapUser(user: User) { payItem = mapper.mapPay(user.defaultPayment) userItem = mapper.mapUser(user) }
The disaster will happen! Sure, I’m exaggerating things, but I think we should try as we can to simplify our lives and try to a) reduce the number of things to think of, b) segregate and isolate such thinking places and encapsulate behind the nice and easy to use API
4. Always remember to call update
Above all of that one inconvenient thing is that you should always remember to call view?.showContent(constructList())
and the point is not that this line is complex, no. The point that you need always to remember that after updating the item, you need to update the view. Which increases your concentration requirement and increases the complexity of your job and, thus, increases the probability of error.
Still, we should remember that on the other hand, this is a nice approach. Because you can decide when you need to forcibly update the view. Which means you can group some updates together.
Let’s use Kotlin a little bit
As we in Kotlin’s world, we can use its benefits. Let’s take a look into observable
delegate, which hooks a special listener to the property’s update.
private var userItem: Item.User? by observable(null) { _, _, _ -> view?.showContent(constructList()) } private var payItem: Item.Pay? by observable(null) { _, _, _ -> view?.showContent(constructList()) }
Now this simple change, completely change the whole game we have here.
On the one hand, we may not care about orderings and showContent
calling. All we need to do is to update the property, and that’s it. Very simple and straightforward:
private fun mapUser(user: User) { userItem = mapper.mapUser(user) payItem = mapper.mapPay(user.defaultPayment) }
You don’t need to think about updating the view, it will be updated automatically.
On the other hand, you lose the opportunity to call showContent
when you need it. Now you can not batch multiple updates into one big update. In the example above the list will be constructed two times. Because we have observable
property the normal equivalent of that method will be:
private fun mapUser(user: User) { userItem = mapper.mapUser(user) view?.showContent(constructList()) payItem = mapper.mapPay(user.defaultPayment) view?.showContent(constructList()) }
And you will not be able to do anything with this.
The combination is the key
Let’s try to sum up all we had into something, that on one hand will simplify all things as the last approach, but on the other hand, will be optimal enough.
Let’s think. The only major issue in the last approach that showContent
is called every time the any of item is updated within the same frame. Would be cool to wait until the main thread will finish its portion of work and only then we will tell “hey, bro, some items are updated, we want to schedule list recreation on new”
So let’s do that.
So let’s try to analyze that class. The only public method we have here is a special delegate listItem
which creates a observable
delegate from a provided argument with the predefined internal listener of property updates.
What happens in this generic observer. Inside is we see that property is changed and update is not triggered yet (!itemInvalidated
) we trigger that update:
itemInvalidated = true updateList()
Next in the updateList
we run the invalidation job:
update() itemInvalidated = false invalidateJob = null
Inside the main
coroutine context. The method update
is provided through the constructor of the batcher.
The magic happening, because everything is happening inside the main thread, which lies inside the main
coroutine context. That’s why we have no concurrency issues and no synchronization issued.
Moreover when you call updateList
it will not run update()
lambda immediately. Instead, this coroutine will be scheduled on the main thread, and when it will be free (after all items will be updated, for instance), the coroutine will be executed.
In the same time, you may drop all thoughts like “Oh, what if one of the items will be changed, while update()
is being called”, because actually it will happen only after because both share one main thread, which is already busy with update()
method.
Let’s look on the updated presenter one more time:
As you can see, we remained the simplicity of the usage (we just updating item properties without remembering to call showContent
) and at the same time, we avoid redundant calls. For instance, for the method mapUser
the showContent
will be called now only one time, once both items are updated, in the next frame.
Afterwords
Immutability is a good thing and its main disadvantage (creation new instance with every slightest update) is already neutralized by the capacity of CPU, amount of memory, and the performance of Garbage collection.
But still, we should not abuse such things and pollute heap with a tremendous amount of objects. Whenever we can and it is justified we should provide simple, subtle, and at the same time optimal solutions for the problems we trying to solve.
If you liked that article, don’t forget to support me by clapping and if you have any questions, comment me and let’s have a discussion. Happy coding!
Also, there are other articles, that can be interesting:
Delay with respect of lifecycle |
![]() |
Attach your presenters to view layer |
![]() |
Why service locator is so unpopular |
![]() |