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

 

Don’t use LiveData in Repositories

 

 
Jossi Wolf
Android @snappmobile_io, working remotely from Hamburg, Germany. I like Kotlin, Animations and Rabbits! github.com/jossiwolf | speakerdeck.com/jossiwolf
Published: July 30, 2020
Tweet
Share
 
 

We recently joined a new project with heavy LiveData usage, everywhere. The search for main thread blockages led us down a rabbit hole of removing a significant portion of our LiveData usages.

 

Our Setup

We started off with Repositories, ViewModels and Fragments. The repositories would connect to data sources and expose LiveData. The ViewModels would then expose the LiveData to the UI Layer.

 

Image for post

Our setup visualised.

 

Now — there are some issues with this, like the actually unneeded abstraction — but we won’t look at that here. Here’s what a typical Repository would look like:

 

I know what you think: This shouldn’t have been a LiveData in the first place. Bear with me!

 

From there, we used it in ViewModels:

 

 

The code didn’t look too great, but still okay. There is one big issue here though — and it’s not visible on first sight.

handleLiveData was a simple helper function that took a LiveData and emitted a loading state before the actual data was emitted. But at some point, we needed to run some transformations on the LiveData. Creating a MediatorLiveData every time is quite verbose, but luckily, there is the Transformations package for LiveData . It offers methods like map and switchMap for LiveData.

 

LiveData and Threading

 

Image for post

Android icon made by Freepik from www.flaticon.com, then lovingly put together in Google Drawings by me.

 

Soon after joining the project, we realised that we had a lot of Main Thread blocks in our app, so we went to investigate. Looking at our code and looking at LiveData documentation, we learned that LiveData observers are always called on the main thread.

This can be very helpful since LiveData is often used in the UI Layer. Whenever a new value comes through, you probably want to be on the main thread anyways to display that data. But this threading behavior also shot us in the foot.

 

The Transformation Methods

Looking at the code underneath, our map function looked like this:

 

Suspiciously similar to the official Transformations code…

 

Coming back to our threading behavior, the callback on line 4 above is called whenever the source emits a value. In the callback, we assign the MediatorLiveData ‘s value to the result of the mapper lambda.

Since the callback simply is a LiveData observer, it gets called on the main thread as well. Without actually noticing, we just switched threads! That’s not exactly a great thing: If you are on the main thread, you don’t want to run any long-running operations, if you are on a background thread, you don’t want to do any UI mutations.
So in this example, our mapper lambda gets invoked on the main thread. Just doing some data transformations might not be noticeable, but what if you’re running some more complicated operations? Maybe reading something from the database?

 

 

So we learned to be extremely cautious about threading when running transformations on LiveData. We fixed it afterwards — you can learn how in the next part of this post, coming soon.

 

How this even happened

Ideally, this wouldn’t have happened in the first place. The LiveDatadocumentation clearly states that observers are called on the main thread, so that would have been a great indicator for the previous developer. But sometimes, time-pressure is high, or you might miss something. It happens to everyone and I’m sure there are many codebases out there that look worse than ours.

The Guide to App Architecture also recommends using LiveData in Repositories. Lots of developers will have followed this advice, possibly bringing main thread blockages to their apps.

The other mechanism that should have helped catch this are the Support Annotations. The methods provided by the Transformations package are annotated with @MainThread , which tells Android Studio that that method should only be called from the main thread. Similarly, there’s also @WorkerThread and some more.

 

Copied for better readability

 

Android Studio will complain if you’re calling a function/method/class that’s also annotated with a Thread Annotation, like this one:

 

 

In our case, those annotations weren’t used, so Android Studio didn’t have a chance to catch these mistakes. If you’re doing something slightly funky with threading, or you are unsure about it, it definitely makes sense to use the Thread Annotations to make things more explicit.

Coming back to the methods offered by the Transformations package, I believe that it shouldn’t call the transformers on the main thread in any case. Transformations most probably shouldn’t be happening in the UI layer in any case, so running them on the main thread doesn’t make lots of sense.

 

A (quick) attempt at fixing the issue

So we needed a quick fix, because who wants to ship apps with main thread blockages?

A really quick fix would look something like this:

 

AppExecutors from the Architecture Components Sample.

 

We make sure that we call the mapper on a background thread and then set the value from the main thread. This might seem a bit complicated but LiveData#setValue has to be called from the main thread. Alternatively, we could use LiveData#postValue , but this might lead to unexpected behavior like values being swallowed.
Obviously, this is all not great, but it works as a quick fix to help with some main thread blocks.

 

LiveData and Repositories

Apart from the other points, LiveData is tied to the Android Lifecycle. This can be a great thing, especially when communicating between ViewModel and components like Activities or Fragments.

The way LiveData is architected, observing it mostly makes sense from the UI layer. Even when using LiveData#observeForever which isn’t bound to a lifecycle, the observer is called on the main thread, so that every time you would have to make sure you’re on the right thread for what you want to do.
Repositories should be kept free of framework dependencies, and with that also LiveData since it is tied to the lifecycle.

 

Flow to the rescue!

Coroutines Flow are an awesome alternative here. Flows offer similar functionality: Builders, cold streams, and useful helpers for e.g. transforming data. Unlike LiveData, they are not bound to the lifecycle and offer more control over the execution context, which Roman Elizarov wrote a great post about.

 

Image for post

Where we’d put our Flows! :)

 

Concluding, threading is always something to really look out for. Try to make it explicit which thread you are and should be on, e.g. by using the Thread Annotations. 
The “real” culprit here is LiveData though. Not everybody might be aware of the fact that LiveData observers are always called on the main thread, so it is something to make sure you’re good with when using LiveData.

With that, calling data transformation methods on the main thread is probably unexpected behavior for most developers and should be made more aware of (which this post hopefully helps with).

 

I’d recommend using LiveData only for communication between ViewModels and the UI Layer where observers being called on the main thread makes more sense.

Watch this space for the second part — migrating from LiveData to Coroutines and Flow!

 

Thanks to Florina Muntenescu. 

 

 

 

Tags: Android, Livedata, Kotlin Coroutines, AndroidDev, Android App Development

 

View original article at: 


 

Originally published: July 15, 2020

Android News
Compose CameraX on Android
Compose CameraX on Android

By Peng Jiang

Android new UI toolkit Jetpack compose is in beta now, which has all the features you need to build production-ready apps. CameraX is another Jetpack support library, which let you control the camera easier. As compose is still under development, lots of the views are still not available the compose way.

By ProAndroidDev -
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 -
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