What is LiveData?

  • If you don’t know, click here.

How to use LiveData?

  • If you don’t know, click here.

What’s wrong with LiveData?

  • That’s a good question! Take a look at this code:

MainActivity code:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mainViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MainViewModel::class.java) mainViewModel.userListLiveData.observe(this, The Observer {list - > / / processing receive successful data}) mainViewModel. ErrorLiveData. Observe (this, The Observer {throwable - > / / processing receive failure error}) mainViewModel. LoadingLiveData. Observe (this, Observer {isLoading -> // Processing the received wait animation})}}Copy the code

ViewModel code:

class MainViewModel : ViewModel() {

    val userListLiveData = MutableLiveData<List<User>>()

    val errorLiveData = MutableLiveData<Throwable>()

    val loadingLiveData = MutableLiveData<Boolean>()

    fun findUserByDepartment(departmentId: String) {
        viewModelScope.launch {
            try {
                loadingLiveData.postValue(true)
                val user = UserRepository.findUserByDepartment(departmentId)
                loadingLiveData.postValue(false)
                userListLiveData.postValue(user)
            } catch (throwable: Throwable) {
                errorLiveData.postValue(throwable)
            }
        }
    }

}
Copy the code
  • Problem with the above code: If we need to display loading, after loading, loading success and loading failure effects on the interface, we need to define many LiveData to achieve this, which is very messy and ugly.

My solution

  • Customize a LiveData that can handle loading, loading success and loading failure interfaces at the same time.
  • Kotlin coroutine, RxJava can be very good support.
  • Perfect support for the ROOM + Paging solution in JetPack.
  • No need to switch threads frequently.

1. Introduce dependencies

① Add the following code to the build.gradle file in the root directory of your project, ignore it if it already exists

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
Copy the code

② Add the following dependencies to your build.gradle file:

Implementation 'com. Gitee. Numeron. Stateful: livedata: 1.0.0'Copy the code

2. Usage

The above code can be written as:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mainViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MainViewModel::class.java) mainViewModel.userListLiveData.observe(this, Observer {stateful -> stateful. OnSuccess {list -> // container was started}. OnLoading {message, progress -> // Process receiving wait animation}})}}Copy the code
class MainViewModel : ViewModel() { val userListLiveData = StatefulLiveData<List<User>>() fun findUserByDepartment(departmentId: String) {viewModelScope. Launch {try {userListLiveData. PostLoading (" is loading the user list for the door..." ) val userList = UserRepository.findUserByDepartment(departmentId) userListLiveData.postSuccess(userList) } catch (throwable: Throwable) { userListLiveData.postFailure(throwable) } } } }Copy the code

With StatefulLiveData, only one LiveData can be declared, but data in multiple states can be processed at the same time. And instead of switching threads in the ViewModel, we just start a coroutine, process data in it, and then throw it to StatefulLiveData, tell it where we are in the process of processing the data, Finally, observe StatefulLiveData in the View layer and handle the interfaces in various states separately.

3. Exception handling

To make StatefulLivedData easier and more convenient, the library also provides an exception handling tool. Using these tools, the code in MainViewModel can be written in the following form:

class MainViewModel : ViewModel() { val userListLiveData = StatefulLiveData<List<User>>() fun findUserByDepartment(departmentId: String) { viewModelScope.launch(StatefulExceptionHandler(userListLiveData)) { UserListLiveData. PostLoading (" is loading the user list for the door..." ) val userList = UserRepository.findUserByDepartment(departmentId) userListLiveData.postSuccess(userList) } } }Copy the code

Instead of wrapping the code in a try/catch by hand, the StatefulExceptionHandler submits the exception to StatefulLiveData when it does occur, and we can write our code more neatly. Of course, you can define an exception handler yourself, modeled after StatefulExceptionHandler, and pass it in as a coroutine context when the coroutine is turned on.

4. Deliver progress

When downloading a large file or retrieving a large amount of data, we can introduce a progress bar to let the user know where we are. In this case, we can pass a Float value to the View layer through the postLoading method of StatefulLiveData.

class MainViewModel : ViewModel() { val userListLiveData = StatefulLiveData<List<User>>() fun findAllUser() { Viewmodelscope.launch (StatefulExceptionHandler(userListLiveData)) {// Notify the View layer to wait, The current progress of 0 userListLiveData. PostLoading (" being loaded all user list..." Val, 0 f) / / get all department departmentList = UserRepository. AllDepartment () departmentList / / for each department of the user. The mapIndexed {index, Department -> // Calculate progress, And make the View layer val progress = (index + 1)/departmentList size. ToFloat () userListLiveData. PostLoading (" user list is loading all..." . Progress) / / access department under all users UserRepository findUserByDepartment (department. Id)} / / will List < List < User > > is converted to a List < User >. Flatten () / / will List < User > to submit to the StatefulLiveData. Let (userListLiveData: : postSuccess) / / send a message to the View layer UserListLiveData. PostMessage (" all user load success!" )}}}Copy the code

As mentioned above, StatefulLiveData has a postLoading overloaded method that takes a String to send a statement to the View layer and a Float to inform the View layer of the current progress. Once we’ve successfully retrieved the data, we’ve added a line of code to call the postMessage method, which we’ll start to explain.

5. Status monitoring

At the same time, in order to deal with different states of data more clearly, a StatefulObserver is provided, which is an implementation class of the Observer. It is used to process various states received by StatefulLiveData.

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mainViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MainViewModel::class.java) mainViewModel.userListLiveData.observe(this, StatefulObserver(UserListStatefulCallback())) } private inner class UserListStatefulCallback : StatefulCallback<List<User>> { override fun onSuccess(value: List<User>) {override fun onLoading(message: String, progress: Float) {override fun onFailure(message: String, cause: Override fun onMessage(message: String) {override fun onMessage(message: String) {override fun onMessage(message: String) {Copy the code

StatefulObserver receives a StatefulCallback parameter. StatefulCallback requires the same generic parameter as StatefulLiveData when instantiated. There are four methods that need to be implemented

  • OnSuccess, which is used to process data received successfully;
  • OnLoading, to handle waiting animation or progress;
  • OnFailure, handling receiving failures
  • OnMessage, the message sent via postMessage;

The onMessage method is optional, depending on the business requirements. All four of these methods run on the main thread, which is inherent in LiveData.

6. The Paging Paging

If you are using ROOM + Paging, you must be writing code like this:

class MainViewModel : ViewModel() {

    val userPagedListLiveData = UserRepository.userSourceFactory().toLiveData(Config(20))
    
}
Copy the code

It’s convenient to declare an instance of LiveData with just one line of code. Considering how convenient StatefulLiveData is for managing data state, how about using them together? Of course! You can do this by adding a line of code at the end:

val userPagedListLiveData = UserRepository.userSourceFactory().toLiveData(Config(20)).toStateful()
Copy the code

Note that although it is not indicated here, Android Studio will tell you that userPagedListLiveData is already of type StatefulLiveData<PagedList>. StatefulLiveData was ejbateful (), which was StatefulLiveData ejbateful (). Ejbateful () was StatefulLiveData ejbateful ().

conclusion

The above is the use method of StatefulLiveData extracted from my work, and there are some other extracted components, please click on my Github for details.