In this series, I translated a series of articles of coroutines and Flow developers, aiming to understand the reasons for the current design of coroutines, flows and LiveData, find out their problems from the perspective of designers, and how to solve these problems. PLS Enjoy it.

When and why to use Android LiveData

Almost a year ago (with the first alpha release in May 2017), Google released Android Architecture Components, a collection of libraries designed to help Android developers design more powerful, testable, and maintainable apps. Most notable are the LiveData class and related life-cycle aware classes, the Room persistence library, and the new paging library. In this article, I’ll explore the LiveData class, the problems it is expected to solve, and when to use the library.

To be honest, LiveData is an observable data holder. It allows components in your application, usually the UI, to observe changes to LiveData objects.

The new concept with LiveData is that it is lifecycle aware, meaning that it respects the lifecycle state of the application component (Activity, Fragment) and ensures that LiveData only updates the component (observer) when it is in an active lifecycle state. This behavior prevents memory leaks and ensures that the application does not do more inefficient work.

To better understand when to use this new observable data holder and the benefits of using it, in the rest of this article I’ll review some alternatives to the basic task of updating the UI based on data changes.

  • Manage data in UI components
  • Use a listener interface
  • Using the Event Bus
  • Using LiveData
  • conclusion

But first, let’s take a look at our sample scenario.

Scenario

To illustrate this with a code snippet, imagine building an interface UI in a social networking application that displays a user’s profile and the number of followers that user has. Below the profile picture and the number of current followers, there is a toggle button that allows the currently logged in user to follow/unfollow the user. We want this button to affect the tag with the number of followers and change the text on the button accordingly. (The code will use the Java language).

#1 — Managing data in the UI Component

One naive idea is to turn UI components (activities, fragments) into “God objects.” You start with the only UI component provided to you by the framework, such as an Activity, where you write your application’s UI code. Later, when you need to process the data and change the UI based on it, you’ll find it easier to continue writing code in the activity because it already contains all the fields and UI elements that need to be updated. Let’s see what the code looks like.

public class UserProfileActivity extends AppCompatActivity { ... @Override protected void onCreate(Bundle savedInstanceState) { ... isFollowing = webService.getIsFollowing(); numberOfFollowers = webService.getNumberOfFollowers(); toggleButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { toggleFollow(); }}); } private void toggleFollow() { if (isFollowing) unFollow(); else follow(); } private void unFollow() { isFollowing = false; numberOfFollowers -= 1; followersText.setText(numberOfFollowers + " Followers"); setNotFollowingButton(); } private void follow() { isFollowing = true; numberOfFollowers += 1; followersText.setText(numberOfFollowers + " Followers"); setFollowingButton(); } private void setFollowingButton() { toggleButton.setText("Following"); toggleButton.setBackground(getLightGreenColor()); } private void setNotFollowingButton() { toggleButton.setText("Follow"); toggleButton.setBackground(getGreenColor()); }}Copy the code

This approach is a negative pattern that violates some of the key principles of writing well-designed code, and has a major flaw when it comes to data and persistence.

That’s the loss of data and state — application components like activities or fragments are managed not by us, but by the system. Because their life cycles are not under our control, they can be destroyed at any time based on user interaction or other factors such as low memory. If we create and process our data in a UI component, once that component is destroyed, all of our data is destroyed. In this example, for example, every time the user rotates the device, the Activity is destroyed and recreated, causing all data to be reset, the network call to be executed again, wasting the user’s traffic, forcing the user to wait for a new query to complete, and so on.

#2 — Using a listener interface

Another approach to the task of updating the UI based on data changes is to use the listener interface, which imposes a specific function on the UI listener.

// IProfileRepository.java public interface IProfileRepository { boolean isFollowing(); void toggleFollowing(); int getNumberOfFollowers(); } // ProfileController.java public class ProfileController { public void showProfile(IProfileListener listener) { boolean isFollowing = profileRepo.getIsFollowing(); if (isFollowing) listener.setFollowingButton(getLightGreenColor(),"Following"); else listener.setFollowingButton(getGreenColor(), "Follow"); listener.setFollowersText(profileRepo.getNumberOfFollowers()); } public void toggleFollowing(IProfileListener listener) { profileRepo.toggleFollowing(); showProfile(listener); } public interface IProfileListener { void setFollowingButton(int color, String text); void setFollowersText(int numberOfFollowers); } } // UserProfileActivity.java public class UserProfileActivity extends AppCompatActivity, implements IProfileListener { ... @Override protected void onCreate(Bundle savedInstanceState) { ... toggleButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { profileController.toggleFollowing(UserProfileActivity.this); }}); profileController.showProfile(this); } @Override public void setFollowingButton(int color, String text) { toggleButton.setBackground(color); toggleButton.setText(text); } @Override public void setFollowersText(int numberOfFollowers) { followersText.setText(numberOfFollowers + " Followers"); }}Copy the code

For brevity, I have omitted some interface declarations and some implementations. First, let’s look at the solution. The Activity itself is not aware of changes in the data information of users’ followers. Its only concern is to display a user interface with text where the user can click a button. Notice that the current Activity does not contain any line of if condition code. The Activity gets data based on ProfileController. The ProfileController, in turn, uses the ProfileRepository to retrieve data, either from the network (using the WebService previously used in the Activity) or from somewhere else (such as in-memory caching or persistence). Once the ProfileController has the data and is ready to update the user interface, it calls back the incoming listener (actually the Activity) and calls one of its methods. In fact, ProfileController is the first step towards model-view-Presenter design. We can set up the Controller to use more mini-controllers, each of which will change its own UI elements, thus taking the ability to change the UI completely out of the activity.

This approach avoids the problem of data loss if the UI component is corrupted, and is useful for separating concerns in your code correctly. In addition, it keeps the UI component code clean and as lean as possible, making our code easier to maintain and, in general, avoiding many life-cycle related issues. But the main drawback to this efficient approach is that it’s a little error-prone, and if you’re not careful, you may find yourself causing an exception or crash. This simple example is a little hard to prove, but for more complex and realistic scenarios, mistakes are bound to happen. For example, if your Activity undergoes a configuration change, your listener reference might be empty. Another example is when your listener’s life cycle is inactive, such as an Activity in the back stack, but you still try to pass events to it and call its functions. In general, this approach requires that you understand the lifecycle of the listener (UI component) and account for it in your code. This is also true for languages like Kotlin where functions are first-class citizens. Although you can pass a function as a parameter rather than the UI component itself, you should also know the lifecycle of the UI component here, because the function usually operates on the UI elements of that component.

#3 — Using an event bus

Another approach is to use an event-based mechanism (publisher/subscriber) when we have to update the user interface based on data changes (demonstrated using GreenRobot EventBus).

// IProfileRepository.java public interface IProfileRepository { boolean isFollowing(); void toggleFollowing(); int getNumberOfFollowers(); } // ProfileController.java public class ProfileController { public void showProfile() { int numberOfFollowers = profileRepo.getNumberOfFollowers(); boolean isFollowing = profileRepo.getIsFollowing(); if (isFollowing) EventBus.getDefault().post( new UpdateFollowStatusEvent(getLightGreenColor(),"Following", numberOfFollowers)); else EventBus.getDefault().post( new UpdateFollowStatusEvent(getGreenColor(), "Follow", numberOfFollowers)); } public void toggleFollowing() { profileRepo.toggleFollowing(); showProfile(); } } // UpdateFollowStatusEvent.java public class UpdateFollowStatusEvent { public int buttonColor; public String buttonText; public int numberOfFollowers; public UpdateFollowStatusEvent(int buttonColor, String buttonText, int numberOfFollowers) { this.buttonColor = buttonColor; this.buttonText = buttonText; this.numberOfFollowers = numberOfFollowers; } } // UserProfileActivity.java public class UserProfileActivity extends AppCompatActivity { ... @Override protected void onCreate(Bundle savedInstanceState) { ... toggleButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { profileController.toggleFollowing(); }}); profileController.showProfile(); } @Subscribe public void onStatusUpdateEvent(FollowStatusUpdateEvent event) { toggleButton.setBackground(event.buttonColor); toggleButton.setText(event.buttonText); followersText.setText(event.numberOfFollowers + " Followers"); } @Override protected void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override protected void onStop() { super.onStop(); EventBus.getDefault().unregister(this); }}Copy the code

As you can see, this solution is very similar to the previous one. The difference is that instead of calling the listener’s methods, we fire events. These events are intercepted by the subscriber, in our case the Activity, and the user interface changes accordingly.

There is a heated debate in the community about whether the event bus is a good solution, or whether listener callbacks are the real solution. However, this technique, as a listener interface, also avoids data loss and maintains separation of responsibilities in the code. In addition, libraries that implement event-based mechanisms often support advanced features such as delivery threads and subscriber priorities (one can also use Android LocalBroadcast without the need for third-party libraries). In contrast to the listener interface approach, the event-based approach doesn’t require you to worry about the lifecycle because most libraries will do it for you. However, you need to be careful to register (and unregister) subscribers so that they can receive events, and failure to do so correctly can result in memory leaks that go unnoticed. Also, while the event bus may seem easy to implement at first, it quickly becomes a jumble of complex events all over the code base, making it really hard to find problems when reviewing or debugging code.

The other big thing you should be aware of when using the event bus has to do with the one-to-many nature of this mechanism. In contrast to the listener approach, where you have only one event subscriber, in the event bus approach, you may find yourself with many subscribers, not all of whom you know. For example, the user opens two profile pages, both instances of the UserProfileActivity type. Then, an event is fired. Both instances of UserProfileActivity receive this event, causing one of them to be incorrectly updated because the event initially corresponds to only one of them. This could be a mistake. To solve this problem, you may find yourself taking a lot of detour, querying for additional event attributes, such as the user’s ID, to avoid false event interception.

In my opinion, the event bus mechanism makes sense, but you should be careful in which situations you use it. For example, in the case of application crossover events, there is no clear relationship between the source of the event and the role in the event. In the case of updating the UI based on data changes, as in our example, I don’t see the case for using the event bus, but between this approach and the previous listener interface approach, I would prefer the latter.

# 4 – Using LiveData

After exploring existing solutions to accomplish this task, let’s take a look at how LiveData for Android architectural components is addressed.

// IProfileRepository.java public interface IProfileRepository { void toggleFollowing(); LiveData<FollowStatus> getFollowStatus(); } // ProfileRepository.java public class ProfileRepository implements IProfileRepository { ... private LiveData<FollowStatus> followStatus; @Override public LiveData<FollowStatus> getFollowStatus() { if (followStatus ! = null) return followStatus; boolean isFollowing = webService.getIsFollowing(); int numberOfFollowers = webService.getNumberOfFollowers(); followStatus = new MutableLiveData<>(); if (isFollowing) followStatus.setValue(getFollowingStatus(numberOfFollowers)); else followStatus.setValue(getFollowStatus(numberOfFollowers)); return followStatus; } @Override public void toggleFollowing() { if (followStatus == null) return; webService.toggleFollowing(); int currentNumberOfFollowers = followStatus.getValue().numberOfFollowers; if (followStatus.isFollowing()) followStatus.setValue(getFollowStatus(numberOfFollowers-1)); else followStatus.setValue(getFollowingStatus(numberOfFollowers+1)); } private FollowStatus getFollowingStatus(int numberOfFollowers) { return new FollowStatus(getLightGreenColor(), "Following", numberOfFollowers); } private FollowStatus getFollowStatus(int numberOfFollowers) { return new FollowStatus(getGreenColor(), "Follow", numberOfFollowers); } } // FollowStatus.java public class FollowStatus { public int buttonColor; public String buttonText; public int numberOfFollowers; public FollowStatus(int buttonColor, String buttonText, int numberOfFollowers) { this.buttonColor = buttonColor; this.buttonText = buttonText; this.numberOfFollowers = numberOfFollowers; } public boolean isFollowing() { return "Following".equals(buttonText); } } // ProfileViewModel.java public class ProfileViewModel extends ViewModel { public LiveData<FollowStatus> getFollowStatus() { profileRepo.getFollowStatus(); } public void toggleFollowing(IProfileListener listener) { profileRepo.toggleFollowing(); } } // UserProfileActivity.java public class UserProfileActivity extends AppCompatActivity { ... @Override protected void onCreate(Bundle savedInstanceState) { ... profileViewModel = ViewModelProviders.of(this).get(ProfileViewModel.class); toggleButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { profileViewModel.toggleFollowing(); }}); observeProfile(); } private void observeProfile() { profileViewModel.getFollowStatus().observe(this, new Observer<FollowStatus>() { @Override public void onChanged(final FollowStatus followStatus) { toggleButton.setBackground(followStatus.buttonColor); toggleButton.setText(followStatus.buttonText); followersText.setText(followStatus.numberOfFollowers + " Followers"); }}); }}Copy the code

All right, let’s look at the code. The Activity creates a ProfileViewModel, which is designed to manage and store UI-related data (or delegate storage to other classes). The great thing about this ViewModel (note that the base class belongs to the Android architecture component) is that it is retained for the life of the Activity, meaning that it will remain until the Activity is permanently gone, that is, destroyed. Once the ProfileViewModel is obtained, the Activity starts to observe changes in the data. That’s the magic of LiveData. The Viewmodel returns LiveData, which is an observable class, making our Activity the observer. As with event-based solutions, when the data is changed, we change the user interface accordingly. In our example, the Viewmodel gets its return value from the UserRepository class, which keeps an instance of LiveData wrapped with a data holder FollowStatus. For brevity, I’ve made the repository memory based rather than persistent data. When the user clicks the Follow/Unfollow button, the code calls the View model’s toggleFollowing method, which in turn calls UserRepository. Once the repository changes the FollowStatus value stored in its LiveData instance, the Activity’s onChanged code will be called again because the Activity watches FollowStatus and waits for the data to change. This is how the data change <-> user interface change cycle works in LiveData.

What’s new about LiveData is that it is lifecycle aware. In our case, it knows the life cycle of the instance we gave it when we started watching it, the life cycle of the Activity. This means that LiveData will send an “on Changed Event” only when the Activity is in an active lifecycle state. For example, if the Activity is in the background, it will not be notified of data changes until it is visible to the user again. This means that there are no more crashes caused by Activity stops. Moreover, since the LiveData observability controls event firing, we do not need to handle the life cycle manually. This ensures that the UI component is always up to date when using LiveData, even if it becomes inactive at some point because it receives the latest data when it becomes active again.

LiveData is so powerful that some people use it to implement an event bus mechanism. In addition, LiveData is supported by the new SQLite persistence library, Room, which is available as part of the Android architecture component. This means that we can save LiveData objects to the database and observe them as normal LiveData later. This allows us to store data in one part of the code and have code in another part of the code watch its data change. We can extend our UserRepository to use Room and persist data, but I don’t want to overextend this example.

Summary

After reviewing the different approaches to the same task, we can think of LiveData as a hybrid of interface listeners and event-based solutions, extracting the best from each solution. As a rule of thumb, I recommend using (or switching to) LiveData in almost every situation where other alternatives are considered (or already used), especially in all scenarios where we want to update the user interface based on data changes in a clean, robust, and reasonable way.

I hope you’ve gained some knowledge from this article about LiveData, where it can help, how to use it, and why it might be a better solution than other existing approaches. Any other ideas? Is there a better solution? Please feel free to comment.

When to load data in ViewModels

Recently, I had an unexpectedly long discussion about an apparently simple question. In our code, exactly where should we trigger the loading of ViewModel data. There are many possible options, but let’s take a look at a few.

Architectural components were introduced to the Android world more than two years ago to improve the way we develop applications. A core part of these components is the ViewModel with LiveData, an observable life-cycle aware data holder that connects the Activity to the ViewModel. ViewModel outputs data, Activities consume data.

This part is clear and won’t cause much discussion, but the ViewModel must load, subscribe, or trigger its data to load at some point. The question is when that should happen.

Our Use Case

For our discussion, let’s use a simple use case to load a contact list in our ViewModel and publish it using LiveData.

class Contacts(val names: List<String>) data class Parameters(val namePrefix: String = "") class GetContactsUseCase { fun loadContacts(parameters: Parameters, onLoad: (Contacts) -> Unit) { /* Implementation detail */ } } class ContactsViewModel(val getContactsUseCase: GetContactsUseCase) : ViewModel() { // TODO When to call getContactsUseCase.loadContacts? fun contacts(parameters: Parameters): LiveData<Contacts> { TODO("What to return here?" )}}Copy the code

What do we want

In order to have some criteria for evaluation, let’s first assume our requirements for efficient loading techniques.

  • Take advantage of the ViewModel to load only as needed, decoupled from life cycle changes and configuration changes.
  • Easy to understand and implement, using a clean code architecture.
  • Small API to reduce the knowledge required to use the ViewModel.
  • It is possible to provide parameters. A ViewModel often needs to accept parameters to load its data.

❌ Bad: Calling a method

This is a widely used concept, popularized even in the case of Google Blueprints, but it has big problems. The method needs to be called from somewhere, and this usually ends up in one of the Activity or Fragment’s lifecycle methods.

class ContactsViewModel(val getContactsUseCase: GetContactsUseCase) : ViewModel() {
  private val contactsLiveData = MutableLiveData<Contacts>()

  fun loadContacts(parameters: Parameters) {
    getContactsUseCase.loadContacts(parameters) { contactsLiveData.value = it }
  }

  fun contacts(): LiveData<Contacts> = contactsLiveData
}
Copy the code
  • ➖ We reload on each rotation, losing the benefits of decoupling from the Activity/Fragment life cycle because they have to be called from onCreate() or some other life cycle method.
  • ➕ is easy to implement and understand.
  • ➖ has a trigger method.
  • ➖ introduces the implicit condition that parameters are always the same for the same instance. The loadContacts() and contacts() methods are coupled.
  • ➕ easily provides parameters.

❌ Bad: Start in ViewModel constructor

We can easily ensure that data is loaded only once by triggering loading in the ViewModel constructor. This approach is also shown in the documentation.

class ContactsViewModel(val getContactsUseCase: GetContactsUseCase) : ViewModel() {
  private val contactsLiveData = MutableLiveData<Contacts>()

  init {
    getContactsUseCase.loadContacts(Parameters()) { contactsLiveData.value = it }
  }

  fun contacts(): LiveData<Contacts> = contactsLiveData
}
Copy the code
  • ➕ we only load data once.
  • ➕ is easy to implement.

The entire public API is a method contacts()

  • ➖ cannot provide arguments for the load function.
  • ➖ We work in the constructor.

✔ ️ Better: Lazy field

We can use Kotlin’s lazy delegate attribute capability, as shown in the following code.

class ContactsViewModel(val getContactsUseCase: GetContactsUseCase) : ViewModel() {
  private val contactsLiveData by lazy {
    val liveData = MutableLiveData<Contacts>()
    getContactsUseCase.loadContacts(Parameters()) { liveData.value = it }
    return@lazy liveData
  }

  fun contacts(): LiveData<Contacts> = contactsLiveData
}
Copy the code
  • ➕ We only load LiveData the first time we access it.
  • ➕ is easy to implement.

The entire public API is a method contacts(). In addition to adding a state, it is not possible to provide a parameter to the loading function, which must be set before accessing the contactsLiveData field.

✔ ️ Good: Lazy Map

We can use lazyMap or similar lazy init depending on the parameters provided. When the parameters are strings or other immutable classes, it is easy to use them as Map keys to get the LiveData corresponding to the supplied parameters.

class ContactsViewModel(val getContactsUseCase: GetContactsUseCase) : ViewModel() {
  private val contactsLiveData: Map<Parameters, LiveData<Contacts>> = lazyMap { parameters ->
    val liveData = MutableLiveData<Contacts>()
    getContactsUseCase.loadContacts(parameters) { liveData.value = it }
    return@lazyMap liveData
  }

  fun contacts(parameters: Parameters): LiveData<Contacts> = contactsLiveData.getValue(parameters)
}
Copy the code

The definition of lazyMap is shown below.

fun <K, V> lazyMap(initializer: (K) -> V): Map<K, V> {
  val map = mutableMapOf<K, V>()
  return map.withDefault { key ->
    val newValue = initializer(key)
    map[key] = newValue
    return@withDefault newValue
  }
}
Copy the code
  • ➕ We only load LiveData the first time we access it.
  • ➕ is fairly easy to implement and understand.

The entire public API is a method contacts()

  • ➕ we can provide arguments, and the ViewModel can even handle multiple arguments simultaneously.
  • ➖ still retains some mutable state in the ViewModel.

✔️ Good: Library method — Lazy onActive() case

When using Room or RxJava, they have adapters to create LiveData directly in the @DAO object, using the extensibility method of publiser.toliveData (), respectively.

The implementations of both libraries ComputableLiveData and PublisherLiveData are lazy, meaning they work when the livedata.onActive () method is called.

class GetContactsUseCase {
  fun loadContacts(parameters: Parameters): Flowable<Contacts> { /* Implementation detail */ }
}

class ContactsViewModel(val getContactsUseCase: GetContactsUseCase) : ViewModel() {
  fun contacts(parameters: Parameters): LiveData<Contacts> {
    return getContactsUseCase.loadContacts(parameters).toLiveData()
  }
}
Copy the code
  • ➕ We load data lazily only when the life cycle is active.
  • ➖ loading is still coupled to the life cycle because livedata.onactive () basically means (onStart() with an observer).

Easy to implement and use support libraries. The entire public API is a method contacts()

In this case, we created a new LiveData for each method call, and to avoid this, we had to resolve the problem that the parameters might be different. Lazy Map can help here. Here’s an example.

✔️ Good: Pass the parameters in constructor

In the previous case, we used the LazyMap option just to be able to pass parameters, but in many cases, an instance of the ViewModel always has the same parameters.

It is much better to pass the parameters to the constructor and use lazy loading or start loading in the constructor. We can use ViewModelProvider. Factory to do this, but it will have some questions.

class ContactsViewModel(val getContactsUseCase: GetContactsUseCase, parameters: Parameters) : ViewModel() {
  private val contactsLiveData: LiveData<Contacts> by lazy {
    val liveData = MutableLiveData<Contacts>()
    getContactsUseCase.loadContacts(parameters) { liveData.value = it }
    return@lazy liveData
  }

  fun contacts(parameters: Parameters): LiveData<Contacts> = contactsLiveData
}

class ContactsViewModelFactory(val getContactsUseCase: GetContactsUseCase, val parameters: Parameters)
  : ViewModelProvider.Factory {
  override fun <T : ViewModel> create(modelClass: Class<T>): T {
    return ContactsViewModel(getContactsUseCase, parameters) as T
  }
}
Copy the code
  • ➕ we only load data once.
  • ➖ is not easy to implement and understand and requires templates.

The entire public API is a method contacts()

  • The ➕ViewModel takes arguments in the constructor and is unchangeable and testable.

This requires extra code to hook the ViewModelFactory so that we can pass dynamic parameters. At the same time, we started having problems with other dependencies, and we needed to figure out how to pass them into the factory along with parameters to generate more templates.

Assisted Injection is trying to address this problem, as Jake Wharton addressed in his talk at Droidcon London 2018. However, there is still some template code, so even though this may be the “perfect” solution, it may be more suitable for your team than the other options.

Which approach to choose

The introduction of architectural components greatly simplifies Android development and solves many problems. Still, there are a few issues, and here we discuss loading ViewModel data and evaluating various options.

Based on my experience, I recommend the LazyMap approach because I find it has a good balance of pros and cons and is really easy to adopt. You can find examples here.

Github.com/jraska/gith…

As you can see, there is no perfect solution, and it’s up to your team to choose the approach that works best for you, balancing robustness, simplicity, and consistency across the project. Hopefully this article will help you choose. Happy coding!

When NOT to Use LiveData

If you are familiar with Android development, I have no doubt that you have heard of architectural components and may even have used them in your projects. There have been several articles on when and how to use them, but I feel there isn’t enough emphasis on when not to use them, especially considering that Google’s Application Architecture guide treats them as a fairly generic tool that can be used at all levels of your architecture. Therefore, there must be a temptation to try to make the most of them 🙂

In this article, I’ll talk about situations in which I don’t recommend using LiveData and the alternatives you can use. This post was inspired by a talk at the android Development Summit ’18 that I found very new and interesting.

1. You have backpressure in your app.

If you have an actual Stream that may have backpressure problems, LiveData will not solve your problem. The reason is that LiveData doesn’t support it. The purpose of LiveData is to push the latest values to the UI while the observer is/is active. You can use RX Flowable or Kotlin’s Flow to handle this correctly. The picture below shows the correct handling of back pressure. In the case you use LiveData, the values of 9,10,11 will be discarded to provide the latest values. Here’s a link to Google’s problem tracker to confirm that LiveData can’t handle back pressure.

2. You need to use a lot of operators on data.

Even if LiveData provides tools like Transformations, only Map and switchMap can help you perform Transformations right out of the box. If you want something more advanced, and maybe chained, LiveData doesn’t provide this kind of data manipulation. Therefore, the best way to handle this requirement is not to use LiveData as a producer, but to use the RX type or Kotlin, because Kotlin supports a variety of higher-order functions and extensions to Collections and sequences. Here are some examples of how many templates can be avoided using higher-order functions in Kotlin.

fruits
    .filter { it.type == "apple" }
    .firstOrNull { it.color == "orange" }
Copy the code

Here is an example of a switchMap transformation.

val result: LiveData<String> = Transformations
    .switchMap(firstRepo.getData(), {
        if (it.type == "apple") {
            MutableLiveData().apply { setValue(it) }
        } 
    })
Copy the code

3. You don’t have INTERACTIONS with data.

There is no point in using lifecycle aware components if you are not propagating data to the user interface. The primary purpose of LiveData is to maintain data state throughout the life cycle of a component. If data is only cached or synchronized in the background, you can use callbacks, RX types or other types or asynchronous operations.

4. You have one-shot asynchronous operations.

There is no need to use LiveData if you don’t need to observe changes in data and propagate them to a life-cycle aware user interface (as we discussed in #3). You can use RX or Kotlin’s Coroutines for more control over operator and thread control. LiveData does not provide complete control over your thread management. LiveData basically has two options: synchronous updates or publishing asynchronous values from a worker thread. This can also be an advantage if you don’t need complete control and just know that changes will hit the UI thread without any additional thread switching steps, which sounds like an advantage in some cases.

5. You don’t need to persist cached data into the UI.

All of the benefits of using LiveData are in some way related to the awareness of the life cycle of LiveData. It allows you to persist UI state through configuration changes. If persistent data is not required, LiveData will not serve its purpose in your use case.

We’ve outlined some of the use cases where using LiveData is unreasonable and may even limit your functionality and extensibility. It’s easier to use things according to their purpose, allowing you to maximize the advantages they offer. LiveData was deliberately created as a data holder, keeping data with configuration changes, and taking full advantage of its lifecycle awareness will bring a lot of benefits to your Android project, but expecting more than it has to offer can put you in a spoon-eating steak situation 🙂 coding pleasure 🙂

Original link:

Proandroiddev.com/when-and-wh…

Proandroiddev.com/when-to-loa…

Proandroiddev.com/when-not-to…