Understanding return value of cacheInBackground() in EndlessAdapter - android

I am trying to follow what is going on with the boolean variable, hasMoreData with EndlessAdapter and why is seems to be prematurely turning false.
Let me start from beginning to run through what happens. Note: I am using a task and setRunInBackground(false);
I start off setting my list and setting the adapter:
profileList = new ArrayList<ProfileReview>();
endlessAdapter = new EndlessProfileAdapter(getActivity(), profileList);
endlessAdapter.setRunInBackground(false);
listView.setAdapter(endlessAdapter);
Sidenote: Not sure if this is correct, but it seems I am setting the list with an empty adapter.
The first thing that appears to happen after adapter is set is the method cacheInBackground(), where my profileList size is zero, so it sets 0 as int startPoint when calling my AsyncTask where hasMoreData is set to true. Meanwhile, in this (cache) method, hasMoreData returns true. Not sure why? Because the list is zero in size? Or because its still associated with the default value of true?
In the task, it grabs first 10 items.
Then as user scrolls, the thobber starts spinning. And next 10 are displayed. Log.d tells me that profileList.size() is now 10 and hasMoreData is therefore false.
public void onItemsReady(ArrayList<ProfileReview> data) {
profileList.addAll(data);
endlessAdapter.onDataReady();
hasMoreData = profileList.isEmpty(); \\ Log.'d this out
}
My questions: My list starts with 10 items, users scrolls, it grabs 10 more. Then stops after a total of 20 items (or when hasMoreData == false.) But I have many more items to pull from. How do I keep hasMoreData == true? What is the trigger for this? Obviously the trigger is list size (I think?), and why would the list size ever be 0 once it starts to grab data? (until the end of course)

Not sure if this is correct, but it seems I am setting the list with an empty adapter.
EndlessAdapter is definitely designed to start with a non-empty adapter. In fact, it is designed assuming that the user must scroll to get it to load more data. Behavior in your current approach is unspecified, and I do not recommend that approach. Please load some data, then populate the list once your first batch of data is ready.
Meanwhile, in this (cache) method, hasMoreData returns true. Not sure why? Because the list is zero in size? Or because its still associated with the default value of true?
Since EndlessAdapter does not have a hasMoreData method. A search of the source code to EndlessAdapter turns up nothing named hasMoreData. Heck, the only places the word "more" appears is in comments.
A sample app has a hasMoreData value. Since you are not using this sample app, I cannot help you with random data members of random classes in your own code.
In the sample app, in EndlessAdapterCustomTaskFragment, I use a data member named hasMoreData. This is a boolean value, designed to be returned from cacheInBackground(). The responsibility of cacheInBackground() is to return true if we should continue to load data (after the current batch just loaded), false otherwise. In the case of this sample app, hasMoreData is populated by the call to onItemsReady(), itself triggered by onPostExecute() of the AsyncTask simulating loading some data. hasMoreData is set to true or false depending upon whether the items collection is empty, so it basically does a single load of additional data, then calls it quits.
But that is the behavior of a sample app. I didn't even write most of this class -- it came as a patch adding in support for your own data-fetching task. Do not consider sample code to be anything more than a sample.
Hence, you need to set your hasMoreData value to whatever makes sense for your application logic to serve whatever role you decided to use hasMoreData for. If hasMoreData has the same role in your code as it does in the sample, leave it true until you have determined that you are out of data, then set it false.

Related

Set RecyclerView start position when installing the app first time

Is there a way for me to set starting position for recyclerview, so when the user installs the app for the first time, and starts the app, the recyclerview will be shown at some position, and not from the start?
I have been using:
recyclerView.scrollToPosition(myPosition, 0)
But since the app is started for the first time, it is always default position shown in the recyclerview. The first param (myPosition) is set in the onBindViewHolder function.
I think this happens because recyclerview creates and destroys views while scrolling, so probably I would need to first iterate through the whole list, but not sure if the views will get destroyed in the end.
You can do this, but you need some form of persistence to store if is the first time seen it.
So, first of all, you can't just scroll to the position because even if you pass the data immediately, the adapter doesn't immediately update. There is a delay.
You have to attach a RecyclerView.AdapterDataObserver to the adapter and inside there do the scrolling.
val observer = object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
if (isFirstTime) {
//scrollToPosition
//mark first time as no longer first time
}
}
}
adapter.registerAdapterDataObserver(observer)
You might still need a delay, if so try with
viewlifeCycleOwner.lifeCycleScope.launch {
delay(400)
//scrollToPosition
}
You need to make your firstTime some form of persistence, so it remains across app-restart. I think SharedPrefferences should be enough.
You could save and load a variable to shared preferences, and once the user scrolls for the first time, that variable would change. (Just save a boolean - isFreshInstall, set to true as default and on scroll set it to false)
Then if the variable indicates it's the first time seeing the app just use a simple if to see ifyou should use myPosition or the position you want to show.

Differences and when to use SortedList<T>'s updateItemAt() or recalculatePositionOfItemAt() methods?

Question 1
I was looking at the example code on this page that uses SortedList with RecyclerView.
At line 127, after the CheckBox status changed, recalculatePositionOfItemAt() method was used. The javadocs for SortedList<T> says that recalculatePositionOfItemAt() is for adjusting item positions without triggering onChanged() callback. And updateItemAt() will call onChanged() and/or onMoved() if necessary.
In the case of the example code, the item's field boolean mIsDone changed. I thought updateItemAt() would be more appropriate here?
Question 2 (related)
I tried to play around to use updateItemAt() with a sorted list, but some times java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling was triggered and I found lines 122-125, 152-154 in the example code helps to avoid the exception. I thought the checkbox checked status changing event can only happen when the user checks/unchecks the checkbox. Why are these lines necessary? Some times random tapping or scrolling events far away from the checkboxes can trigger the event?

web service returns 10000 rows of data, how to handle this

this is obviously too much data to display in an android app.. what can i do to mitigate this problem ?
I tried to show all 10000 rows in a list view but i got poor performance. I also tried to display all 10000 rows of data in the list view but that just made it slow
Why do you need that method ? WS method's approach is wrong. First of all, you must change method signature like this (int pageNumber, int recordNumber ) and make pagination enabled. If you use a listview, you can show the first 10 record. Then, if the user need more than that, could click to show more button.

setListAdapter(adapter_name) to change the list displayed on screen, but still appears information from the previous displayed list

I have an activity that extends ListActivity, a list of "concepts" (let's call this list "C") and an onItemClickListener defined for this list. Whenever I click a "concept", no matter which one, the app must display another list. I have the following code to change the displayed list:
if(position == 0) change_list("adapter1");
else if (position == 1) change_list("adapter2");
else if (position == 2) change_list("adapter3");
else if (position == 3) change_list("adapter4");
else if (position == 4) change_list("adapter5");
Where position is the position of the clicked element in C
The function change_list performs setListAdapter(parameter) depending on the parameter I pass.
If I click the first element of C (the first concept), a list related to the first concept must appear. However, after calling setListAdapter(adapter), the data related to this concept is displayed, and also part of the C's list data.
For example: let's suppose C has these concepts:
A B C D E
and I click "A", which would lead to display a list with the following data: {a1,a2}
That's the final result:
a1 a2 C D E
And then, when I interact with another element on screen or I scroll down the list, the "ghost" data disappears and only the correct data remains on screen, just like this:
a1 a2
To make things worse, when I want to display list C again, nothing strange happens. Everything is displayed correctly.
At any time incorrect data is stored where it doesn't have to. One function my app must allow is to generate a txt file , and the generated txt file contains exactly the data I introduced. No data is corrupted or duplicated. I also tried using notifyDataSetChanged() and other functions, but I didn't solve the problem.
EDIT :
Here goes the xml code used for the main list of the activity:
<ListView
android:id="#android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#FF0000"
android:layout_below="#+id/afegir"/>
And an example of code in which I determine which contents must be displayed on screen:
else if(comprovar_concepte_actiu() == 1){
pnt = mydbhandler.getStoredValues("despeses1");
pnt.moveToFirst();
if(pnt.moveToFirst()){
do{
adapter_mostrar.add(pnt.getString(pnt.getColumnIndex("nom")));
}while(pnt.moveToNext());
}
adapter_mostrar.notifyDataSetChanged();
}
Where comprovar_concepte_actiu() returns and integer that tells which concept has been clicked in the main list C and adapter_mostrar is the single adapter I'm using now, instead of using multiple adapters (which made me use setListAdapter)
At the beginning of the activity, I call this.setListAdapter(adapter_mostrar). That's all I have.
EDIT 2 :
https://www.dropbox.com/s/7twgy043lkxb2x5/conceptes.java?dl=0
Here is a link to my conceptes.java activity. Press CTRL+F once opened and search "this is where I call.. " and you will directly get to the function where the change of list displayed on screen starts
I haven't found a solution yet. Any idea will be totally appreciated
The problem here is that - when you set a new adapter - the old data is still drawn. In other words, there has been no command to "refresh" the listView. However, the new adapter will be commanded to draw its own views. What ultimately occurs is that the old items are still there, the new items are redrawn, but when scrolled away the new adapter won't redraw/recreate the old items.
The solution is to simply refresh the adapter. However, there are two ways to go about this:
Add a new adapter every time and use myListView.invalidateViews(); or something similar [This is probably the easiest solution to implement, although probably not the best in the long run]
Change the dataset of the adapter and use notifyDataSetChanged() [on the adapter]
The latter option is a far better idea. You should use a single adapter and simply change its data over time. Once its dataset is changed, then tell the adapter that such a thing happened so it refreshes. However, you should read more here on all the different thoughts and processes about it, rather than take my opinion on it.
Edit:
There's apparently some very nicely, thought out answers around. Here's another one, that tells you more specifically about the differences between these two:
Is there any difference between ListView.invalidateViews() and Adapter.notifyDataSetChanged()?
Edit2:
With the onClickListener in mind, invalidateViews() will most likely not work, as it'll probably still draw the old views to "finish" the click (ie, draw the highlighting).
Changing the data directly inside a single adapter and using Adapter.notifyDataSetChanged() is your best bet, as it'll know to redraw everything from a single adapter and use only the current data defined by this single adapter.
Best to leave the data specifics (and defining what to draw based off of that data) up to what actually knows the data, rather than a higher up container that knows nothing specific about the actual data.

Google I/O 2010 - The world of ListView notifyDataSetChanged()

sorry for stupid question. But really interesting and incomprehensible. In this session discussed about notifyDataSetChanged() method.
From documentation for this method - "called when the data set being observed has changed, and which when read contains the new state of the data". My English bad and I do not understand all. But I right if guess that method called when I need refresh ListView with new data set?
If I'm right then I'm confused. In the past and my first program I played with contacts api of android. And run some processing in an asynctask. At this time appeared dialog with progress bar and in the background, you could see how the state of ListView changed in real time. Data for ListView row changed via BindView.
Why? So I'm in something wrong. Explain please.
As i read it, BindView is only used with cursors, which are a specific type of a data set basically. You can have alternative data sets, there is for example an ArrayListAdapter in the API which uses an ArrayList as its dataset. In case that data set changes, notifyDataSetChanged() will have to be called to notify the list view that its bounds will have to be recalculated and its views have to be redrawn (and probably some more).
If you decide to write your own and create the possibility to modify the data shown in the list view through an adapter (one could imagine adding method like addObject(SomeObject o) in your home made adapter for example), then you'd call notifyDataSetChanged() in that method.
Similarly if you have a deleteObject(SomeObject x), if the remaining data set is larger than zero you'd call notifyDataSetChanged() or when the remaining data set is empty you'd call notifyDataSetInvalidated() which in turn will to some extra stuff like setting the so called empty view in the list if you have one specified.

Categories

Resources