/
NetworkBoundResource.java
132 lines (117 loc) · 4.91 KB
/
NetworkBoundResource.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package ivankuo.com.itbon2018.data;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MediatorLiveData;
import android.arch.lifecycle.Observer;
import android.os.AsyncTask;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import ivankuo.com.itbon2018.api.ApiResponse;
import ivankuo.com.itbon2018.data.model.Resource;
/**
* A generic class that can provide a resource backed by both the sqlite database and the network.
* <p>
* You can read more about it in the <a href="https://developer.android.com/arch">Architecture
* Guide</a>.
* @param <ResultType> : Type for the Resource data
* @param <RequestType> : Type for the API response
*/
public abstract class NetworkBoundResource<ResultType, RequestType> {
private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();
@MainThread
NetworkBoundResource() {
result.setValue(Resource.<ResultType>loading(null));
final LiveData<ResultType> dbSource = loadFromDb();
result.addSource(dbSource, new Observer<ResultType>() {
@Override
public void onChanged(@Nullable ResultType data) {
result.removeSource(dbSource);
if (shouldFetch(data)) {
fetchFromNetwork(dbSource);
} else {
result.addSource(dbSource, new Observer<ResultType>() {
@Override
public void onChanged(@Nullable ResultType newData) {
result.setValue(Resource.success(newData));
}
});
}
}
});
}
private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
final LiveData<ApiResponse<RequestType>> apiResponse = createCall();
// we re-attach dbSource as a new source, it will dispatch its latest value quickly
result.addSource(dbSource, new Observer<ResultType>() {
@Override
public void onChanged(@Nullable ResultType newData) {
result.setValue(Resource.loading(newData));
}
});
result.addSource(apiResponse, new Observer<ApiResponse<RequestType>>() {
@Override
public void onChanged(@Nullable final ApiResponse<RequestType> response) {
result.removeSource(apiResponse);
result.removeSource(dbSource);
//noinspection ConstantConditions
if (response.isSuccessful()) {
saveResultAndReInit(response);
} else {
onFetchFailed();
result.addSource(dbSource, new Observer<ResultType>() {
@Override
public void onChanged(@Nullable ResultType newData) {
result.setValue(Resource.error(newData, response.errorMessage));
}
});
}
}
});
}
@MainThread
private void saveResultAndReInit(final ApiResponse<RequestType> response) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
saveCallResult(response.body);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
// 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.
result.addSource(loadFromDb(), new Observer<ResultType>() {
@Override
public void onChanged(@Nullable ResultType newData) {
result.setValue(Resource.success(newData));
}
});
}
}.execute();
}
// Called when the fetch fails. The child class may want to reset components
// like rate limiter.
protected void onFetchFailed() {
}
// returns a LiveData that represents the resource
public LiveData<Resource<ResultType>> asLiveData() {
return result;
}
// Called to get the cached data from the database
@NonNull
@MainThread
protected abstract LiveData<ResultType> loadFromDb();
// Called with the data in the database to decide whether it should be
// fetched from the network.
@MainThread
protected abstract boolean shouldFetch(@Nullable ResultType data);
// Called to create the API call.
@NonNull
@MainThread
protected abstract LiveData<ApiResponse<RequestType>> createCall();
// Called to save the result of the API response into the database
@WorkerThread
protected abstract void saveCallResult(@NonNull RequestType item);
}