问题
This is the case:
I have the domain layer to provide data fetching interface for business logics, and I have 2 data sources: local database and remote network.
It works like this:
- Request all users:
DataRepository.getInstance().getUsers();
- In DataRepository, there are 2 sources:
LocalDataSource.getUsers()
which fetches all users from local database, if there is no data then ignore this request.RemoteDataSource.getUsers()
which requests latest user list from our server(Even if there is data in local database, for the purpose of keeping data updated), when data requested then save or update it to local database and send back the results.
I know that I may achieve my goal by doing this in DataRepository
:
public Observable<List<User>> getUsers() {
return Observable.create(new Observable.OnSubscribe<List<User>>() {
@Override
public void call(Subscriber<? super List<User>> subscriber) {
// 1. Request users from local database
List<User> localUsers = mLocalDataSource.getUsers();
if (!localUsers.isEmpty()) {
subscriber.onNext(localUsers);
}
// 2. Request the latest user list from server
// Send a retrofit2 request
Call<List<User>> call = mRemoteDataSource.getUsers();
try {
List<User> networkUsers = call.execute().body();
mLocalDataSource.saveUsers(networkUsers);
subscriber.onNext(networkUsers);
subscriber.onCompleted();
} catch (IOException e) {
subscriber.onError(e);
}
}
});
}
Now thinking that I already used rxjava in the project, why not using SqlBrite and Retrofit2 RxAdapters to do this for more convenience?
So LocalDataSource.getUsers()
now returns Observable<List<User>>
and so does RemoteDataSource.getUsers()
.
LocalDataSource.java
public Observable<User> getUsers() {
final String sqlQuery = String.format("SELECT * FROM %s", UserTable.TABLE_NAME);
return mDatabaseHelper.createQuery(UserTable.TABLE_NAME, sqlQuery)
.mapToList(new Func1<Cursor, User>() {
@Override
public User call(Cursor c) {
return UserTable.parseCursor(c);
}
});
}
RemoteDataSource.java
public Observable<List<User>> getUsers() {
return mRetrofitApi.users();
}
Question:
What should I do in DataRepository.getUsers()
to achieve the same thing I did with the old tricks?
public Observable<List<User>> getUsers() {
Observable<List<User>> localUsers = mLocalDataSource.getUsers();
Observable<List<User>> remoteUsers = mRemoteDataSource.getUsers()
.flatMap(new Func1<List<User>, Observable<User>>() {
@Override
public Observable<User> call(List<User> users) {
return Observable.from(users);
}
})
.doOnNext(new Action1<User>() {
@Override
public void call(User user) {
mLocalDataSource.saveUser(user);
}
})
.toList();
// What should I return to make two observables both able to emit results to the Subscriber
return Observable.concat(localUsers, remoteUsers); // ???
}
回答1:
What should I do in DataRepository.getUsers() to achieve the same thing I did with the old tricks?
In such case, you can use concat
:
public Observable<List<User>> getUsers() {
return Observable.concat(localUsers.first(), remoteUsers);
}
But if it doesn't matter whether local or remote result comes first, you can use merge
:
public Observable<List<User>> getUsers() {
return Observable.merge(localUsers.first(), remoteUsers);
}
In addition, if you only just want one result (quicker one wins), you can use amb
:
public Observable<List<User>> getUsers() {
return Observable.amb(localUsers.first(), remoteUsers);
}
来源:https://stackoverflow.com/questions/37569906/how-to-handle-multiple-data-sources-with-rxjava