Android FragmentTransaction() IllegalStateException: Fragment already added - java

I am trying to show multiple fragments in a FrameLayout container. The basic idea is to use a spinner to select a different fragment to be displayed in the container. I am trying to add them programmatically and for this I have created one xml file for each day of the week which is what I am trying to show.
// Set up the spinner to select days of the week.
Spinner spinner = (Spinner) weekView.findViewById(R.id.day_selecter);
spinner.setOnItemSelectedListener(new OnItemSelectedListener(){
#Override
public void onItemSelected(AdapterView<?> parent, View view,
int pos, long id) {
// For each day of the week, create a new instance of Switches
// get an instance of FragmentTransaction from your Activity
FragmentManager fm = getFragmentManager();
FragmentTransaction fragmentTransaction = fm.beginTransaction();
Fragment frag = fm.findFragmentByTag(Integer.toString(pos));
if(frag == null) {
switch(pos) {
case 0: frag = new mondayFragment();
break;
case 1: frag = new tuesdayFragment();
break;
case 2: frag = new wednesdayFragment();
break;
case 3: frag = new thursdayFragment();
break;
case 4: frag = new fridayFragment();
break;
case 5: frag = new saturdayFragment();
break;
case 6: frag = new sundayFragment();
break;
}
fragmentTransaction.add(R.id.week_program_switches, frag, Integer.toString(pos));
}
else
fragmentTransaction.replace(R.id.week_program_switches, frag, Integer.toString(pos));
fragmentTransaction.commit();
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
The program manages to load the fragment corresponding to the first day of the week, i.e. the mondayFragment into the container. Each fragment contains textboxes and checkboxes so i need to save the view of each fragment loaded. At times the app would allow me to select a different day but the content from the previous fragment gets carried over and it eventually crashes the app with an IllegalStateException error: Day has already been added to fragment transaction. other times, simply selecting a different day in the spinner immediately crashes the app with the same error. I really need your help in this as I'm all out of ideas.
Few Notes:
1. I have not added any fragment statically into any layout. I have seen this to be the cause of many related issues.
2. I originally intended to use a single fragment and create multiple instances in a frameLayout container but i had similar issues of saving the content in each instance.

Related

java.lang.IllegalStateException crash reports from Google Play Console

I have a trouble with my Android app.
Some devices which use Android 7.1 or 8.0 often report IllegatStateException crash.
I have searched on Google and tried to fix it many times but no use.
I cannot figure out what causes this crash.
So please help me if you have time.
Here is a full report:
java.lang.IllegalStateException:
at android.support.v4.app.FragmentManagerImpl.addFragment (FragmentManagerImpl.java:60)
at android.support.v4.app.BackStackRecord.executeOps (BackStackRecord.java:137)
at android.support.v4.app.FragmentManagerImpl.executeOps (FragmentManagerImpl.java:38)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether (FragmentManagerImpl.java:112)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute (FragmentManagerImpl.java:89)
at android.support.v4.app.FragmentManagerImpl.execPendingActions (FragmentManagerImpl.java:21)
at android.support.v4.app.FragmentManagerImpl$1.run (FragmentManagerImpl.java:2)
at android.os.Handler.handleCallback (Handler.java:869)
at android.os.Handler.dispatchMessage (Handler.java:101)
at android.os.Looper.loop (Looper.java:206)
at android.app.ActivityThread.main (ActivityThread.java:6733)
at java.lang.reflect.Method.invoke (Method.java)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:240)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:845)
I have a main activity contains a FrameLayout.
I use this FrameLayout to display 5 different fragments by using:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
switch (button) {
case 1:
ft.replace(R.id.mainframe, fragment1, FRAGMENT1_TAG);
break;
case 2:
ft.replace(R.id.mainframe, fragment2, FRAGMENT2_TAG);
break;
case 4:
Fragment fragment4 = new Fragment();
ft.replace(R.id.mainframe, fragment4, FRAGMENT4_TAG);
break;
}
ft.setTransition(FragmentTransaction.TRANSIT_ENTER_MASK);
ft.commit();
Some fragments must be saved to restore later (fragment 1 -> 3), some must be refreshed (create new instance) (fragment 4 -> 5).
Moreover, on fragment 4 and 5, there are a viewpager and an array of button to move between pages for each. The code of viewpagers:
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewPager.setAdapter(new FragmentPagerAdapter(getChildFragmentManager(), new Fragment[] {
new Page1(),
new Page2()
}));
}
private void onButtonTap(Button btn) {
switch (btn.getId()) {
case R.id.button1:
viewPager.setCurrentItem(0, true);
break;
case R.id.button2:
viewPager.setCurrentItem(1, true);
break;
}
}
I also define 2 classes derive from Dialog and use Toast.
I cannot reproduce on simulators or my devices. It just happens on a few devices.
Is there anything wrong with my code?
Please help me. This crash stays for a long time, I need to get rid of it.
I'm looking forward to hearing from you.
Thank you very much.
Long
Edit 1: Here is where I call ft.replace on fragment 1
#Override
protected void onResume() {
// Check server connection to go back to the first fragment
if (!ConnectivityReceiver.getInstance().isServerConnected()) {
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.main_frame);
if (currentFragment != null &&
(FRAGMENT_4_TAG.equals(currentFragment.getTag()) || FRAGMENT_5_TAG.equals(currentFragment.getTag())) && isActive()) {
.....
ft.replace(fragment1);
}
}
}
According to the source code, the addFragment method throws that exception with the message "Fragment already added" if you attempt to add a Fragment that is already a member of the manager.
Now, the version of the FragmentManagerImpl source code that I can see looks rather different to the stack trace, so this is a bit of a guess. But I suspect that the problem is that you may be calling ft.replace on fragment2 or fragment1 when they are already part of the layout. Under certain circumstances ....

How to run the same fragment several times?

I'm creating an app that will allow the user to take little tests. There is therefore a TestActivity made up by a PreTestFragment (with a button "Start Test"), a TestFragment and a PostTestFragment (with results of the test). As TestFragment will be the fragment hosting a single question I want it to be displayed several times (let's say 10).
All I could come up with was making a static counter that is incremented every time the user presses NEXT in a TestFragment and that makes him quit after the counter reached 10.
I'm new to fragment management, so I feel like I may have done several errors in the implementation of the onClick() of the NEXT button.
static private int current_question;
public void onClick(View view) {
FragmentManager mng = getFragmentManager();
FragmentTransaction fgmTr = mng.beginTransaction();
Fragment currentFgm = mng.findFragmentByTag(FragmentTags.TEST_FTAG.toString());
Fragment nextFgm;
Bundle bundle = currentFgm.getArguments();
if (current_question < 10) { // test still running
current_question++;
nextFgm = mng.findFragmentByTag(FragmentTags.TEST_FTAG.toString());
// ...
// putting needed data in the bundle
// ...
if (nextFgm == null)
nextFgm = new TestFragment();
} else { // test is finished
nextFgm = mng.findFragmentByTag(FragmentTags.POST_FTAG.toString());
// ...
// putting different needed data in the bundle
// ...
if (nextFgm == null)
nextFgm = new PostTestFragment();
}
nextFgm.setArguments(bundle);
fgmTr.replace(R.id.test_body, nextFgm, FragmentTags.POST_FTAG.toString());
fgmTr.addToBackStack(null);
fgmTr.commit();
}
#Override
public void setArguments(Bundle args) {
super.setArguments(args);
// Should I use if-else here too to pass different data in the bundle?
}
Running it and pressing "NEXT" the first time I get the following error:
java.lang.IllegalStateException: Fragment already active
How to fix this code?
If your fragment (nextFgm) has already been created, you can't call setArguments.
take a look:
https://developer.android.com/reference/android/app/Fragment.html#setArguments%28android.os.Bundle%29
Usually i did it by create a new fragment's instance every time:
android.support.v4.app.FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frame, new myFragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();

How to go to root activity from any fragment by pressing back button?

i have an activity with a navigation drawer built using the library https://github.com/mikepenz/MaterialDrawer
I want to load 4 different pages from selecting 4 different items from the navigation drawer.
I decided to use fragments for each item in the navigation drawer, so when any item is pressed, a new fragment for that option is created and shown to the user.
What i want is that, regardless of which fragment the user is currently in, or which fragment he came from, whenever he presses the back button, he is returned to the original activity.
This is the code i have so far, but apparently the Fragment Backstack is not being updated.
result = new DrawerBuilder().withSelectedItem(-1)
.withActivity(this)
.withToolbar(toolbar)
.withAccountHeader(headerResult)
.addDrawerItems(
item1,item2,item3,item4
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
#Override
public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
result.setSelection(position,false);
clearStack();
switch (position) {
case 1:
fragment = new admin_fragment_data_entry();
break;
case 2:
fragment = new admin_fragment_data_edit();
break;
case 3:
break;
case 4:
break;
}
initiateFragment();
return false;
}
}
)
.build();
#Override
public void onBackPressed() {
int count = getFragmentManager().getBackStackEntryCount();
//handle the back press :D close the drawer first and if the drawer is closed close the activity
if (result != null && result.isDrawerOpen()) {
result.closeDrawer();
}
else if (count == 0) {
super.onBackPressed();
//additional code
}
else {
getFragmentManager().popBackStack();
}
}
private void initiateFragment(){
if (fragment != null) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.addToBackStack(null);
ft.replace(R.id.container_body, fragment);
ft.commit();
}
}
private void clearStack(){
int count = getFragmentManager().getBackStackEntryCount();
while(count > 0){
getFragmentManager().popBackStack();
count--;
}
}
Any help would be highly appreciated.
Also, should i have used fragments here? Or i should have just started 4 different activities for 4 different options from navigation drawer?
Are you using the correct fragment manager to get your backstack count?
I notice in other parts of your class you're using getSupportFragmentManager instead.

Fragment listview update from activity

I am pretty new to Android (3 days), but I have a pretty good background in PHP (which may be the cause of my confusion in a Java based environment). I started building an Android app using Android Studio (Beta). I created the default Android Studio activity with the Navigation Drawer Activity. I edited the activity fragment part to look like this:
#Override
public void onNavigationDrawerItemSelected(int position) {
Bundle bundle = new Bundle();
bundle.putStringArrayList("contacts", arr);
bundle.putStringArrayList("messages", messages);
Fragment fragment = null;
switch (position) {
case 0:
fragment = new FriendsFragment();
break;
case 1:
fragment = new ChatsFragment();
break;
case 2:
fragment = new GroupsFragment();
break;
case 3:
fragment = new LogoutFragment();
break;
default:
break;
}
if (fragment != null) {
fragment.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container, fragment).addToBackStack(null).commit();
}
}
As you can see I am passing a Bundle to my Fragments called "messages" and "contacts" when an item is selected in the Navigation Drawer. The "messages" bundle are XMPP messages received by the aSmack library from an OpenFire server. So basically I'm trying to create a XMPP client. When I run the app I can receive the messages in the "ChatsFragment".
Now my problem:
I have to press the "ChatsFragment" item on the drawer to have my messages updated (re-receive the bundle) everytime I feel like there are new messages received from the server. But I want this to be done automatically by the fragment.
First I would like to know if my procedure is correct (Activity listens to server, creates bundle, send bundle to fragment, bundle updates messages on receive**)
** = This part I haven't been able to understand how to implement.
1- If the procedure is correct tell me how I should get the messages to be updated by the fragment through the activity?
2- If this is not the correct way to do things in Android, recommend me a way of doing it.
My code for displaying the messages in fragment:
private void displayListView() {
// Messages array list
List<String> contacts = getArguments().getStringArrayList("messages");
//System.out.println("arr: " + contacts);
//create an ArrayAdaptar from the String Array
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(getActivity(),
R.layout.url_list, contacts);
ListView listView = (ListView) getView().findViewById(R.id.listView);
// Assign adapter to ListView
listView.setAdapter(dataAdapter);
//enables filtering for the contents of the given ListView
listView.setTextFilterEnabled(true);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// Send the URL to the host activity
//mListener.onURLSelected(((TextView) view).getText().toString());
}
});
}
Thanks in advance.
Typically for long running operations in the background, like listening to a server, incoming messages, etc, you need to use Services. You do so by subclassing the Service class in Android.
Now for your problem - the design approach should be that you have a background service listening to incoming messages. Anytime a message is received (an input stream in your socket operator) you should send a "broadcast" an intent that a message was received. A custom broadcast receiver that you create should wait for this broadcast. Within the onReceive() method of this receiver, you should trigger the creation of the bundle and updating your message.
Remember you should always delegate your long running operations in Android to services. That is exactly what they are for.
So basically if you're already listening for new messages to come in your activity, then you must have some kind of callback like onMessageRecieved() or something like that.
If you do, you can then notify your fragment in this way.
Create a field (goes under your class declaration) called curFrag, so something like this:
public class MyActivity extends Activity {
private Fragment curFrag;
//other code...
}
then in the code you posted you would initialize the curFrag there, but you also need to set a tag for the current fragment. This will be based on your case statement. Make some final string variables as tags.
i.e.
public static final String CHATSTAG = "chatstag";
#Override
public void onNavigationDrawerItemSelected(int position) {
Bundle bundle = new Bundle();
bundle.putStringArrayList("contacts", arr);
bundle.putStringArrayList("messages", messages);
Fragment fragment = null;
String tag = null;
switch (position) {
case 0:
tag = FRIENDSTAG;
fragment = new FriendsFragment();
break;
case 1:
tag = CHATSTAG;... and so on through the switch statement.
fragment = new ChatsFragment();
break;
case 2:
fragment = new GroupsFragment();
break;
case 3:
fragment = new LogoutFragment();
break;
default:
break;
}
if (fragment != null) {
fragment.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
//remember to set the tag.
if(tag != null) {
fragmentManager.beginTransaction()
.replace(R.id.container, fragment, tag).addToBackStack(null).commit();
} else {
fragmentManager.beginTransaction()
.replace(R.id.container,fragment,DEFAULTTAG).addToBackStack(null).commit();
}
//new code
curFrag = fragment;
}
}
Now in your activity when a new message comes in, check the tag of the fragment and then if it matches a certain fragment, notify the fragment that new data has come in and retrieve it in the fragment.
public void onMessageRecieved() {
if(curFrag.getTag().equalsIgnoreCase(CHATSTAG)) {
ChatsFragment frag = (ChatsFragment) curFrag;
frag.notifyDataRecieved();
}
}
Once you have a reference to your fragment in the activity, you have access to any public methods in that fragment.
If your fragment cannot access the data on its own, then you'll need to get a reference to the activity and create a method in the activity that returns the new data.
So in your activity:
public String getMessageData() {
String newData = ...//get stuff from server;
return newData;
}
then in your fragment
public void notifyNewMessage() {
try {
MyActivity activity = (MyActivity) getActivity();
} catch (ClassCastException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
String message = activity.getMessageData();
//do something with the message.
}
It's not necessarily pretty but it works pretty well. You should also check to make sure your fragments are attached when you do this so that you avoid null exceptions.
Hope this helps!

Checking current opened fragment position while using DrawerLayout

I am using android DrawerLayout from Support Library to show slide menu.I am using a single activity and 5-6 fragments to show them upon selection in DrawerLayout menu.But I have a small problem which is "How do I check which fragment is currently visible so if user selected the menu item which corresponds to already opened fragment. Currently it creating the Fragment again and displaying it which is not good.The function that triggers when clicked on menu item is:
private void selectItem(int position) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
// Locate Position
switch (position) {
case 0:
ft.replace(R.id.content_frame, fragment1);
break;
case 1:
ft.replace(R.id.content_frame, fragment2);
break;
case 2:
ft.replace(R.id.content_frame, fragment3);
break;
}
ft.commit();
mDrawerList.setItemChecked(position, true);
// Close drawer
mDrawerLayout.closeDrawer(mDrawerList);
}
How do I check if the requested fragment is already opened so not to create it again?Is their any method to check this through FragmentManager?
I would add to #Plato's answer.
Check get currently displayed fragment question if you haven't already.
The answer says that when you add the fragment in your transaction, you can use a tag to represent a particular fragment. Something like:
ft.replace(android.R.id.content, fragment, "MY_FRAGMENT");
Later if you want to check if that fragment is visible, you can do something like:
RequestedFragment fragment = (RequestedFragment)getFragmentManager().findFragmentByTag("MY_FRAGMENT"); //"My_FRAGMENT" is its tag
if (fragment.isVisible()) {
// add your code here
}
Hope this helps.
the simplest approach would be to store last used position and compare next time. Just make your selectItem method like:
int lastPosition = -1;
private void selectItem(int position) {
if( position != lastPosition ) {
[ old method body here ]
lastPostion = position;
}
}
Assign different tags to each one of your fragments and call findFragmentByTag() of the FragmentManager to check if each one is already present.
You can also use the findFragmentById() of the FragmentManager for the same purpose and use the ids of your fragments.
If you are absolutely sure that each of fragment1, fragment2, fragment3 cannot be visible at the same time and if you reuse the same fragments (i.e. do not create a new fragment each time you change position) then the method isVisible() from the class Fragment should tell you if that fragment is currently visible.
Your switch should look like:
switch (position) {
case 0:
if(!fragment1.isVisible()) {
ft.replace(R.id.content_frame, fragment1);
}
break;
case 1:
if(!fragment2.isVisible()) {
ft.replace(R.id.content_frame, fragment2);
}
break;
case 2:
if(!fragment3.isVisible()) {
ft.replace(R.id.content_frame, fragment3);
}
break;
}

Categories

Resources