There is no mention of why. I still have to code all the view holder stuff, so what is the benefit of using it instead of RecyclerView.Adapter?
Just curious.
It automatically adds a RealmChangeListener to your RealmResults, thus automatically calling adapter.notifyDataSetChanged() when the underlying elements are written to. Therefore you don't need to write the code to keep your adapter in sync with the results.
Related
Basically I have a RecyclerView with chat messages. The scenario is as follows:
I add a single message -> Causes RecyclerView to add a new ViewHolder
Delay of about 2 seconds
I change the first message and add a new one -> Should cause RecyclerView to rebind the first message and create a new ViewHolder for the second one.
My problem lies in step 3. as the first ViewHolder is removed and a new one is created and then bound. This produces a very slight flickering which is a little bit annoying and all in all this shouldn't happen.
I am using DiffUtil to dispatch changes, the whole setup is a little bit more involved so it is hard to describe the whole picture but I narrowed it down to this:
The DiffUtil produces the correct result and calls onItemRangeChanged for the first item and onItemRangeInserted for the second item. For whatever reason the first ViewHolder is then removed and a new on is created, added and then bound.
Did somebody experienced similar behaviour or has a clue why this could be the case? I tried to debug through the RecyclerView code but it is a nightmare and I still don't understand the whole thing. What could be the reason that the first ViewHolder cannot be rebound and a new one has to be created?
If I can provide any more information just ask for it and I will try my best.
A small repository showing the problem can be found here: https://github.com/MaxGierlachowski/recyclerview_viewholder_bug
If you look into the error logs and look at the 4th "MESSAGE" log you see that a onChanged and a onInserted are dispatched to the adapter but there is a new ViewHolder created for the onChanged one. I know that RecyclerView checks for a lot of things like animation completion and so on but I really would like to know why the ViewHolder isn't rebound but recreated.
EDIT:
Something I figured out was that if that createViewHolder is called by the RecyclerView the ViewHolder that should be reused is inside the mChangedScrap list and mState.isPreLayout() is false so the function tryGetViewHolderForPositionByDeadline() doesn't search the mChangedScrap list and creates a new one. Still this shouldn't happen, the ViewHolder isn't even animated or something and it seems like all animations are finished at that point.
Small video of the flickering:
Hey so I figured it out myself but I still don't fully understand why this is necessary. So the ItemAnimator inside RecyclerView decides whether a changed ViewHolder will be just rebound or recreated. It will decide it on three factors that are mentioned in the article below. So basically because I was using the DiffUtil I just had to provide some arbitrary to the onItemRangeChanged call and it would reuse the ViewHolder instead of creating a new one.
It was this article that help me understand the problem:
https://medium.com/android-news/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-continued-d81c631a2b91
Probably you have a problem with your DiffUtil.Callback. From the documentation, there is a method areItemsTheSame(T oldItem, T newItem) based on the result diff util could remove and add new holder or just update the content of existing holder. Make sure that your implementation is right.
areItemsTheSame -> check for example that left and right holders are the same types of holders.
areContentsTheSame -> here you should check for equality. object1.equals(object2)
I have a question that is more related to proper design and architecture of MVVM than to coding itself. In my project I have a situation that on ViewModel is suplying data, that are later used in RecyclerView.Adapter to create a proper view.
However I wonder if this would be also correct (from proper 'way of doing things' POV) if some of the data would be supplied in form of id's to be further fetched from Room or external server? For instance during onBindViewHolder use some LiveData with observe() to update certain fields on succesfull load.
Doing data fetch in the views is a no-go. It defeats the very purpose of MVVM and in particular the Android Jetpack efforts. One of the big reason is the data needs to survive configurations. Putting "data fetching" mechanism in the view defeats that as the view can be destroyed and recreated anytime when need be.
So I would suggest you make sure all calls to the network or any other source of data for that matter revolve around the ViewModel and never a view. Let the VM feed the data to the View through observer.
Exception to what I have just said is such use case as loading images with Picasso or Glide where by you feed them URL and they load image. But that's a different thing as they are designed to handle that.
UPDATE with followup Questions
it's ok to put observe() still inside Adapter, during the binding process?
No! Data sent to the adapter must be complete in the purpose it is supposed to serve. For example, if you have to do list app and your Top-Level Activity displays all Todos, then you must feed adapter will complete data (Title, Created time, et al).
The data that are not going to be displayed (like descriptions or sub-to-do-lists) and aren't necessary to identify specific to do should not be fetched (unless you want to store them for sole purpose of avoiding a second network call and pass them to the next activity as serialized data).
When user clicks specific To-Do, then launch new activity with its own ViewModel that will fetch details for that activity (assuming you passed some ID with intent).
If the first, then I understand that observe() should not only update data, but also populate it later to Observer and call notifyDataSetChanged(), right?
Observe is a way to post data to the view when either it have changed or the old view was destroyed and so the new view is being given the same old data that survived the "view death". So it is in the same method where updating data of the Adapter should be done and hence call to notifyDataSetChanged() or similar methods.
I hope that makes it clear.
I think it's best to keep the ViewModel separate from the Adapter. From what I'm gathering you want to basically have each list item load it's own data from Room by having each list item observe on a particular ID for that item. This sounds rather inefficient as you're basically having each item execute a query on the database (or network call) to retrieve just one item for all items that are going to be displayed, think about how it will scale if you were displaying a 100 items. I think it's best that you just execute one query to get the list of data and set the data to the list items in the adapter, keep it simple. Note that onBindViewHolder() doesn't just get called once but multiple times when you're scrolling the screen, so it could get quite ugly if you're trying to lazily load every list item.
I want to refresh my HomeScreenActivity so you can see your balance when you have paid (it works with NFC/HCE).
When you scanned your phone you go back to the HomeScreenActivity. The problem is that when you go back, your balance button is not updated so you have your old balance. You get your new balance when you go back to the login activity and then login again.
I tried a lot to fix this but nothing seems to work. Maybe the problem is that the communication with your balance goes from a database/API.
I hope someone can explain me or fix the problem!!
P.S. I do not have a adapter for balance!
Please use onActivityResult method for the actvity where you want to refresh the amount. And in onActivityResult method reset the adapter and call notifyDataSetChanged().
You have many way to do this. You can research about onActivity result, it will help you solve this problem. EventBus, observer is the same solution.
You have a lot of ways to make your data always up-to-date:
I will list some of them:
Use Realm for your local DataBase. It have own classes extending recyclerviewAdapter and other to notify you when data in tables was changed.
Use new Android Room library it also can do the same thing using LiveData
Use RxJava to subscribe to some changes in you app. When changes will happen Rx will emit new item to all subscribers.
User Observer OOP pattern
NotifyDataSetChanged doing the same thing. When you expect your container data to be changed you call this method and RecyclerView.Adapter will rebind all existed ViewHolders to new data.
These are not all possible variants to organize notifications. You can also use EventBus or kind of timer to refresh data with specified delay(this isn't the best one).
I have an Activity with a Fragment that displays a ListView containing simple TextViews. A menu item can trigger another Activity via an Intent. That new Activity clears the ArrayList underlying the ArrayAdapter for the ListView using ArrayList.clear().
When I backup from the new Activity to my original one with the ListView, and get control in onResume(), I find that my ListView.getChildCount() is the same as when it was left due to the Intent, but the ListView.getCount() is now properly zero!
I have tried using the adapter's clear() method, I have tried Adapter.notifyDataSetChanged() (although I should not have to).
If I modify the underlying ArrayList from within the fragment itself, all seems fine. For example, clicking on an element gives you an option to remove it, move it up or down, etc... That works OK.
Also, If I then leave the List Activity and return to it again, all is well. So clearly the ArrayList is the same list. I never create a new list, only .clear() it.
Any idea how the Child Count can possibly be more than the underlying element count? Perhaps some kind of observer for the ArrayList does not trigger because the Activity is suspended? In which case how could I sync them up again? I have tried invalidate() for example.
This is under API 23.
I seem to have found a workaround. I noticed another post here discussing thread safety. It seemed to have implications if you do not modify your list from the original UI. So, in my onResume(), I just did a setAdapter() again. With the same adapter and list, and viola! All seems well again.
I have a List of items that I want in a ListView, and I can make it work with setting a custom adapter every time the List grows, but the program flow is kind of weird and I have problems with persistence. (If I switch tabs, the UI gets rebuilt with an empty ListView.)
Now, in my day job I'm a C# developer, so when I look at this problem I see a WPF ListView bound to an ObservableCollection. Does Android/Java have something like that, a "fire and forget" connection between a UI element and a data structure?
You don't need to replace the adapter every time you change the data. The adapter "adapts" between data and view. There is no need to change the adapter as long as the way it adapts does not change.
Activity / Fragment lifecycle is not necessarily the lifecycle of your data collection. You can for example make a singleton data collection somewhere and use an adapter to display that collection all the time. Call .notifyDataSetChanged() on the adapter if you changed the data.
A persistent data collection in Android is probably best backed by a database. Take a look at LoaderManager & ContentProvider to provide and load data then displayed via CursorAdapter.
There is no automatic way of keeping a bunch of data available outside of your Activity / Fragment / .. lifecycle and it can get quite complicated but that's basically what you have to do if you want to keep data for longer than a given lifecycle. Singletons, Activity#onSaveInstanceState(), Activity#getLastNonConfigurationInstance(), Fragment#setRetainInstance(), ... are useful utilities to keep data in memory, databases are good for persistent data.
You have to do a little bit work yourself but it's possible. Use a ContentProvider as your DataSource. How the data is stored is up to you. I would prefer a SQLite-DB. A content provider has the possibility to add ContentObservers. (See this related question.)
You can write a CourserAdapter to fetch the Data from your content provider. And your ContentObserver should call notifyDataSetChanged() on your adapter. This closes the circle and your UI refreshes itself.
In Addition to zapls answer:
You can also write an adapter which contains a BroadcastReceiver. When your DataSource changes you can send a LocalBroadcast. The broadcast handler just calls notifyDataSetChanged() of your adapter. I think this would work around most of the lifecycle problems because only active elements will get the broadcast.
The google documentation has an example for such a solution.