Trigger a second query from LiveData and merge results (Firestore)? - android

I have two collections: Users and Books. I need to get the results of both of them whether Users OR Books is updated and then merge the results together into a LinkedHashMap to use as a listView menu.
I thought a MediatorLiveData would be the way to go, but if I put the query of Users and the Query of Books in then I get null from one of the two LiveData objects because only one or the other fires. I thought maybe if one of them fires, then perhaps I have a query run inside each addSource() in the MediatorLiveData, but I'm not sure if that's the way to go.
My post regarding the MediatorLiveData is here:
Using MediatorLiveData to merge to LiveData (Firestore) QuerySnapshot streams is producing weird results
My two queries and LiveData objects are as follows:
//getUsers query using FirebaseQueryLiveData class
private Query getUsersQuery() {
FirebaseAuth mAuth = FirebaseAuth.getInstance();
adminID = mAuth.getUid();
query = FirebaseFirestore.getInstance().collection("admins")
.document(adminID)
.collection("users")
return query;
}
private FirebaseQueryLiveData usersLiveData = new FirebaseQueryLiveData(getUsersQuery());
//getBooks query using FirebaseQueryLiveData class
private Query getBooksQuery () {
FirebaseGroupID firebaseGroupID = new FirebaseGroupID(getApplication());
groupID = firebaseGroupID.getGroupID();
query = FirebaseFirestore.getInstance().collection("books")
.whereEqualTo("groupID", groupID)
return query;
}
private FirebaseQueryLiveData booksLiveData = new FirebaseQueryLiveData(getBooksQuery());
Somehow when Users updates, I need to get the data of Books as well and then merge them, but I also need this to happen if Books updates and then get the data of Users and merge them.
Any ideas would be greatly appreciated.
Additional Note/Observation
Okay, so I'm not completely ruling out a MediatorLiveData object. Certainly it allows me the listening of two different LiveData objects within the same method, however, I don't want to merge the two of them directly because I need to act on each liveData object individually. So as an example: usersLiveData fires because we create or modify a user, then I need to query books, get the results and merge users and books etc.
Below is my MediatorLiveData as it currently stands:
//MediatorLiveData merge two LiveData QuerySnapshot streams
private MediatorLiveData<QuerySnapshot> usersBooksLiveDataMerger() {
final MediatorLiveData<QuerySnapshot> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
mediatorLiveData.setValue(querySnapshot);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
mediatorLiveData.setValue(querySnapshot);
}
});
return mediatorLiveData;
}
Right now it's returning null results of the other LiveData source. Instead I need to query then merge. Any ideas on how to do this? There isn't much out there on this very thing.
I tried putting a query inside a Function that is called using a Transformations.map() but because of it be an asynchronous call, the return statement is being called before the query finishes.
Here's my attempt at the Function:
private class ListenUsersGetBooks implements Function<QuerySnapshot, LinkedHashMap<User, List<Book>>> {
#Override
public LinkedHashMap<User, List<Book>> apply(final QuerySnapshot input) {
userBookList = new LinkedHashMap<>();
getBooksQuery().get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
List<User> users = input.toObjects(User.class);
List<Book> books = task.getResult().toObjects(Book.class);
Log.d(TAG, "USERLIST! " + users);
Log.d(TAG, "BOOKLIST! " + books);
for (User user : users) {
bookList = new ArrayList<>();
for (Book book : books) {
if (user.getUserID().equals(book.getUserID())
&& book.getBookAssigned()) {
bookList.add(book);
}
else if (user.getAllBookID().equals(book.getBookID())) {
bookList.add(book);
}
}
userBookList.put(user, bookList);
}
Log.d(TAG,"OBSERVE userBookList: " + userBookList);
}
});
return userBookList;
}
}

Here's a simple version of what you could do, I hope it makes sense.
You're close with the MediatorLiveData. Instead of MediatorLiveData<QuerySnapshot> you probably want to use a custom object like this:
class MyResult {
public QuerySnapshot usersSnapshot;
public QuerySnapshot booksSnapshot;
public MyResult() {}
boolean isComplete() {
return (usersSnapshot != null && booksSnapshot != null);
}
}
Then in your observers, do something like this:
private MediatorLiveData<MyResult> usersBooksLiveDataMerger() {
final MediatorLiveData<MyResult> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
MyResult current = mediatorLiveData.getValue();
current.usersSnapshot = querySnapshot;
mediatorLiveData.setValue(current);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
MyResult current = mediatorLiveData.getValue();
current.booksSnapshot = querySnapshot;
mediatorLiveData.setValue(current);
}
});
return mediatorLiveData;
}
Then when you observe the combined live data:
usersBooksLiveDataMerger().observe(new Observer<MyResult>() {
#Override
public void onChanged(#Nullable MyResult result) {
if (result == null || !result.isComplete()) {
// Ignore, this means only one of the queries has fininshed
return;
}
// If you get to here, you know all the queries are ready!
// ...
}
});

I think I solved it. We were declaring a new MyResult object in each mediatorLiveData.addSource() method. Which meant that we were getting a new object for each QuerySnapshot so we would never get them to merge with each other.
Here's the update to MediatorLiveData:
private MediatorLiveData<MyResult> usersBooksLiveDataMerger() {
final MediatorLiveData<MyResult> mediatorLiveData = new MediatorLiveData<>();
final MyResult current = new MyResult();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
current.setUsersSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
current.setBooksSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
return mediatorLiveData;
}
Now I'm getting users and books in the observer in Activity! Now the only thing I need to do is transform (merge the data) into a LinkedHashMap, but I think I got that figured out. Thanks Sam!

So this is where I am with your suggestions Sam.
I added getter and setter methods to the MyResult class as it wasn't giving me access to the member variables in the observer otherwise:
public class MyResult {
QuerySnapshot usersSnapshot;
QuerySnapshot booksSnapshot;
//default constructor
public MyResult() {
}
public QuerySnapshot getUsersSnapshot() {
return usersSnapshot;
}
public void setUsersSnapshot(QuerySnapshot usersSnapshot) {
this.usersSnapshot = usersSnapshot;
}
public QuerySnapshot getBooksSnapshot() {
return booksSnapshot;
}
public void setBooksSnapshot(QuerySnapshot booksSnapshot) {
this.booksSnapshot = booksSnapshot;
}
public boolean isComplete() {
return (usersSnapshot != null && booksSnapshot != null);
}
}
Here's the MediatorLiveData and get method. I changed the MyResult class initialization to = new MyResult(); thinking there was an issue with using mediatorLiveData.getValue(); as the initialization and get method.
private MediatorLiveData<MyResult> usersBooksLiveDataMerger() {
final MediatorLiveData<MyResult> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
MyResult current = new MyResult();
current.setUsersSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
MyResult current = new MyResult();
current.setBooksSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
return mediatorLiveData;
}
public MediatorLiveData<MyResult> getUsersBooksLiveDataMerger() {
return usersBooksLiveDataMerger();
}
And finally the observer:
mainViewModel.getUsersBooksLiveDataMerger().observe(this, new Observer<MainViewModel.MyResult>() {
#Override
public void onChanged(#Nullable MainViewModel.MyResult myResult) {
if (myResult == null || !myResult.isComplete()) {
// Ignore, this means only one of the queries has fininshed
Log.d(TAG, "OBSERVE BLAH!!!!");
return;
}
// If you get to here, you know all the queries are ready!
// ...
List<Book> books;
List<User> users;
books = myResult.getBooksSnapshot().toObjects(Book.class);
users = myResult.getUsersSnapshot().toObjects(User.class);
Log.d(TAG, "OBSERVE MERGE users: " + users);
Log.d(TAG, "OBSERVE MERGE books: " + books);
}
});
Please note: I did do a null check in the mediatorLiveData, just took it out for testing purposes.
Somehow I need to trigger my books query if just my users is triggered AND I need to trigger my users query if just my books is triggered...I feel like there is a step before the MediatorLiveData that needs to happen so we can make sure one liveData triggers the other query. Does that make sense?

You can greatly simplify the usage by using my LiveDataZipExtensions https://gist.github.com/Benjiko99/d2e5406aab0a4a775ea747956ae16624
With them, you don't have to create an object to hold your combined result.
Example usage
val firstNameLD = MutableLiveData<String>().apply { value = "John" }
val lastNameLD = MutableLiveData<String>().apply { value = "Smith" }
// The map function will get called once all zipped LiveData are present
val fullNameLD = zip(firstNameLD, lastNameLD).map { (firstName, lastName) ->
"$firstName $lastName"
}

Related

Refresh data whit refreshLayout using livedata

i have a fragment in my app that i show two list of saparate data in it.i'm using from android architecture components to load my data.
Once the data is fetched from the network, I store it locally using Room DB and then display it on the UI using ViewModel that observes on the LiveData object (this works fine). However, I want to be able to have a refreshLayout which When Refreshing Occurs a refresh action and perform a network request to get new data from the API if and only if there is a network connection.The issue is when Refreshing Occurs data load from locate database and network together .
my question is :How do I manage to get data only from Network when refreshing data?
How do I manage to get data only from Network when refreshing data?
I've seen this question and it didn't help me...
my codes:
repository:
public NetworkResult<LiveData<HomeHealthModel>> getHomeHealth(String query) {
MutableLiveData<String> _liveError = new MutableLiveData<>();
MutableLiveData<HomeHealthModel> data = new MutableLiveData<>();
LiveData<List<GeneralItemModel>> liveClinics = App.getDatabase().getGeneralItemDAO().getTops(GeneralItemType.Clinics, GeneralItemType.TOP);
LiveData<List<GeneralItemModel>> liveDoctors = App.getDatabase().getGeneralItemDAO().getTops(GeneralItemType.Doctors, GeneralItemType.TOP);
setupService(_liveError); //request data from network
data.postValue(new HomeHealthModel(liveClinics, liveDoctors));
_liveError.postValue(String.valueOf(NetworkResponseType.LocaleData));
return new NetworkResult<>(_liveError, data);
}
my viewModel
public class HomeHealthVM extends ViewModel {
private MutableLiveData<String> queryLiveData;
private LiveData<String> networkErrors;
private LiveData<List<GeneralItemModel>> Clinics;
private LiveData<List<GeneralItemModel>> Doctors;
public HomeHealthVM(HealthRepository repository) {
queryLiveData = new MutableLiveData<>();
LiveData<NetworkResult<LiveData<HomeHealthModel>>> repoResult;
repoResult = Transformations.map(queryLiveData, repository::getHomeHealth);
LiveData<HomeHealthModel> model = Transformations.switchMap(repoResult, input -> input.data);
Doctors = Transformations.switchMap(model, HomeHealthModel::getDoctors);
Clinics = Transformations.switchMap(model, HomeHealthModel::getClinics);
networkErrors = Transformations.switchMap(repoResult, input -> input.error);
}
public void search(String queryString) {
queryLiveData.postValue(queryString);
}
public String lastQueryValue() {
return queryLiveData.getValue();
}
public LiveData<String> getNetworkErrors() {
return networkErrors;
}
public LiveData<List<GeneralItemModel>> getClinics() {
return Clinics;
}
public LiveData<List<GeneralItemModel>> getDoctors() {
return Doctors;
}
}
my fragment code:
private void setupViewModel() {
ViewModelFactory<HealthRepository> factory = new ViewModelFactory<>(new HealthRepository());
healthVM = ViewModelProviders.of(this, factory).get(HomeHealthVM.class);
healthVM.getNetworkErrors().observe(this, states -> {
try {
if (Integer.parseInt(states) != WarningDialogType.Success &&
Integer.parseInt(states) != WarningDialogType.Locale) {
stopLoading();
linerNoInternet.setVisibility(View.VISIBLE);
linerContent.setVisibility(View.GONE);
}
} catch (Exception e) {
stopLoading();
linerNoInternet.setVisibility(View.VISIBLE);
linerContent.setVisibility(View.GONE);
}
});
healthVM.getDoctors().observe(this, doctors -> {
if (doctors.size() > 0) {
doctorsAdapter.submitList(doctors);
stopLoading();
} else {
}
});
healthVM.getClinics().observe(this, clinics -> {
if (clinics.size() > 0) {
clinicsAdapter.submitList(clinics);
stopLoading();
} else {
conesClinics.setVisibility(View.GONE);
}
});
healthVM.search("");
}

How to perform long running Databse operation using RxJava2 till all the task executed and data inserted into Database in Android?

I'm new in RxJava. I have currently executed three API calls parallel which is independent of each other via Retrofit using Single.Zip Operator. On getting a successful response of all three API calls, I have to insert the data from all three APIs into Room database into Different entities which takes 20 seconds.
So I need to execute database operations inside Single.Zip operator. Because the logic is written inside onSuccess method running away before Database Operation performed.
I have tried to take separate Observer for performing database operation but didn't work.
public void callOfflineDataAPIs() {
setIsLoading(true);
Single<BaseResponse<ProductResponse>> single1 = getDataManager().getOfflineProductListApiCall(getDataManager().getLastTimeStampOfflineProductCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
Single<BaseResponse<LocationResponse>> single2 = getDataManager().getOfflineLocationListApiCall(getDataManager().getLastTimeStampOfflineLocationCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
Single<BaseResponse<OfflineMasterData>> single3 = getDataManager().getOfflineMasterDataListApiCall(getDataManager().getLastTimeStampOfflineMasterCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
DisposableSingleObserver<List<Boolean>> result = Single.zip(single3, single1, single2,
(offlineMasterDataBaseResponse, productResponseBaseResponse, locationResponseBaseResponse) -> {
List<Boolean> apiCalls = new ArrayList<>();
apiCalls.add(masterDataCRUDOperation(offlineMasterDataBaseResponse));
apiCalls.add(productDataCRUDOperation(productResponseBaseResponse));
apiCalls.add(locationDataCRUDOperation(locationResponseBaseResponse));
return apiCalls;
}).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribeWith(new DisposableSingleObserver<List<Boolean>>() {
#Override
public void onSuccess(List<Boolean> apiCalls) {
setIsLoading(false);
LogHelper.e(TAG, "DisposableSingleObserver- onSuccess");
boolean isSync = true;
for (int i = 0; i < apiCalls.size(); i++) {
if (!apiCalls.get(i)) {
isSync = false;
LogHelper.e(TAG, "DisposableSingleObserver- onSuccess- apiCalls.get(i)", i);
callOfflineDataAPIs();
break;
}
}
if (isSync) {
LogHelper.e(TAG, "IF-isSync");
if (BuildConfig.IS_CLIENT_BUILD) {
LogHelper.e(TAG, "IF-isSync-IS_CLIENT_BUILD-true");
getDataManager().setCurrentWarehouseKey(1);
getNavigator().onGoButtonClick();
} else {
LogHelper.e(TAG, "ELSE-isSync-IS_CLIENT_BUILD-false");
getWarehouseList();
}
}
}
#Override
public void onError(Throwable e) {
LogHelper.e(TAG, "DisposableSingleObserver- Throwable");
setIsLoading(false);
String errorMessage = new NetworkError(e).getAppErrorMessage();
getNavigator().exitApplicationOnError(errorMessage);
}
});
}
Logic written inside onSuccess Method execute once all DB Operation performed.
You can modify your code to something like:
DisposableSingleObserver<List<Boolean>> result = Single.zip(single3, single1, single2,
(offlineMasterDataBaseResponse, productResponseBaseResponse, locationResponseBaseResponse) -> {
List<Boolean> apiCalls = new ArrayList<>();
apiCalls.add(masterDataCRUDOperation(offlineMasterDataBaseResponse));
apiCalls.add(productDataCRUDOperation(productResponseBaseResponse));
apiCalls.add(locationDataCRUDOperation(locationResponseBaseResponse));
return apiCalls;
}).subscribeOn(getSchedulerProvider().io())
.map(new Function<List<Boolean> apiCalls, List<Boolean> apiCalls>() {
#Override
public List<Boolean> apiCalls apply(List<Boolean> apiCalls) throws Exception {
// perform database operations here
return apiCalls;
}
})
.observeOn(getSchedulerProvider().ui())
.subscribe(new Observer<List<Boolean>>() {
#Override
public void onNext(User user) {
// Do something
}
#Override
public void onError(Throwable e) {
// Do something
}
#Override
public void onComplete() {
// Do something
}
});

How to include Source Cache in cloud firestore realtime update in MVVM architecture android

In my app i am using android MVVM architecture, so for retrieving data from cloud firestore i am using layers so i create one more class (FirebaseQueryLiveData) for getting result from firestore. So with my code i am getting the realtime update but not able to add the Cache Source feature of firestore.I want to enable offline mode by adding cache soure. How to add it.
ProductViewModel.java
public class ProductViewModel extends AndroidViewModel {
private FirebaseFirestore db = FirebaseFirestore.getInstance();
private MediatorLiveData<List<ProductModel>> productListLiveData;
private FirebaseQueryLiveData liveData;
public ProductViewModel(#NonNull Application application) {
super(application);
}
public LiveData<List<ProductModel>> getProductList() {
productListLiveData = new MediatorLiveData<>();
completeProductList();
return productListLiveData;
}
private void completeProductList() {
Query query = db.collection("mainCollection").document("productList")
.collection("productCollection");
liveData = new FirebaseQueryLiveData(query);
productListLiveData.addSource(liveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(QuerySnapshot queryDocumentSnapshots) {
if (queryDocumentSnapshots!= null){
List<ProductModel> productModelList = new ArrayList<>();
for (QueryDocumentSnapshot documentSnapshot : queryDocumentSnapshots){
ProductModel model = documentSnapshot.toObject(ProductModel.class);
productModelList.add(model);
}productListLiveData.setValue(productModelList);
}
}
});
}
FirebaseQueryLiveData.java
public class FirebaseQueryLiveData extends LiveData<QuerySnapshot> {
private MyValueEventListener listener = new MyValueEventListener();
private Query query;
Source source = Source.CACHE;
private boolean listenerRemovePending = false;
private ListenerRegistration registration;
private Handler handler = new Handler();
private final Runnable removeListener = new Runnable() {
#Override
public void run() {
registration.remove();
listenerRemovePending = false;
}
};
public FirebaseQueryLiveData(Query query) {
this.query = query;
}
#Override
protected void onActive() {
super.onActive();
if (listenerRemovePending){
handler.removeCallbacks(removeListener);
}else {
registration= query.addSnapshotListener(listener);
}
listenerRemovePending= false;
}
#Override
protected void onInactive() {
super.onInactive();
handler.postDelayed(removeListener, 2000);
listenerRemovePending=true;
}
private class MyValueEventListener implements EventListener<QuerySnapshot> {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots, #Nullable FirebaseFirestoreException e) {
setValue(queryDocumentSnapshots);
}
}
}
For Android and iOS, Cloud Firestore has offline persistence enabled by default. This means that your app will work for short to intermediate periods of being disconnected.
And yes, you can specify the source with the help of the DocumentReference.get(Source source) and Query.get(Source source) methods.
By default, get() attempts to provide up-to-date data when possible by waiting for data from the server, but it may return cached data or fail if you are offline and the server cannot be reached. This behavior can be altered via the Source parameter.
So we can now pass as an argument to the DocumentReference or to the Query the source so we can force the retrieval of data from the chache only like this:
FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference docIdRef = db.collection("tests").document("fOpCiqmUjAzjnZimjd5c");
docIdRef.get(Source.CACHE).addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
//Get data from the documentSnapshot object
}
});
In this case, we force the data to be retrieved from the cache only but why to use this feature when you say that you want to get realtime updates? So for your use-case I don't see why you would get the data from cache.

Connecting RxJava Observables in layers

I have 3 layers in my app. Layer1 subscribes to Observable from layer2. Layer2 subscribes to layer3 in order to emit returned data to layer1.
Layer1
layer2.getData(data).subscribe(newData -> {Log.d("onNext", "returned");},
throwable -> {Log.d("onError", throwable.getMessage());});
Suppose layer3 has a method called downloadDataFromApi(data);
public Observable<Data> getData(String data) {
return Observable.create(new Observable.OnSubscribe<Data>() {
#Override
public void call(Subscriber<? super Data> subscriber) {
Data data = new Data();
subscriber.onNext(data);
subscriber.onCompleted();
// Can't find a way to connect to layer3.
}
});
}
What do I need to do in layer2's getData() method? I basically want to have logics before returning Observable back to layer1.
Does that make sense?
Just return the Observable directly. Then layer1 handles subscription as usual.
class Layer2 {
public Observable<Data> getData(String data) {
return layer3.getData(data);
}
}
From what I see you have 3 layers (presentation, business logic, data access).
So what you could do is the following:
class PresentationLayer {
private BusinessLogicLayer layer;
PresentationLayer() {
layer = new BusinessLogicLayer();
}
public void showName() {
layer.getNameWithoutRxPrefix()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
#Override
public void accept(String name) throws Exception {
// show name somewhere
Log.d("PresentationLayer", "name: " + name);
}
});
}
}
class BusinessLogicLayer {
private DataAccessLayer layer;
BusinessLogicLayer() {
layer = new DataAccessLayer();
}
public Observable<String> getNameWithoutRxPrefix() {
return layer.getName()
.map(new Function<String, String>() {
#Override
public String apply(String name) throws Exception {
return name.replace("Rx", "");
}
});
}
}
class DataAccessLayer {
public Observable<String> getName() {
return Observable.just("RxAndroid");
}
}
As you can see, I return an Observable in my data access layer (getName), and chain another method to it in my business logic method (map) before returning it to the presentation layer.

rxjava and android.database.contentobserver

App uses RxJava to make network call, then modify results with data from database and display them. App listens to database changes with android.database.ContentObserver and modify data when there is change. Currently it works with code below but is there some nicer RX way how to achieve same?
Thanks!
Observable<ArrayList<Foo>> observable = Observable.create(new Observable.OnSubscribe<ArrayList<Foo>>() {
#Override
public void call(Subscriber<? super ArrayList<Foo>> subscriber) {
//make api call and get list of foos
ArrayList<Foo> apiResults = api.getFooList();
//loop results and if foo is already in local sqlite db, update it with local values
for (Foo foo : apiResults) {
if(localSqlite.contains(foo){
foo.update(localSqlite.get(foo));
}
}
subscriber.onNext(apiResults);
HandlerThread observerThread = new HandlerThread("ContentObserver-thread");
observerThread.start();
final ContentObserver co = new ContentObserver(new Handler(observerThread.getLooper())) {
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
for (Foo foo : apiResults) {
if(localSqlite.contains(foo){
foo.update(localSqlite.get(foo));
}
subscriber.onNext(apiResults);
}
mContext.getContentResolver().registerContentObserver(uri, true, co);
subscriber.add(Subscriptions.create(new Action0() {
#Override
public void call() {
mContext.getContentResolver().unregisterContentObserver(co);
}
}));
}
Subscription subscription = observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
box's suggestion to investigate SqlBrite is probably the best course of action, but if you don't have time to learn a new API here is a cool technique that I found.
If you wrap each database query result in a behavior subject, you can cache it in a hashmap using its URI as a key. Then when you override the Content Observer's onChange method, you can easily push a refreshed object downstream:
final protected MaxSizeHashMap<Uri, Subject<T, T>> subjectMap = new MaxSizeHashMap<>(cacheSize / 300);
final private ContentObserver contentObserver = new ContentObserver(new Handler()) {
#Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
if (subjectMap.containsKey(uri)) {
subjectMap.get(uri).onNext(query(uri));
}
}
};
Then in your get method, build the object, wrap it in a behavior subject and add it to the map before returning the observable:
public Observable<T> getFoo(U id) {
Uri uri = getUriForId(id);
if (!subjectMap.containsKey(uri)) {
subjectMap.put(uri, BehaviorSubject.create(query(id)));
}
return subjectMap.get(uri);
}

Categories

Resources