RecyclerView notifyDataSetChanged() updates both adapters when I call this method on one - android

I have two adapters:
private StreamingNowAdapter mStreamingNowAdapter;
private UserStreamingNowAdapter mUserStreamingNowAdapter;
When I load them with Arrays
mStreamingNowAdapter = new StreamingNowAdapter(mEventId, mEventName,
mEventPreview, mNumberViewers, mContext);
mUserStreamingNowAdapter = new UserStreamingNowAdapter(mUserId,
mUsername, mProfilePic, mStreamingUserNumberViewers, mContext);
and call notifyDataSetChanged(); how come when I call this method on one adapter, both adapters become notified and displayed?
I only called the method on the first adapter
mStreamingNowAdapter.notifyDataSetChanged();
and it loads my data into my second recycler mUsersStreamingNowAdapter without me calling notifyDataSetChanged on the second adapter.
Is this a bug or is it supposed to be like this?
Edit1: Two separate recyclerviews, both adapters attached to their own recyclers.
RecyclerView mRecyclerViewStreaming =
view.findViewById(R.id.fraghome_recycler_streaming_now);
RecyclerView mRecyclerViewFollowing =
view.findViewById(R.id.fraghome_recycler_followed_users);
mRecyclerViewStreaming.setLayoutManager(new
LinearLayoutManager(getContext()));
mRecyclerViewFollowing.setLayoutManager(new
LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false));
mRecyclerViewStreaming.setAdapter(mStreamingNowAdapter);
mRecyclerViewFollowing.setAdapter(mUserStreamingNowAdapter);
I've checked the layout multiple times to make sure both the recyclerview's id's are not identical. Still can't figure out why this is happening.

A correctly managed RecyclerView and its required artifact (an Adapter, its data, etc.) should not interfere with another correctly managed RecyclerView or any other component for what is worth.
Unfortunately, the shared code you provided is not sufficient to determine what could be happening in your project.
To the best of my knowledge, RecyclerView adapters don't talk to each other in any way so a notifyDataSetChanged() on an adapter, should have no impact whatsoever on another adapter, regardless of where said adapters are declared (assuming they are different instances, of course).
All this being said, I'll offer you my personal comments about the code you posted. Please don't take it personally, for I'm reviewing the code, not the author, from a pure technical point of view based upon my experience with Android.
Fragment
It immediately caught my attention to see that a Fragment, a component known for being completely wild when it comes to lifecycle management and state, has so many responsibilities.
It starts by having no less than eight array lists, and managing two recyclerviews, and its respective adapters, and even the LayoutManagers used by the lists.
Not happy with having to manage all this, this Fragment also needs to handle a networking layer, error handling (not yet implemented), and data storage (in memory for now).
Because you may have put this together really fast simply to illustrate a case, I will let the fragment in peace for now.
Data
You're putting a lot of stress on your fragment and adapters by splitting the data in a very inefficient way. 4 array lists, one per field? What?
for(HomeFragmentPojo.StreamingNow streamingNow : streamingNowArrayList){
mEventId.add(streamingNow.getEvent_id());
mEventName.add(streamingNow.getEvent_name());
mEventPreview.add(streamingNow.getEvent_preview());
mNumberViewers.add(streamingNow.getNumber_viewers());
}
Why are you doing this?
If you have a StreamingNow object that has all 4 fields.
Why don't you have a ListAdapter<StreamingNow, ViewHolder>() that simply takes a list of StreamingNow objects?
You can simply do adapter.submitList(...) to pass the list once you get it and the adapter (when properly created) will do the right thing.
If you ever update the list, you simply submit it again, and the adapter will calculate the difference and only update the rows that need to be updated.
The BIND method, is also simple, because it would look like: (pseudo code)
viewHolder.eventName = getItem(position).name
viewHolder.viewerCount = getItem(position).viewerCount
//For the image...
Glide.with(mContext)
.load(getItem(position).preview)
.apply(RequestOptions.centerCropTransform())
.into(viewHolder.preview);
...
You get the idea.
Based upon the properties I see, your StreamingNow "pojo" kinda looks like that
class StreamingNow {
String id;
String name;
String preview; //maybe an URL? You use Glide so in the bind method, use it.
String viewerCount;
}
When I look at your other adapter, I see that it has the same data...
Id, Name, Pic, Count of Viewers.
There is no need to use a different adapter, you can reuse the same adapter assuming they have to behave the same way; if the Users adapter has to do something different on click, for example, you could pass the min information needed for the adapter to decide what to do when an item is clicked. Or better yet, you could simply pass a generic Listener so the adapter simply calls "this item "X" was clicked". Because these adapters aren't here to make decisions and call framework things, they have a lot of work in their plates, and this is not and should not be their responsibility.
Whoever manages, creates, and maintains these adapters (so far, your Fragment) should be the one in charge of doing this. Or even better yet, a ViewModel or Presenter should receive this and make an informed decision.
In any case, the click event inside the adapter is not the biggest problem here, and it will work anyway.
What I am trying to leave written here, is that your problem is a lack of separation of concerns between components.
The smaller and more concrete your classes are, the easier it is to replace them, test them, debug them, enhance them, etc.
You're throwing all your code in a big place. If you separate it, it's a lot easier to keep it under control.
So, what's the deal?
I suggest you take a step back, enhance your RecyclerView to use a ListAdapter
I wouldn't perform the network request in onCreate, instead do it in onViewCreated or similar, because during onCreate in a Fragment, there's no guarantee that the view will be created. (Remember when I told you Fragments were a pain?).
I wouldn't have that network code in the Fragment to be honest; it's quite simple to add a ViewModel and have the data there. Then you can have the ViewModel request the data during onCreate, but you'll only get the LiveData from it when your lifecycle allows it... which is the whole point of LiveData and observing it.
If this sounds like a lot of work, it may as well be, especially if you are new to Android. But it will pay off.
Good luck!

Related

MutableLiveData<ArrayList<T>> vs MutableLiveData<List<T>>. Why should we use List here?

I created a library here: https://github.com/chanjungkim/ALiveData
This library is made because of MutableLiveData<ArrayList<T>>. Many people who learns about LiveData complains or they are confused with this type when they need to manipulate(add, remove, etc) the MutableLiveData. That's because ArrayList is easy to manipulate and _arrayList.value!!.add(item) or _arrayList.value!!.remove(0) seems to notify. But they don't.
At the end, when we want to notify, we must assign a value like _arrayList.value!! = mList. ArrayList and List both need to set the data like _arrayList.value!! = mArrayList or _arrayList.value!! = mList.
My question is List doesn't have add(), remove(), etc. On the other hand, ArrayList already has those functions and helps us manipulate the list much easier.
some people suggested like this
_list.value = list.value.toMutableList().doWhatever().toList()
So, what's the point of using List over ArrayList? Could you give me example with the explanation of using it?
LiveData can be used in different ways, and of course there is no one correct way, but a very common way of using it is within the Android MVVM architecture recommended by Google for use in Android apps.
In this architecture, the Activity (or Fragment) observe the LiveData of the ViewModel. When doing this, the goal would be to make the UI as 'dumb' as possible, where you try to handle as much of the app logic and behaviour in the ViewModel, and the Activity simply observes and reflects it on the UI.
In a case like this, it is often preferable for the values of the LiveData being observed to be immutable.
By doing this, it limits the Activity from being able to manipulate the data it is observing, such as add()ing or remove()ing anything from it. As just described, the goal should be to limit the UI's ability to make exactly these type of changes. If the Activity wants to add() an item to an ArrayList that it is observing, it should instead do this by calling a method on the ViewModel, which will in turn update it's own LiveData.value to the new, updated list, which will in turn be observed by the Activity and updated on the UI.
By only allowing the Activity to observe the immutable values, it helps enforce this separation of concerns, and limits any accidental 'leak' of logic into the Activity itself.
This idea can be extended further by ensuring that the observed values are of type LiveData, and not MutableLiveData. Using the latter can allow the Activity to manipulate the live data on its own, and break the MVVM pattern.
A List is an interface, and defines the methods that must be implemented by any classes that would like to behave like a list. It can be considered the 'most basic' version of a list, and only defines the minimum requirements for an implementing class to behave like a list.
In the same way, the List interface itself extends the Collections interface, which in turn extends the Iterable interface. Each one adds more functionality to the one before it... kind of like lego blocks stacked on top of each other, creating more complex shapes.
An ArrayList is a class, which implements MutableList (which itself implements List). This means that an ArrayList can be instantiated, and passed around as an actual object. Because of this object oriented design, and according to the Liskov substitution principle, any class (or interface) can be replaced by a subclass (or class implementing the interface) interchangeably.
This is a fundamental principle to object oriented design. It helps break parts of the application down into smaller, more basic and more manageable pieces, and then grow as required.
To answer your question more specifically, if the class that is observing the LiveData only cares about the methods defined in the List interface, then that is the all it requires to know about the value. The actual value could in fact be an ArrayList, a MutableList or even a custom class MyOwnFancyList<E>: List<E>, it does not matter to the observer, just as long as it implements the List interface.

How to properly use LiveData with RecycleView

I have a project that loads a list from the server. This data will eventually be stored into a database, but for now is stored in memory in a MutableLiveData. A RecyclerView's adapter is watching the data and displaying it. So far everything is working as expected, using a FAB the user can post a new entry which will go at the top of the list, on success I get a 200 and here's the main part where I'm getting lost...
When I want to add a single item to a list stored in a LiveData, the observer is unaware of the delta. I currently make a call to RecyclerView.Adapter.notifyDataSetChanged(), though the ideal in my case would be to call notifyItemInserted(0) or in other cases I can see various other notifications. What the best way to do this? The lifecycle architecture library appears to be very well thought of, I assume I'm missing something simple. I can't imagine having to manually perform a diff between the lists?

update listview cells/items whose index may change

I have elements in a listview that change the way they look based on a network response
by the time the network responds the listview item (or item in the arraylist) could be at a different index
What I can do:
Make an alternate api call back to the server which returns all the items in the list (in their most updated form), and then call notifyDataSetChanged() on the adapter
but this seems like a waste of processes, and so does some alternative of searching an arraylist for the equivalent object, updating it and then calling notifyDataSetChanged()
Is there a way instead to have something like a BroadcastReceiver within the adapter that can keep track of the adapter item which started the network call or service? any maybe only respond to the receiver if the view is not currently recycled
It's hard to give an exact answer as your best approach since what you described is a really high level overview. I'll have to give an equal high level answer. Hopefully it help.
There's not many ways around searching an ArrayList in the adapter for a given item. One good idea:
You could create a custom adapter which is backed by an ArrayList but also maintains a Set of the data as well. The benefit is finding an item is O(1) however any adds or removes require you to modify two collections instead of one...which will cause a slight slow down. I've personally had to use this solution once for a highly complex adapter/listview. It could get updated quite often (to the point throttling notifyDataSetChanged() was once discussed) Surprisingly the slow down in maintaining a List and Set was hardly noticeable and overall worked well.
You could use a similar approach if your data has some sort of unique id associated with it. In which case you could build a Map of the data and use the maps values() method to obtain the List to use for the adapter. While using the keys to quickly find and update the required data. This may or may not be more difficult then the Set idea. Further if you can get your data into a SparseArray (having a unique int for each item), then you could use a SparseArrayAdapter which can get you O(log n) search times. Of course you loose the ability to sort your data in any meaningful way.
I'm not sure how viable the BroadcastReceiver idea is. I would see it more like each item's object instance would control the network request/response for itself, but that would seem tricky and odd. There's always the option of using a CursorAdapter. Just store all your data to DB. Have the network calls update the DB which can then be reflected within the CursorAdapter.

How do I edit Endless adapter in a way that the loading stops if there is no data to load?

The endless adapter that I've used in my code, doesn't stop expecting data even if I am out of it. Thus the throbbing symbol, which is the loading symbol here, keeps on circling expecting some data.
How do I stop it? How do I make the endless adapter know that I'm out of data?
Also, I would like to tweak the adapter so that it can use multiple lists. Is it possible? By multiple lists, I mean list embedded inside another list. If yes, is there an example or any ideas as to how to do it?
How do I make the endless adapter know that I'm out of data?
Quoting the documentation:
Your EndlessAdapter subclass also needs to implement cacheInBackground(). This method will be called from a background thread, and it needs to download more data that will eventually be added to the ListAdapter you used in the constructor. While the demo application simply sleeps for 10 seconds, a real application might make a Web service call or otherwise load in more data.
This method returns a boolean, which needs to be true if there is more data yet to be fetched, false otherwise.
Since this method is called on a background thread, you do not need to fork your own thread. However, at the same time, do not try to update the UI directly.
If you expected to be able to retrieve data, but failed (e.g., network error), that is fine. However, you should then return false, indicating that you have no more data.
Also, I would like to tweak the adapter so that it can use multiple lists. Is it possible? By multiple lists, I mean list embedded inside another list.
No. Android does not support the notion of lists inside of lists. You are welcome to take a look at my MergeAdapter (if you really mean that you wish to concatenate multiple lists together) or Android's ExpandableListView (if your lists-in-lists is really some sort of shallow tree structure).
It is possible to use different data for your own Adapter this data can be of any type such as
ArrayList<HashMap/HashSet<?,List<?>>> it is your own business how you will use it within your getView(...) method. You can implement a poller service which will update your Adapter with data accordingly and setAdapter() after. If there's no data just idle...
hope this helps abit.

Where should this code live - in my Activity or my Adapter?

I'm looking for guidance as to how to modularize my code. I have an activity and a listAdapter and they are getting pretty complex. I'm not sure what code should live where and how much knowledge each of these 2 classes should have of each other. How do you decide whether to put code in an activity or its adapter? And what patterns do you use to keep these classes as lean as possible?
Your description is too generic, so I cannot give you an exact answer (would be useful to explain why they are getting bigger and bigger, what is the extra code good for).
But generically speaking, just think about what each class supposed to do. The "Activity" (as I see it), is a main controller, it "knows everybody", and it connects the other components together (the ListView with the list adapter). The list adapter's purpose is simply to map data to views. If they are getting bigger, extract new (utility) classes.
For example assume a big part of the code in ListAdapter formats timestamps (eg. takes timestamp as long value, and based on current time creates a string like "2 hours ago"). Then it makes sense to create a new utility class called TimeFormat (with a constructor which takes a context, you'll need it later to fetch string resources). Then the ListAdapter will create an instance of this class.
Another example would be data saving. In that case you could create a class called "Model" or "Document" (again with a constructor taking a "Context" instance). It would be responsible (for example) to load the data by parsin XML files, and to save the data by generating XML files. In this case this class would be instantiated by the activity.
Also note that the ListAdapter should really do what it supposed to do: create/setup views based on data. It should never depend on other views (in other views it should work with any ListView in any layout file). So if you have "findViewById" call, which access a view outside of the ListView (or the ListView itself), then that code should be moved to the activity.
Also, when in doubt you can try to find an open source application, which is relatively mature, and does something similarn (and see how that is solving the problem).
Per the adapater documentation in android
An Adapter object acts as a bridge between an AdapterView and the underlying data for that view. The Adapter provides access to the data items. The Adapter is also responsible for making a View for each item in the data set.
So if your code has to do with getting the data to display or creating the views, then it goes in the adapter. Everything else goes in the Activity or else where. If you're spending a lot of code retrieving the information you want to display, consider using some sort of AsyncTaskLoader class. Note that loader classes can be accessed from API Levels less than 3.0 using the android compatibility package.

Categories

Resources