Say I have an adapter that interfaces with a shared data set. During the layout of a composite view (i.e. listView), getView is called and the adapter is accessed. GetView is called for subsequent elements. The adapter provides direct locked references to elements in the shared data set.
Now the dataset may change from underneath the adapter. In this scenario the adapter would be invalidated when the data set changes. However, it is possible that the data is invalidated while the UI is in a draw or layout phase.
My question is, what happens if another thread invalidates the dataset while the view is iterating through it's children (getView is being called, and the adapter is being accessed)?
It depends on what your backing data looks like and how you've invalidated it, as well as how your getView is implemented. Depending on these things you may get a view that represents old data, new data, or some undefined state which may result in unexpected data or even a NullPointerException or other exception.
A more detailed answer can be provided if you supply more detail in your question about the type of data and adapter. Source code for your adapter, particularly your getView method would also allow a more specific answer.
You should always modify the contents of your adapter from your UI thread to prevent issues from this. Otherwise you'll occasionally get a ConcurrentModificationException, NullPointerException or an IndexOutOfBoundsException.
I highly recommend the I/O 2010 talk about ListViews if you haven't had a chance to watch it yet, there is a ton of good info in it about how they work and how to optimize them.
Google I/O 2010 - The World of ListView
Related
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!
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.
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.
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.
Would it be there noticeable speed improvement if I would create local references to needed view elements (e.g. EditText or Button) in my activity (in onCreate()) and use them for accessing needed elements or it does not much matter if I always use findViewById() when I need to access some particular element?
It's an old question, but let me post my answer for anyone who happen to be curious just as me: at least in api level 17 (4.2.2), DFS (Depth First Search) is used to look for a view, and no caching mechanism exists (meaning search will be repeated every time you call findViewById()).
It will be a speed improvement if you have a complex layout and you are accessing those Views too often. It is a good practice to define private variables and to bind them to the references returned by findViewById once in onCreate() then accessing them throughout your code.
If you are accessing those Views just once for, let's say, adding OnClickListeners to them, I don't think it is needed to create local references, as you call them.
If you are using a ListActivity this is what the View Holder pattern advocates. I would say it depends on how often the view is getting refreshed.
Here's a video and pdf from Google I/O which talks about implementing a ViewHolder pattern in the getView method of a ListAdapter
The world of ListView