I have a Fragment A (say) which contains a FragmentPagerAdaptor, which further contains Fragments (having list view).
When I click on a list item, I replace the complete Fragment A with another fragment (say B), I have also added the transaction for Fragment A in the back stack; now when I press the back button from B, the list in the child Fragment of A gets recreated and scrolled to top.
I want to retain the state(scroll) of the list, I tried storing the scrollY of the list and setting it again, but it's inaccurate and the list takes some time to initialize, also it takes time to scroll (since the list can have thousands of items).
I came across this link to resolve the issue:
http://ideaventure.blogspot.in/2014/10/nested-retained-fragment-lost-state.html
but setting the setRetainInstance(true) in child Fragments crashes the app saying:
java.lang.IllegalStateException: Can't retain fragements that are nested in other fragments
It is also a known bug in google forums:
https://code.google.com/p/android/issues/detail?id=74222
Any kind of help will be greatly appreciated!
I want to retain the state(scroll) of the list, i have tried storing the scrollY of the list and setting it again, but it's inaccurate and the list takes some time to initialize, also it takes time to scroll(since the list can have thousands of items).
this is the recomended aproach, retained fragments were designed to maintain data not UI elements, also nested fragments have some limitations. There are SO's on how to maintain list scroll position, ie.: Maintain/Save/Restore scroll position when returning to a ListView. So you should be able to find a working solution.
If you have some heavy data structure for your list, you can put it inside non nested retained fragment with no UI. This fragment will not be destroyed during config changes (like screen rotation), but it will still be destroyed when you close your app and Android decides to kill your process. So This may cause crashes if you forgot about this case, you must prepare your app for it.
Other option would be to store you list data in sqlite database, it would be immune to config changes, but probably updates of sqlite might be slow.
Related
As far as I can tell, Views only receive DragEvents if they had implemented onDragEvent() or had set an OnDragListener before startDrag() (or startDragAndDrop() for API 24+) is called. They will then continue to receive additional drag events if they return true for DragEvent.ACTION_DRAG_STARTED.
However, I am looking for a way to receive DragEvents after a drag operation had already started, for Views that got added to the layout during the drag operation.
To illustrate why, my situation is roughly the following:
I have a ViewPager2 with ListView fragments whose list items can be dragged. If I drag an item over another item, I "enter" that item and a new ListView fragment is shown with new child items. This works fine.
However, since these new child items didn't exists at the time of starting the drag operation, they don't receive DragEvents when I continue to drag the item over those new items.
So, basically, I want to be able to enter multiple levels deep in one continuous drag operation.
Is it possible to have newly added Views receive DragEvents for an ongoing drag operation?
Okay, I've managed to solve it by re-dispatching the original DragEvent (the one with action DragEvent.ACTION_DRAG_STARTED) to my main component, which is an instance of ViewGroup.
On inspecting the source code for ViewGroup.dispatchDragEvent() I found that ViewGroup has a member variable called mChildrenInterestedInDrag that gets filled with children interested in drag events, when DragEvent.ACTION_DRAG_STARTED is received.
So when I called ViewGroup.dispatchDragEvent() with the original DragEvent, after I entered an item in the list to view its child items, those new ListView child items were now responding to additional DragEvents.
I'm a bit worried that this approach might yield unexpected side effects. If I come across such effects upon further testing, I'll update this answer. But for now it will do.
If somebody knows that this indeed might yield unexpected side effects and/or has a better solution I'd love to hear them.
Addendum:
Upon storing the original DragEvent for re-dispatching, I had to "clone" the event, because, while it worked properly in an API 19 emulator, on an API 19 phone the original DragEvent's action were continuously altered during dragging, so its action wouldn't reflect DragEvent.ACTION_DRAG_STARTED anymore and re-dispatching didn't have the desired effect of registering newly added Views as interested in the ongoing drag operation.
Since DragEvents cannot be cloned or constructed, I had to "clone" it with the help of a Parcel:
void storeStartEvent(DragEvent startEvent) {
Parcel parcel = Parcel.obtain();
startEvent.writeToParcel(parcel, 0);
// important to "rewind" the Parcel first
parcel.setDataPosition(0);
this.startEvent = DragEvent.CREATOR.createFromParcel(parcel);
}
Other than that, I didn't experience any noticeable problems yet, so it may have just solved my issue nicely.
I have a Android app that shows lots of real-time data jammed onto one large scrolling activity.
Now i want to split it up into two simpler screens using fragments, where only one fragment may be on the screen at any one time.
I read up a whole lot on fragments and watched several videos, but before i start ripping up my code to convert it to fragments i wanted to know the following.
If i create two fragments A and B, then while showing fragment B, data comes in for fragment A. Can the controlling activity still communicate with fragment A giving it data even though its off screen? OR do i have to save the data somewhere and then when the user switches to fragment A then I give fragment A the data to be shown, while saving incoming data for fragment B which will now be off screen?
The problem is that right now im not saving any data because everything is on one screen, so as data come in i just displayed it, but if i switch to using fragments i dont know if i can do the same thing by passing the data to the fragments even if they are not on screen at the same time.
Thanks.
If you retrieve your data with multiple asynchronous requests in your Activity, you may create a fragment for each of them and move related retrieval operation into that fragment (probably to oncreateView() method). Then, you can use ViewPager (probably with TabLayout) in the parent Activity to be able to use all those fragments. Therefore, your Activity only deals with setting the ViewPager and leave the rest to fragments.
ViewPager shows one page at a time but it can initialize other fragments as well, even before they are shown. You can use ViewPager's setOffscreenPageLimit() method to increase that range.
In case you need a communication channel between fragments and the activity, you may create callback mechanisms, as described here.
I've got a RecyclerView which contains items, obviously. It uses the DefaultAnimator for all its animations.
When deleting an item, the deletion is animated, but not as it should be. The issue is that it seems like the size of the list is reduced by one first, then the clicked item is deleted and afterwards all items below are moved upwards by one. Take a look at this short video to see what I'm talking about.
The code used for the removal of a item is the following:
MainActivity.events.events.remove(listItems.keyAt(0));
notifyItemRemoved(listItems.keyAt(0));
Where MainActivity.events.events contains the data for the items and listItems.keyAt(0) contains the currently selected item.
What I've tried yet (none of this worked):
Made sure there's no other call which interrupts the animation (like notifyDataSetChanged()).
Implemented the above code directly into a onClickListener for the items inside the adapter.
Implemented the data directly into the adapter instead of a different class.
Replaced the position with getAdapterPosition() or a fixed value (i.e. 0)
Used notifyItemRangeRemoved() after notifItemRemoved().
Hint: I've got the animation to work previously, but as of today it doesn't work anymore.
EDIT:
If I remove the actual removal command (i.e. MainActivity.events.events.remove(listItems.keyAt(0));) from the code snippet given above, the animation is played correctly, but the element then isn't actually removed, so this doesn't solve the problem at all.
I'm currently writing an app to provide food information/a grocery list function. My current code uses a switch statement to determine which button (fruit, vegetables, etc.) was pressed and passes an array list to a fragment that is then displayed as a listview.
When its called by the information activity, users can click on items and be presented with a detail view. If the calling activity is the grocery list activity, users can swipe to add to an empty array list. Since the bulk of the code used is the same, is there a way to do this without using two separate fragments?
All I can think of is putting the activity hashcode in the bundle being passed
to the fragment and using if statements to determine which activity passed the bundle (and thus which event listeners are used), but this doesn't seem like a very good solution to me.
I have a few ListViews driven by custom Adapters (ArrayAdapter and CursorAdapter). In the getView() and bindView() functions of the adapters, I'm creating a row in the ListView and setting a Listener to a particular UI element in the row.
Currently, as the ListView rows are sent to the Recycler the listener is still hooked up so the memory is never released. I don't see a callback for when the row is being disposed of that would give me an opportunity to detach the listener before the row is recycled.
How do I manage the listener in the ListView rows? I only have the getView()/bindView() to add the listener to the row, but nothing to remove them.
Thanks
Android OS does the ListView row view's recycling for you. The views get generated on the fly when user scrolls the list and get destroyed when they go out of view. Android OS destructs the views whenever other application require more memory and some of your objects are not being used, Sort of garbage collection.
And as to your question regarding removing listeners, they get removed automatically since the object they were point to doesn't exist anymore and they get marked as garbage objects too.
After some playing around I decided to use a ViewHolder pattern, and within that I have a variable that tracks the listener. When the recycled view is provided (where applicable) I pull out the listener (it does still exist) and make sure to unregister it as a listener, before creating a new listener and registering that in its place. This probably doesn't catch EVERY listener, but I'm OK with a few ListView rows being stuck in memory.