preface

Recently very not smooth, every night go home to hit a car! It was easy to get a car at 10 p.m., didn’t we say 996? Is everyone enjoying the news, and the driver is too busy? What the hell, even if I can’t get a taxi, I will study, after all, a day without studying I feel sick!

Previous articles have talked about the basic use and principles of LiveData and ViewModel in JatPack. The historical articles are as follows:

A little bit into the pit of JetPack: ViewModel

A bit into the pit of JetPack: Lifecycle

A little bit into the pit of JetPack: LiveData

A little bit into the pit JetPack: NetworkBoundResource for actual foreplay

A little bit into the pit JetPack (final chapter) : Actual COMBAT MVVM

Today we’re going to look at some practical applications. Actual combat preliminary plan two articles, respectively:

  • A simple network framework written by Google Sample: NetworkBoundResource.
  • MVVM project actual combat

NetworkBoundResource article

What is NetworkBoundResource

First of all, let’s talk about NetworkBoundResource. Actually, NetworkBoundResource is simply a class, which is only 100+ lines. Is a fairly efficient network processing logic specification combined with LiveData in the Google Sample.

But this class, combined with LiveData, creates extremely convenient common network features such as:

  • Do not request the network, use the cache directly
  • Custom policy, whether to request network
  • Cache is used after network loading fails
  • Return type processing
  • , etc.

Don’t say, directly on the code! Let’s take a look at some uses of this class:

// The UI layer calls this method directly, gets the LiveData, and listens to it.
fun loadData(queryId: Long = - 1): LiveData<Resource<DataResp>> {
        return object :
            NetworkBoundResource<DataResp, DataResp>(
                appExecutors
            ) {

            override fun saveCallResult(item: DataResp) {
                // This method is called when the network data comes back. We can do some persistence logic
            }

            override fun shouldFetch(data: DataResp?).: Boolean {
	            If false, then loadFromDb() is called
                return isUseNetWork
            }

            override fun loadFromDb(a): LiveData<MusicStoreMainResp> {
	            // Implement your own logic for fetching data from non-network environments (e.g. memory, DB)
                return data
            }

            override fun createCall(a): LiveData<ApiResponse<DataResp>> {
                return 
        }.asLiveData()
    }
Copy the code

We can see that the four implementation methods correspond to:

  • The persistence callback after the data is returned
  • Whether to make a network request
  • Request data locally (implemented by the business side)
  • Network request (implemented by the business side)

For our business side, we just need to call oadData(), then observe(), and return LiveData.

Of course, the corresponding real business request needs to be implemented

NetworkBoundResource flow chart

So NetworkBoundResource abstracts a bunch of logic for us, and the implementation is pretty short, so let’s look at the code, what does NetworkBoundResource do? To help us accomplish all this logic so easily?

Three, NetworkBoundResource source code implementation

On the source code:

For details, please refer to Google Sample: github.com/googlesampl…

abstract class NetworkBoundResource @MainThread constructor(private val appExecutors: AppExecutors) {
// Here is the data available to the business, liveData
//MediatorLiveData
private val result = MediatorLiveData<Resource<ResultType>>()
init {
    // Send a LOADIN to notify the LOADING state
    result.value = Resource.loading(null)
    @Suppress("LeakingThis")
    // Db is also a data source
    val dbSource = loadFromDb()
    result.addSource(dbSource) { data ->
        // The first callback of db is used to determine the validity of the data
        result.removeSource(dbSource)
        // Whether it is valid is defined by the business (request network policy)
        if (shouldFetch(data)) {
            fetchFromNetwork(dbSource)
        } else {
            // The data is valid and the observer will immediately receive a callback {Source. Plug}
            result.addSource(dbSource) { newData ->
                setValue(Resource.success(newData))
            }
        }
    }
}
@MainThread
private fun setValue(newValue: Resource<ResultType>) {
    if(result.value ! = newValue) { result.value = newValue } }private fun fetchFromNetwork(dbSource: LiveData<ResultType>) {
    val apiResponse = createCall()
    // Add dbSource again and it will quickly send its latest value. The DB has data, but the data is expired. The data is called back to the service first
    // In LOADING mode, data can also be obtained and displayed to the user.
    result.addSource(dbSource) { newData ->
        setValue(Resource.loading(newData))
    }
    result.addSource(apiResponse) { response ->
        // This is the source used to control the flow, remove, avoid data chaos, and designers do not let add duplicate source
        result.removeSource(apiResponse)
        result.removeSource(dbSource)
        when (response) {
            is ApiSuccessResponse -> {
                appExecutors.diskIO.execute {
                    // Save the data back to the cache, so that the next time we request to come, we can ensure that the LOADING state of the data is up to date.
                    saveCallResult(processResponse(response))
                    appExecutors.mainThread.execute {
                        // originally annotate: we specially request a new live data,
                        // otherwise we will get immediately last cached value,
                        // which may not be updated with latest results received from network.
                        // Re-read from the library
                        result.addSource(loadFromDb()) { newData ->
                            setValue(Resource.success(newData))
                        }
                    }
                }
            }
            is ApiEmptyResponse -> {
                appExecutors.mainThread.execute {
                    // reload from disk whatever we had
                    result.addSource(loadFromDb()) { newData ->
                        setValue(Resource.success(newData))
                    }
                }
            }
            is ApiErrorResponse -> {
                onFetchFailed()
                result.addSource(dbSource) { newData ->
                    setValue(Resource.error(response.exception, newData))
                }
            }
        }
    }
}
// An abstract method handled by the business side
protected open fun onFetchFailed(a) {}
fun asLiveData(a) = result as LiveData<Resource<ResultType>>
@WorkerThread
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.data
@WorkerThread
protected abstract fun saveCallResult(item: RequestType)
@MainThread
protected abstract fun shouldFetch(data: ResultType?).: Boolean
@MainThread
protected abstract fun loadFromDb(a): LiveData<ResultType>
@MainThread
protected abstract fun createCall(a): LiveData<ApiResponse<RequestType>>
}
Copy the code

The end of the

This part, I suggest you understand. Because it’s really, really, really easy to use, it’s designed to incorporate a series of clever applications from LiveData. After understanding, you will definitely have a deeper understanding of LiveData, and in the following MVVM, you will also feel the ingenuity and refreshing.

The next actual combat, is basically combined with NetworkBoundResource MVVM design, hope to give you in the business architecture to bring help.

I am a fresh graduate, recently and friends to maintain a public number, content is we in the transition from fresh graduate to development of this road to step on the pit, as well as our step by step learning records, if interested in friends can pay attention to, together with the ~