Properly updating RealmModel after onClick without blocking the UI in Android - android

I have a RecyclerView which displays items from a Realm database.
To do so I have implemented a RealmRecyclerViewAdapter based on the realm-android-adapters which gets an OrderedRealmCollection passed in.
For this I load the data with findAllAsync()
realm.where(Entry.class).findAllAsync()
When the user scrolls through the list he has the option to "favorite" some of the entries, which I have implemented with a normal OnClickListener on the "favorite" button.
Now I would love to update the "favorite" boolean of the entry whenever the user clicks the button. After the user clicked, a small animation is shown on the button.
For this I tried the following which sadly gives a wrong thread exception:
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
entry.setLiked(!entry.isLiked());
}
});
As an alternative I tried it with the normal executeTransaction which will work, but prevent the animation from being shown.
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
entry.setLiked(!entry.isLiked());
}
});
As an interesting addition this gives the following log:
Mixing asynchronous queries with local writes should be avoided. Realm will convert any async queries to synchronous in order to remain consistent. Use asynchronous writes instead.
Is there any possibility to update the item completely async without introducing lag or preventing the animation from being shown?

RealmRecyclerViewAdapter calls adapter.notifyDataSetChanged() when your Realm gets auto-updated, so the layout animation won't be called.
As for actually doing the change,
public class EntryViewHolder extends RecyclerView.ViewHolder {
#BindView(R.id.entry_favorite)
Button favButton;
public EntryViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
}
public void bind(Entry entry) {
final long entryId = entry.getId();
favButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Entry entry = realm.where(Entry.class)
.equalTo("id", id)
.findFirst();
if(entry != null) {
entry.deleteFromRealm();
}
}
});
}
});
}
}

Execute this on a background Thread. I recommend using classic Thread.class .Start animation outisde new Thread, and dismiss it through a callback on the UI Thread.
Realm realm = Realm.getInstance(..);
realm.beginTransaction();
YourClass yourClassInstance = realm.where(YourClass.class)
.equalTo(..)
.findFirst();
if(yourClassInstance != null){
yourClassInstance.setLiked(!yourClassInstance.getLiked());
}else{
yourClassInstance = realm.createObject(YourClass.class);
yourClassInstance.setLiked(whateverYouWant);
}
realm.commitTransaction();

Related

How to wait for list of Maybe items to complete in RxJava2?

Apologies in advance if I lack a basic understanding of how to use RxJava2, because this seems to me something that should be quite fundamental. I've wracked my brains with unsuccessful Google searches, so welcome any resource recommendations. I've opted to use a 'sanitized' representation of my workaround code for the sake of clarity.
Problem description
I have an RxJava2 function asyncCallForList() that returns a Maybe<Arraylist<CustomClass>>. Each CustomClass object in this list only has a few basic fields populated (e.g. the source database only contains a unique identifier and a title string for each item).
The full data required for each item is in another database location, which is retrieved using another function asyncCallForItem(uid), which returns a Maybe<CustomClass> based on the unique identifier, where the encapsulated CustomClass has all the required data. This function is to be called for each item in the list returned by asyncCallForList().
The desired functionality is to update my UI once all the objects in the list have been populated.
Workaround #1
It is easy enough to loop through the resulting array list in the doOnSuccess() attached to the initial Maybe<Arraylist<CustomClass>>, then update my UI in the doOnSuccess() on the Maybe<CustomClass> returned by the subsequent asynchronous calls. This is not an acceptable workaround as there will be an unknown number of UI updates being made (the initial list returned could have any amount of items) and will hurt performance.
Workaround #2
This gets the desired outcome but feels like the wrong way to go about it - I suspect there is a more elegant RxJava2 solution. Basically, I create a custom Observable in which loop through the items in the list and get the full data for each. However, rather than update the UI each time I populate a CustomClass item, I increase a counter, then check if the counter exceeds or equals the initial list size. When this condition is met I call the onComplete() method for the observable's emitter and update the UI there.
private void fetchRemoteDataAndUpdateUi() {
//Counter reset to zero before any asynchronous calls are made.
int count = 0;
Maybe<ArrayList<CustomClass>> itemList = asyncCallForList();
Consumer<ArrayList<CustomClass>> onListReturnedSuccess;
onListReturnedSuccess = new Consumer<ArrayList<CustomClass >>() {
#Override
public void accept(ArrayList<CustomClass> list) throws Exception {
//Custom observable created here, in which the resulting array list is processed.
listObservable = Observable.create(new ObservableOnSubscribe<CustomClass>() {
#Override
public void subscribe(final ObservableEmitter<CustomClass> e) throws Exception {
for (CustomClass customClass : list) {
final CustomClass thisCustomClass = customClass;
//Call to get full data on list item called here.
asyncCallForItem(customClass.getUid())
.doOnSuccess(new Consumer<CustomClass>() {
#Override
public void accept(CustomClass customClass) throws Exception {
thisCustomClass.update(customClass);
e.onNext(thisCustomClass);
count++;
if (count >= list.size()) {
e.onComplete();
}
}
}).subscribe();
}
}
});
listObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Observer<CustomClass>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(CustomClass customClass) {
//Here I add the populated CustomClass object to an ArrayList field that is utilised by the UI.
listForUi.add(customClass);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
//Here the UI is finally updated once all CustomClass objects have been populated.
updateUi();
}
});
}
};
//Kick everything off.
itemList.doOnSuccess(onListReturnedSuccess).subscribe();
}
flatMap it!
asyncCallForList()
.subscribeOn(Schedulers.io())
.flatMapSingle(list ->
Flowable.fromIterable(list)
.flatMapMaybe(item ->
asyncCallForItem(item.id)
.subscribeOn(Schedulers.io())
.doOnSuccess(response -> {
// copy state from the original item
response.text = item.text;
})
, 1) // number of concurrent item calls
.toList()
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(successList -> { /* update UI */ }, error -> { /* report error */ });

RxJava - Observable which emits items which are new or changed

I am a newbie in reactive world and trying to implement the following scenario with rxjava/rxandroid 2.x.
I have a local data set as ArrayList mItems in Application class. The same data set is synchronized with server and updated every time user opens the app. However before server returns the response, I want to display the local data set in RecycleView backed by adapter. As soon as the response is returned, the adapter should update the list with delta and without disturbing the order in the UI.
So far I have tried this:
public Observable<List<Item>> getItemsObservable() {
Observable<List<Item>> observeApi = itemServiceAPI.getItemsForUser(accountId);
if (mItems != null) {
return Observable.just(mItems).mergeWith(observeApi);
} else {
return observeApi;
}
}
To update the UI, the above method is invoked like this:
Observable<List<Item>> itemsObservable = appContext.getItemsObservable();
itemsObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultObserver<List<Item>>() {
#Override
public void onNext(List<Item> Items) {
// Code to update the adapter
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
With this I get the onNext called twice for each local data set and remote data set. How to achieve the desired functionality? Does it need use of filter operators to exclude items?
What's the best approach to achieve this?
You can use 'startWith' operator: it subscribes to different observable first.
appContext.getItemsObservable()
.startWith(localCacheObservable)
.subscribe(adapter::updateData)
Adapter's update data should handle diff calculations.
UPDATE
First of all, why are you using Observable.just(mItems) ??? That's unnecessary.
Your code should look like
itemServiceAPI.getItemsForUser(accountId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultObserver<List<Item>>() {
#Override
public void onNext(List<Item> Items) {
// Code to update the adapter
mAdapter.updateItems(items);
/* method in adapter class
*
* public void updateItems(List<Item> mList) {
this.items.addAll(mList);
notifyDataSetChanged();
}
* */
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
Here, your adapter will be updated in onNext. make sure before calling API, you have to set your adapter with local items.

Android realm executeTransactionAsync OnSuccess() taking too long

the problem that I am having is the following: For some reason onSuccess() is being call 2 minutes after all the code inside execute() is executed. Not sure why is taking that long.
CODE:
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Log.e("time-pre", Calendar.getInstance().getTime().toString());
RealmResults<TVRealm> tvRealms = realm.where(TVRealm.class)
.equalTo("favorite", true)findAll();
Log.e("time-post", Calendar.getInstance().getTime().toString());
}
}, new OnSuccess() {
#Override
public void onSuccess() {
Log.e("time-on-success", Calendar.getInstance().getTime().toString());
listener.onDataUpdate(tvList);
}
});
This is not happening on all devices, my understanding is that is only happening on devices that were using a really old realm db (like 0.84 on gradle) and after updating is causing this.
Does anyone has any clue about this issue?

Firebase Listener freezes app ui

My app implements a ChildEventListener to load the data into an ArrayList (approximately 7000 items).
During childAdded execution for each item, the interface freezes completely and can not be used.
Is there any way to run it in the background and that does not impair usability?
I've already tried using an AsyncTask and a Thread but the app freezes anyway. Thanks in advance.
class FBTask extends AsyncTas {
#Override
protected Boolean doInBackground(final Boolean... params){
int size = 7000; //aprox,
final ArrayList<Model> aux = new ArrayList<>();
Query db = FirebaseDatabase.getInstance().getReference()
.child("List").orderByChild("Double");
ChildEventListener cEL = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Model x = dataSnapshot.getValue(Model.class);
if(x.getT()!=null) {
aux.add(x)
Log.i("onChildAdded", x.getId() + " Added, pos: " + dX.size());
if(aux.size()>=size) {
data = aux;
}
}
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
db.addChildEventListener(cEL);
}
#Override
protected void onProgressUpdate(Boolean... values) {
}
#Override
protected void onPreExecute() {
}
#Override
protected void onPostExecute(DownAdapter result) {
if(result != null) {
DownActivity.downRecView.setAdapter(result);
}
}
#Override
protected void onCancelled() {
}
}
All network interaction and other work the Firebase client does already happens off the main thread. The only things that happens on the main thread are callbacks to your code, such onChildAdded(). This is done so you can update your UI from that code.
My guess is that calling dataSnapshot.getValue(Model.class) 7000 times is taking too much times, which is causing frames to be skipped. Do you really need 7000 models? I'd normally recommend to only retrieve data that you're going to show directly to the user, and 7000 models sounds like more than could reasonably fit on screen for most Android devices.
If you really must retrieve and decode that many items, you will need to use a AsyncTask or a background service. If you're having trouble making those work, share the minimal code that reproduces where you got stuck.
Every callbacks are handled by your Main Thread ( UI thread). Because you have large number of items (7000 items), there is array creation, copy of items from smaller to large array list is happening in runtime. This is causing ANR ( freeze your app). To avoid this, you can simply use new thread to add items in the array list. when you complete adding all items, do inter thread communication ( notify main thread) so that main thread does the further work. This is the exact solution. I had solved in the past the similar problem.

Using RxJava inside RecyclerView Adapter

So a few weeks ago i asked this question: recyclerview periodic ui child updates.
And today i want to refactor that funcionality using Rxjava. It's actually pretty simple, i accomplish the following way:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (friend.getGameStatus().equals(GameStatus.INGAME)) {
holderOnline.startRepeatingTask();
} else {
holderOnline.stopRepeatingTask();
}
}
class MyViewHolderOnline extends RecyclerView.ViewHolder {
private Subscription subscribe;
public MyViewHolderOnline(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
public void startRepeatingTask() {
subscribe = Observable.interval(updateInterval, TimeUnit.MILLISECONDS)
.map(aLong -> current.getGameStatusToPrint())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<String>() {
#Override public void onCompleted() { }
#Override public void onError(Throwable e) { }
#Override
public void onNext(String gameStatusToPrint) {
gameStatus.setText(gameStatusToPrint);
}
});
}
void stopRepeatingTask() {
if(subscribe != null && !subscribe.isUnsubscribed())
subscribe.unsubscribe();
}
}
The problem however is different. I have a navigationDrawer implemented with Activities that are Paused and not Destroyed when switched. So, after i switch to the activity that don't contains this adapter, the observable keeps on sending stuff because its a periodic interval Observable. So my question is, what should i do? I need to unsubscribe when the activity is paused, but i have no idea how since to, and also how to subscribe back. Any help or ideas?
So, if I understand you correctly, one way to solve your problem is to answer the question: In an Activity that contains a RecyclerView, how do I get references to all the ViewHolders that are currently displayed/bound?
For example, to stop all the updates you could then do the following in the onPause() of your Activity:
// assuming you are using a LinearLayoutManager:
final int firstVisibleIndex = mLinearLayoutManager.findFirstVisibleItemPosition();
final int lastVisibleIndex = mLinearLayoutManager.findLastVisibleItemPosition();
for (int i = firstVisibleIndex; i <= lastVisibleIndex; i++) {
YourViewHolder viewHolder = (YourViewHolder) findViewHolderForAdapterPosition (i);
viewHolder.stopRepeatingTask();
}
And likewise, you could restart the tasks in the onResume of your Activity.
BUT: Now that I wrote this I am not sure whether there may be ViewHolders beyond the visible area of the RecyclerView (i. e. before the first or after the last visible item) that are already bound but that would not be reached with this method. If that turns out to be the case you can still iterate over all indices of the items in your adapter and just discard any null return values.

Categories

Resources