I hope I'm wording my question clearly.
I'm working on a legacy project and there's a particular screen that looks like this:
The big rectangle is a Fragment and the multiple rectangles are each an ExpandableListFragment inside a ViewPager
The content of each ExpandableListFragment is loaded via JSON from server.
On each ExpandableListFragment I sent a request to server for JSON data inside its onActivityCreated after I have created and set the associated list adapter.
This request is asynchronous and there's a callback to load the obtained data.
The data thus displayed as ExpandableList with group at index (0) expanded.
I could scroll ViewPager to left or right depending on the size of contained ExpandableListFragment
I have figured out to sent a request for each ExpandableListFragment exactly one.
But, suppose that I have gone so far to the left (or right) too quickly. For example, I'm at index number 12.
And then request for index number 0 is finished, and my callback got called.
I've got IllegalStateException Content view not yet created.
The responsible line for the exception is, the method is the callback method.
public void processResult(ScheduleListJSON response) {
setListData(response);
adapter.notifyDataSetChanged();
// The problem line is here.
getExpandableListView().setOnChildClickListener(ProgramScheduleExpandableList.this);
if (getExpandableListAdapter().getGroupCount() > 0) {
getExpandableListView().expandGroup(0);
}
}
I think it's because I have gone so far to the right that the listview addressed by my callback (index 0) in this method has already gone.
Question:
Any suggestion on how to achieve my intention? Expand a group view on response which could arrive very late that even if the user hasn't left the main Activity, it may have already gone because the fragment is so far off screen.
Thank you very much in advance.
UPDATE
oh wait, does mPager.setOffscreenPageLimit(int) has something to do with how many listviews inside a pager are guaranteed to be available?
Related
I'm making an API call getData(forPage: Int): Response which returns a page-worth of data (10 items max) and thereIsMoreData: Boolean.
The recyclerView is implemented that by scrolling, the scroll listener automatically fetches more data using that API call:
val scrollListener = object : MyScrollListener() {
override fun loadMoreItems() {
apiFunctionForLoading(currentPage + 1)
}
}
The problem is that with longer screen devices that have more space for items (let's say 20), the RV receives 10 items and then doesn't allow scrolling, because there's no more items to scroll to. Without scrolling, more data cannot be loaded.
My naive solution:
load first set of data
if thereIsMoreData == true I load another page of data
now I have more data than the screen can display at once hence allowing scroll
Is there a more ellegant solution?
Android has this Paging Library now which is about displaying chunks of data and fetching more when needed. I haven't used it and it looks like it might be a bit of work, but maybe it's worth a look?
Codepath has a tutorial on using it and I think their stuff is pretty good and easy to follow, so maybe check that out too. They also have this older tutorial that's closer to what you're doing (handling it yourself) so there's that too.
I guess in general, you'd want your adapter to return an "infinite" number for getItemCount() (like Integer.MAX_VALUE). And then in your onBindViewHolder(holder, position) method you'd either set the item at position, or if you don't have that item yet you load in the next page until you get it.
That way your initial page will always have the right amount of content, because it will be full of ViewHolders that have asked for data - if there's more than 10, then item 11 will have triggered the API call. But actually handling the callback and all the updating is the tricky part! If you have that working already then great, but it's what the Paging library was built to take care of for you (or at least make it easier!)
An elegant way would be to check whether the view can actually scroll down:
recyclerView.canScrollVertically(1)
1 means downwards -> returns true if it is possible tro scroll down.
So if it returns false, your page is not fully filled yet.
So basically, i have a RecyclerView within a fragment, set within onCreateView(), also i've put volley request inside onCreateView() that updates the Recycler adapter with new data that is being lazy loaded from the server. Everything worked great until i started sliding between pages too fast - and since onCreateView() is being called for fragments that surround current fragment while sliding - volley sends multiple data with the same parameters to the server - and my RecyclerView gets same data for a couple of times (depending on how fast i slide).
I've put something like:
static boolean requestAllowed = true;
#OverRide public View onCreateView() {
inflater etc..
if(requestAllowed) {
requestAllowed = false;
sendingVolleyRequest();
}
And then within sendingVolleyRequest(), i have put in onResponse() or onErrorResponse() requestAllowed = true;.
However, it gets passed by that and its sends requests regardless of that statement (probably thread issue), so i am just wondering - is there any good way of solving this, other than forbiding sending same parameters two times in a row (which will prohibit getting updates if list gets updated after i finish loading before update). Thank you in advance, and sorry for not providing code.
Implement onPageChangedListener() on your viewPager and in onPageSelected() method send volly request.
I am making an web service based Android application. My problem is child count with expandableListView. I must use 2 different web services. Namely, 1st web service is getting parent informations. I want this, when i clicked parent 2nd web service must start and get child informations. My child counts are flexible. 1st parent have 2 childs, 2nd parent have 5 childs. How can i manage them.
I am using expandablelistviewadapter. This adapter want child counts before using this code "listview.setadapter(adapter)"
So I want to use dynamic child counts. When I clicked parent dialog will show and childs getting from server.
I don't get your questin. Writing a custom adapter is the way to go in most cases. So you are on the right way. This adapter should use a datastructure what does what you want.
edit:
this should work right? (Item is my baseclass for dynamic data)
public class MyBaseListAdapter implements ListAdapter {
List<? extends Item> items;
#Override
public int getCount() {
return items.size();
}
edit II:
this line may also be improtant for you:
adapter.notifyDataSetChanged();
and you should try to sync or block access while you change the data
you should give the child count as what u have for each parent i.e. 0. When parent is clicked, get the children for the clicked parent, update ur data-structure and then call adapter.notifyDataSetChanged();
So I want to use dynamic child counts. When I clicked parent dialog
will show and childs getting from server.
There's nothing dynamic about the child counts, you just need to update the adapter when the child data becomes available. As the other answers have pointed out, you need to implement a custom adapter. The basic flow would be:
make the first webservice call to retrieve the group data.
when that call finishes build an instance of your custom adapter where the child count(getChildrenCount()) is zero(because we don't have any data). Ideally you'll show the user some sort of indicator that data is being retrieved. You spoke about a dialog, I would go(and my example is based on this) with a custom child row which indicates loading(in which case you would return 1 from getChildrenCount()).
in the OnGroupClickListener make the call to the webservice to retrieve the data for that particular clicked group. You'll also need to make sure that only the first group click makes the request to fetch data.
when the child data for a group becomes available update the adapter(or make it fetch the new data) and call notifyDataSetChanged().
I've made a small sample on how you might approach this(to indicate that the data is being retrieved for a group I make that clicked group to show a loading child row while the data isn't yet available). The code is commented and you can find it here.
MvvmCross version 6.2
_myExpandList = view.FindViewById<ExpandableListView>(Resource.Id.yourExpandedList);
int ExpandViewCount = _myExpandList.Adapter.Count;
I was looking for this all day and grew increasingly annoyed by all the answers. This seems to be the simplest solution.
As the title says, I'm using a BaseAdapter to display items in a ListView. Obviously a ListView will reuse views, including TextViews and NetworkImageViews.
Assuming 3 items can be displayed at once, the NetworkImageView will be reused for items at index: 1, 4, 7, ....
Depending on what's being displayed, the NetworkImageView will either:
request the image from the Network and display it,
display a cached Bitmap,
or display a local drawable resource.
Items 2 and 3 work fine, however in Scenario 1, let's say we're displaying item at index 4 from the network, and the user scrolls to item 7 before 4 is loaded and it's a local resource, we display the local resource. However our network image request may just be finishing now, so we end up displaying an incorrect image.
How can I enforce the proper (expected)behavior?
The answer from #Snicolas is spot on, but lacks some pointers on how to actually accomplish that. So here goes.
The general idea is to keep track of the ongoing image requests for every row. That way, when you encounter a recycled row, you can cancel the pending request and kick off a new one for the new data relevant to that row.
One straightforward way to accomplish that is to make the ImageContainer that you can get back when requesting an image load, part of the adapter's ViewHolder/RowWrapper. If you're not using this pattern yet, you should. Plenty of examples out there, including a good I/O talk.
Once you've added the ImageContainer to your holder, make an image request and store the container that you get back. Somewhat like this:
ImageListener listener = ImageLoader.getImageListener(holder.imageview, defaultImageResId, errorImageResId);
holder.mImageContainer = ImageLoader.get(url, listener);
The next time a recycled row comes in the adapter's getView() method, you can get your holder back from it and check wether it has a ImageContainer set. One of the following 3 scenarios may apply:
There is no ImageContainer, which means you're good to go to make a new image request.
There is an ImageContainer and the url that it is loading is the same as for the new row data. In this case you don't have to do anything, since it's already loading the image you're after.
There is an ImageContainer but the url that it is loading is different from the new row data. In this case, cancel the request and make a new one for the current row data.
If you like, you can move some of this logic by having your BaseAdapter extension implement AbsListView.RecyclerListener (and set the adapter as recycler listener for the ListView or GridView). The onMovedToScrapHeap(View view) method gets passed in the view that has just been recycled, which means you can cancel any pending image requests in there.
You don't need to enforce anything if you use the provided NetworkImageView.
NetworkImageView detects when it has been recycled and cancels the request automatically.
I don't know this API but my guess is that you should cancel any pending request before recycling such a view. How you can do that I can't say.
Did you here of alternatives like :
picasso ?
robospice UI Module ?
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.