Loading...
Home
  • Tech Blogs
  • Videos
  • Conferences
    • Droidcon News
    • Upcoming Conferences
    • Become a Partner
    • Past Events
    • Keep Me Informed
    • Diversity Scholarships
  • Community
    • droidcon Team
    • How to Hold a Droidcon
  • Android Careers
Sign In

Global CSS

 

Immutable lists in presenter

How to reduce the number of recreations

 

 
Michael Spitsin
Love being creative to solve some problems with an simple and elegant way
Published: August 28, 2020
Tweet
Share
 

 

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, and DiffUtil 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 showContentcalling. 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.

 

 

Image for post
 

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

With the help of Kotlin delegates

proandroiddev.com

 

Attach your presenters to view layer

With help of Kotlin delegates

proandroiddev.com

 

Why service locator is so unpopular

And how you can use it in project

proandroiddev.com

 

 

Tags: Android, Immutability, MVP, Kotlin, Architecture

 

View original article at: 


 

Originally published: August 28, 2020

Android News
Getting… your BottomSheetScaffold working on Jetpack Compose Beta 03
Getting… your BottomSheetScaffold working on Jetpack Compose Beta 03

By Carlos Mota

It’s Monday, no releases this week, and… there’s a new version of Jetpack Compose — beta 03—available. What a perfect time to just increment 02 to 03 and see what’s new. The API is (almost) final so after updating from alpha to beta there weren’t any big changes to do. However, and remember that’s still in development, there’s always something that I need to update. 

By ProAndroidDev -
Android News
Noisy Code 🗣 With Kotlin Scopes
Noisy Code 🗣 With Kotlin Scopes

By Chetan Gupta

Scopes make your code more readable? think again... You are going to encounter these scope functions namely let, run, apply, also, within every Kotlin codebase, along with all the mischievous ways developers exploit their usage from the way they were intended for. Let see how popular opinion on those ends up just as a code noise.

By ProAndroidDev -
Android News
Improving Android DataBinding with Bindables library
Improving Android DataBinding with Bindables library

By Jaewoong Eum

DataBinding is one of the most important factors for MVVM architecture. The basic concept of DataBinding is to link the view and view model via observer patterns, properties, event callbacks, etc. Linking and automating communication between the view via the bound properties or something in the view model has a lot of benefits in the MVVM architecture concept.

By ProAndroidDev -
Android News
KMM QuickStart Guide
KMM QuickStart Guide

By Mayank Kharbanda

Kotlin Multiplatform (KMP) is a code-sharing technology that allows us to use the same code for different platforms whether it’s JVM, Android, iOS, watchOS, tvOS, Web, Desktop, or WebAssembly. In this article, we will use Kotlin Multiplatform Mobile (KMM) which is a subset of KMP with the focus on providing better tooling and support for sharing code on mobile platforms i.e. Android and iOS.

By ProAndroidDev -
droidcon News

Tech Showcases,

Developer Resources &

Partners

/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/home-details/EmployerBrandingHeader
EmployerBrandingHeader
https://jobs.droidcon.com/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/jobs-droidcon/jobs.droidcon.com
jobs.droidcon.com

Latest Android Jobs

http://www.kotlinweekly.net/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/kotlin-weekly/Kotlin Weekly
Kotlin Weekly

Your weekly dose of Kotlin

https://proandroiddev.com/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/pad/ProAndroidDev
ProAndroidDev

Android Tech Blogs, Case Studies and Step-by-Step Coding

/detail?content-id=/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Zalando/Zalando
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Zalando/Zalando
Zalando

Meet one of Berlin's top employers

/detail?content-id=/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Academy for App Success/Academy for App Success
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Academy for App Success/Academy for App Success
Academy for App Success

Google Play resources tailored for the global droidcon community

Follow us

Team droidcon

Get in touch with us

Write us an Email

 

 

Quicklinks

> Code of Conduct

> Terms and Conditions

> How to hold a conference

> FAQs

> Imprint

Droidcon is a registered trademark of Mobile Seasons GmbH Copyright © 2020. All rights reserved.

powered by Breakpoint One