This is an odd error which I'm battling with at the moment.
I have an activity that displays a search fragment once it has completed initialisation. The search fragment contains a listview to display the results and that listview is inside a SwipeRefreshLayout so that the user can refresh the search results.
If the user selects an item from the list the search fragment is removed and the parent activity is displayed (it has other fragments). The user can choose to open the search fragment and refresh the results if they wish.
The behaviour I have is that if they use the swipe refresh when the fragment is first opened it works as expected. However, if they dismiss the search fragment and then open it again and then swipe down for refresh I get the java.lang.IllegalStateException: Fragment already added exception thrown for the search fragment.
The code to introduce the search fragment the first time is:
#Override
protected void onPostExecute(Void empty) {
dismissSearchProgress();
getFragmentManager().beginTransaction().
setCustomAnimations(R.animator.slide_in_left, 0, 0, R.animator.slide_out_left).
add(R.id.main_vwContent, mWoSearchFragment).commit();
}
The second time the code is introduced via a swipe action on the screen:
case MotionEvent.ACTION_UP:
if (swipeInRange) {
/* do some other stuff */
getFragmentManager().beginTransaction().setCustomAnimations(R.animator.slide_in_left, 0, 0, R.animator.slide_out_left).add(R.id.main_vwContent, mWoSearchFragment).commit();
}
break;
There are no errors thrown in the second instance unless they swipe down to refresh. The error is thrown before the onRefresh event fires.
Anyone have any ideas? Not sure what code is attempting to add the search fragment again as the exception does not have any of my code in the stack trace and the debugger isn't catching anything.
It's amazing what writing up a problem will do for you. The issue was that to open the fragment the user has to swipe from a particular screen location. So on MotionEvent.ACTION_DOWN we check to see if they have started within the target area and set a flag. Then on 'MotionEvent.ACTION_UP' the fragment is added. However, in the case of a SwipeRefreshLayout it intercepts the 'MotionEvent.ACTION_DOWN' event however it pushes the 'MotionEvent.ACTION_UP' up the stack.
So in my case, the flag was still set to true as the last action taken before the refresh was to swipe to add the fragment.
The fix was to ensure that the flag was reset to false when the search fragment was added.
Related
I am stuck with this problem from few hours now.
I have a Android App, There is Activity A. This activity has bottom navigation drawer, that uses view pager, I have 3 views, account, contact and notes.
On each view you can see list of items, as in contacts you can see list of contacts added, on notes you can see list of notes added and so on.
There is fab icon on every fragment that is basically add/edit button, when clicked it opens a dialogfragment which is used for editing or adding contacts or notes.
My problem is that I can only open my Edit dialog for Contact and notes one time. Once you rotate the screen 2-3 times, I get the error .. Fragment ContactDialogXXXX has not been attached yet.
fun editContactDialog(contact: Contact?) {
val transaction = childFragmentManager.beginTransaction()
val previous = childFragmentManager.findFragmentByTag(ContactEditDialog.name)
if (previous != null) {
transaction.remove(previous)
}
val dialog = ContactEditDialog.getInstance(contact, accountId)
dialog.show(transaction, ContactEditDialog.name)
}
I am using this above method to open dialog everytime. and only gives me error that Fragment XX has not been attached yet. after rotation. I have even done below thing from given links with no luck.
set retainInstance to true in onCreate
also added this snippet
override fun onDetach() {
super.onDetach()
callbacks = null
}
I did add
if (!isAdded)
return
but adding above line in that method only stops the crash but my dialog does not open then.
I have searched everywhere online, looked into all possible combinations on stackoverflow but my problem persists.
tried solutions from below places:
Passing an Object to Fragment or DialogFragment upon Instantiation
https://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
https://guides.codepath.com/android/using-dialogfragment#styling-dialogs
What am I missing? Is there another way of doing it? A better way of creating error prone Custom Dialog Fragments?
I have a SearchView in the Toolbar of an Activity, which hosts a Fragment which has a ViewPager of 4 tabs, each tab consisting of another Fragment (there is a reason for this structure so I don't want to dwell on it unless someone thinks this is the reason for my problem). When the user originally searches something, everything works fine. However, when the user goes and edits the search query, and hits enter, the tab that was already selected doesn't refresh (it will refresh when we navigate away from it to other tabs and then come back).
The following are screenshots of what I mean (the first image is after the original search where everything is all and well, the second image is me editing the query, the third image is after I hit enter - the results stay the same and nothing is updated since isVisible() in the code below returns false when the fragment to me seems clearly visible).
When a query is submitted essentially what happens is the activity sends the search query to the fragment that contains the ViewPager and TabLayout, which passes it to the current Tab. This is the code that is called in the Fragment that contains the ViewPager after the query is submitted:
public void setSearchQuery (String query) {
//mTabLayout.requestFocus();
mSearchQuery = query;
Fragment fragment = mPagerAdapter.getItem(mViewPager.getCurrentItem()); // should theoretically get the current fragment
mTabLayout.getTabAt(mViewPager.getCurrentItem()).select();
if (fragment != null && fragment.isVisible()) {
((ResultsPagerAdapter.QuerySubmitCallback) fragment).submitQuery(query);
}
}
The submitQuery() line at the bottom is never called because isVisible returns false. However, when logging the lifecycle methods of the current tab the last calls are onStart() followed by onResume(). When I click on the toolbar and edit the query and hit enter, this doesn't change, and the current tab is visible, so I have no idea why this returns false.
I should add in the hosting Activity after the user submits their query, I remove focus from the Toolbar and SearchView so that the keyboard collapses after the search is entered.
When you switch the tabs,the fragment will call setUserVisibleHint(boolean isVisibleToUser).And then,you can get the value of isVisibleToUser by getUserVisibleHint().
If you want to get isVisible of all kinds of fragment,you should update the value of isVisible in such methods:
onAttach and onDetach()
onStart() and onStop()
onHiddenChanged
setUserVisibleHint
onViewAttachedToWindow and onViewDetachedFromWindow
I add a fragment with a shared element transition like so
currentFragment.setEnterTransition(TransitionInflater.from(context).inflateTransition(android.R.transition.fade));
currentFragment.setExitTransition(TransitionInflater.from(context).inflateTransition(android.R.transition.fade));
Transition transition = TransitionInflater.from(context).inflateTransition(android.R.transition.slide_right);
transition.setDuration(context.getResources().getInteger(R.integer.fragment_transition_duration));
and then
targetFragment.setSharedElementEnterTransition(TransitionInflater.from(context).inflateTransition(R.transition.change_image_transform));
targetFragment.setEnterTransition(TransitionInflater.from(context).inflateTransition(android.R.transition.fade));
targetFragment.setReturnTransition(transition);
The issue I'm seeing is that once I've navigated to several of these fragments in a row, if I then tap the back button - calling the default onBackPressed() - repeatedly and very quickly, the activity shows the wrong fragment as visible. When I tap on the screen, the click events go to the correct fragment (maybe beneath the visible fragment), but I cannot see that correct fragment.
If I tap the back button more slowly, I get the correct behavior. Has anyone ran into a scenario like this before?
Edit : this is what FragmentActivity is doing
/**
* Take care of popping the fragment back stack or finishing the activity
* as appropriate.
*/
public void onBackPressed() {
if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
supportFinishAfterTransition();
}
}
Another thing to mention is that when I remove the Return Transition I do not have this problem.
Ok, I found the answer if anyone is interested. I don't know why this works, but it seems to fix my issue.
If you have a fragment which has a return transition set on it, then in your onDestroyView() call the method setReturningTransition(null).
In my app fragment(first fragment) where user can press on item and app will show fragment with list(second fragment), where user can drop caught item.
Code:
public void startDragNDrop(){
showFragmentWithList();
JSONObject object = new JSONObject();
object.put(Constants.PARAM_ID, getId());
ClipData data = ClipData.newPlainText("", object.toString());
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(mMainLayout);
mMainLayout.startDrag(data, shadowBuilder, mMainLayout, 0);
}
And when second fragment catches ACTION_DROP it will close himself. And it works well, but if user remove finger faster than method finish their work, dragndrop won't start and second fragment won't receive dragndrop event, as result new fragment won't disappear.
I tried to fix it via setting dragndrop listener at first fragment and when it will catch ACTION_DRAG_STARTED it will call showFragmentWithList() and fragment will show. But I faced of new problem, View.OnDragListener doesn't work for any views inside of second fragment. Can somebody help me with this problem?
You should start a drag-n-drop action prior to showing the second fragment, not the other way around. This is a natural order of events. This way you won't end up in a situation when the second fragment has already been shown but the actual drag-n-drop hasn't started yet.
It's hard to tell from the question where is the root of the problem but I would suggest trying to use a UI handler instead of calling methods directly. This way events will go to the UI message queue and thus, will be dispatched after the system events such as onStart(), onResume() and so forth. This might fix the problem of not receiving events in the second fragment.
Also please make sure your first fragment doesn't "steal" those drag events from the second fragment. Maybe it's your first fragment who receives ACTION_DROP and that's why the second one doesn't. Hope that helps.
I have an app setup that uses fragments in various place, in one case a fragment exists and upon pressing a button another replaces it using replace() all normal so far, however when the user presses the android back button and the new fragment is removed/popped (whatever the system uses) and the app returns to the first fragment there is no event being fired that i can override to perform an action.
is this normal?, the docs seem to suggest that onResume should be fired in this instance, other sites don't, and my app doesn't, if anyone has any clue if this is normal behaviour or not that be great and if it is what event can i hook into?
Edit: i forgot that all of this is occuring as nested fragments, the first fragment is the child of another so is added using a child fragment manager the second replaces the first using its regular fragment manager, besides the issue im describing this is working great
how the first fragment is originally put in place
Menu_Fragment menufragment = new Menu_Fragment();
getChildFragmentManager().beginTransaction().replace(R.id.menu_container, menufragment, "_menu_fragment").commit();
How the second fragment is "added"
Google_Map_Container_Fragment mapcontainerfrag = new Google_Map_Container_Fragment();
getFragmentManager().beginTransaction().replace(R.id.menu_container, mapcontainerfrag, "addedmap").addToBackStack(null).commit();