Strange behavior of FragmentManager.findFragmentByTag() - android

Ok, today I noticed a strange behavior of findFragmentByTag(): if I rotate the screen one time the fragment is returned correctly, but on second rotation I always get null. Am I Doing something wrong? is there a way to fix this?
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
Fragment f = fm.findFragmentByTag("frag");
if (f == null){
Log.e("Fragment","null");
f = new Fragment();
}
f.setRetainInstance(true);
fm.beginTransaction().remove(f).add(f, "frag").commit();
}

Try to call setRetainInstance(true); in fragment's onCreate() and change the code above to:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstance == null){
FragmentManager fm = getSupportFragmentManager();
Fragment f = new Fragment();
fm.beginTransaction().add(f, "frag").commit();
}
}
Set the id as well to show it in a layout view group

Related

Unable to save Fragment state with onSaveInstanceState

I have a MainActivity that manages two Fragments: InputDataFragment (is the one displayed at the opening of the app) which contains a series of EditText and ResultsFragment, which replaces InputDataFragment after a button is clicked.
I want to save the data in the EditText fields so that, even after configuration changes such as screen rotations, if I navigate back from ResultsFragment to InputDataFragment, I can use those data to fill them again.
This is the code I use to navigatefrom InputDataFragment to ResultsFragment:
#Override
public void onCalculate(String firstElement, String secondElement) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ResultsFragment resultsFragment = new ResultsFragment();
fragmentTransaction.replace(R.id.container, resultsFragment);
fragmentTransaction.commit();
}
This is where I save the values in InputDataFragment
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
// Check if there's something to retain before putting it into outState Bundle
if (!firstEditText.getText().toString().isEmpty()) {
outState.putString("firstElement", firstEditText.getText().toString());
Log.d(LOG_TAG, "Power saved");
}
if (!secondEditText.getText().toString().isEmpty()) {
outState.putString("secondElement", secondEditText.getText().toString());
}
}
And this is where I retrieve them
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
firstEditText.setText(savedInstanceState.getString("firstElement"));
secondEditText.setText(savedInstanceState.getString("secondElement"));
}
}
I've tried doing what was suggested in the accepted answers on these Stack Overflow questions, saving and restoring my Fragment references:
Once for all, how to correctly save instance state of Fragments in back stack?
Using onSaveInstanceState with fragments in backstack?
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, "dataFragment", dataFragment);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (savedInstanceState != null) {
dataFragment = (InputDataFragment) getSupportFragmentManager().getFragment(savedInstanceState, "dataFragment");
} else {
dataFragment = new InputDataFragment();
}
fragmentTransaction.add(R.id.container, dataFragment);
fragmentTransaction.commit();
}
The values are correctly present in Bundle when onActivityCreated is called in the Fragment because I've tried to Log them
The problem is that I get an error in the line where I call putFragment within onSaveInstanceState, when I'm in ResultFragment and I rotate the device.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mycompany.myproject, PID: 20161
java.lang.IllegalStateException: Fragment InputDataFragment{679fd26} is not currently in the FragmentManager
at androidx.fragment.app.FragmentManagerImpl.putFragment(FragmentManager.java:923)
at com.mycompany.myproject.MainActivity.onSaveInstanceState(MainActivity.java:21)
at android.app.Activity.performSaveInstanceState(Activity.java:1646)
at android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.java:1508)
at android.app.ActivityThread.callActivityOnSaveInstanceState(ActivityThread.java:5476)
at android.app.ActivityThread.callActivityOnStop(ActivityThread.java:4792)
at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:5424)
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:5342)
at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:69)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2155)
at android.os.Handler.dispatchMessage(Handler.java:109)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:7539)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:958)
I solved it, the problem was here
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (savedInstanceState != null) {
dataFragment = (InputDataFragment) getSupportFragmentManager().getFragment(savedInstanceState, "dataFragment");
} else {
dataFragment = new InputDataFragment();
}
fragmentTransaction.add(R.id.container, dataFragment);
fragmentTransaction.commit();
}
When I'm in ResultsFragment and rotate the device, Activity onCreate method is called. Even though I got the previous InputDataFragment back from Bundle, calling FragmentTransaction.add() caused the loss of the data.
Moving FragmentTransaction code to the case where savedInstanceState == null was the solution.

MapView messed up my fragment transactions?

I have a fragment with a mapview. I can open another fragment (call it listfragment) from the action bar, it works fine. But if I rotate the screen, and then try to open the listfragment, it does not load, instead the mapview flickers on time (the map in the view goes blank and then appears again). If I try to load the listfragment again by clicking the menuitem on the action bar, the app crashes with
java.lang.IllegalStateException: Fragment already added:
Part of MainActivity that loads the fragment:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mNewTrackFragment = (NewTrackFragment) getFragmentManager()
.findFragmentByTag(TAG_NEW_TRACK_FRAGMENT);
if (mNewTrackFragment == null) {
mNewTrackFragment = NewTrackFragment.newInstance();
}
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.fragment_container, mNewTrackFragment)
.commit();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_track_list) {
if (null == mTrackListFragment) {
mTrackListFragment = TrackListFragment.newInstance();
}
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, mTrackListFragment, TAG_TRACK_LIST_FRAGMENT)
.addToBackStack(null)
.commit();
return true;
}
Part of NewTrackFragment with the MapView:
public NewTrackFragment() {
}
public static NewTrackFragment newInstance() {
NewTrackFragment fragment = new NewTrackFragment();
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_new_track, container, false);
mTrackTitleTV = (TextView) view.findViewById(R.id.tv_track_title);
mDistanceTV = (TextView) view.findViewById(R.id.tv_distance);
mElapsedTimeTV = (TextView) view.findViewById(R.id.tv_elapsed_time);
mSpeedTV = (TextView) view.findViewById(R.id.tv_speed);
mAscentTV = (TextView) view.findViewById(R.id.tv_ascent);
mDescentTV = (TextView) view.findViewById(R.id.tv_descent);
mAltitudeTV = (TextView) view.findViewById(R.id.tv_altitude);
mStartStopFab = (FloatingActionButton) view.findViewById(R.id.fab_startstop);
mMapView = (MapView) view.findViewById(R.id.new_track_mapview);
setupMapView(savedInstanceState);
return view;
}
private void setupMapView(Bundle savedInstanceState) {
mMapView.onCreate(savedInstanceState);
mMapView.getMapAsync(this);
}
I googled the best of the evening for some info about it but found nothing similar.
There's a couple of things wrong here. Right now, you are not passing TAG_NEW_TRACK_FRAGMENT as a tag in the call to add within onCreate. Because of this, you are adding a NewTrackFragment instance without an associated tag, and therefore the call to findFragmentByTag will always return null. Essentially, you are creating a new instance of NewTrackFragment every time you rotate the device. This is not good, because the state of the FragmentManager is preserved across device rotations, meaning it is still holding on to each Fragment you add to it. Because you are unconditionally calling add, the FragmentManager will end up holding multiple instances of NewTrackFragment.
With that said, what you should do is add the tag in the call to add and only call add once you know the FragmentManager is not currently holding on to an instance of NewTrackFragment:
mNewTrackFragment = (NewTrackFragment) getFragmentManager()
.findFragmentByTag(TAG_NEW_TRACK_FRAGMENT);
if (mNewTrackFragment == null) {
mNewTrackFragment = NewTrackFragment.newInstance();
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.fragment_container, mNewTrackFragment, TAG_NEW_TRACK_FRAGMENT)
.commit();
}
You might want to do something similar with your TrackListFragment as well:
if (id == R.id.action_track_list) {
FragmentManager fragmentManager = getFragmentManager();
mTrackListFragment = (TrackListFragment) fragmentManager.findFragmentByTag(TAG_TRACK_LIST_FRAGMENT);
if (null == mTrackListFragment) {
mTrackListFragment = TrackListFragment.newInstance();
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, mTrackListFragment, TAG_TRACK_LIST_FRAGMENT)
.addToBackStack(null)
.commit();
}
}

Saving and Restoring Fragment state in Android

how to Saving and Restoring Fragment state in Android ?
my code for save and restore state :
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("CurrentState",CurrentState);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
if (savedInstanceState != null) {
// Restore last state for checked position.
CurrentState = savedInstanceState.getInt("CurrentState", 0);
}
.
.
.
switch (CurrentState){
case 1 :button_DisplayMemoris.performClick();break;
case 2 :
linearLayout_AddMemoris.setVisibility(View.VISIBLE);
linearLayout_Memoris.setVisibility(View.GONE);
linearLayout_DisplayMemoris.setVisibility(View.GONE);
break;
default:break;
}
return inflate;
}
when CurrentState=2 , linearLayout_AddMemoris Not displayed
How do I fix it?
update :
this is my activity :
public class ToolsActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tools);
displayView();
}
public void displayView() {
// update the main content by replacing fragments
Fragment fragment = = new MemoirsFragment();
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.frame_container, fragment);
ft.commit();
}
}
Is there a way to solve the problem?
I have no idea
Add in onCreate method null checking, now you are replace restored fragment by new fragment
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tools);
if (savedInstanceState == null)
displayView();
}

Android rotation: Why fragment not restored by TAG?

I have an activity called MainActivity with a fragment inside called 'HomeFragment'. I want to restore fragment after screen rotation, however something seems missing.
I followed below process:
save fragment by using getSupportFragmentManager().putFragment(...) during onSaveInstanceState
try to restore fragment by fragmentManager.findFragmentByTag during onCreate (after checking that bundle is not null)
However findFragmentByTag returns null.
Here is code snippets inside MainActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set up the action bar to show a dropdown list.
final ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
setupNavigationSpinner(actionBar);
FragmentManager fragmentManager = getSupportFragmentManager();
if (savedInstanceState != null) {
if (fragmentManager.findFragmentByTag("frag") != null) {
// findFragmentByTag always return null
homeFragment = (HomeFragment) fragmentManager
.findFragmentByTag(HomeFragment.ARG_ITEM_ID);
contentFragment = homeFragment;
}else{
homeFragment = new HomeFragment();
switchContent(homeFragment, HomeFragment.ARG_ITEM_ID);
}
} else {
homeFragment = new HomeFragment();
switchContent(homeFragment, HomeFragment.ARG_ITEM_ID);
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
getSupportFragmentManager().putFragment(outState, "frag",homeFragment );
}
public void switchContent(Fragment fragment, String tag) {
FragmentManager fragmentManager = getSupportFragmentManager();
while (fragmentManager.popBackStackImmediate())
;
if (fragment != null) {
FragmentTransaction transaction = fragmentManager
.beginTransaction();
transaction.replace(R.id.content_frame, fragment, tag);
// Only ArticlDetailFragment is added to the back stack.
if (!(fragment instanceof Fragment)) {
transaction.addToBackStack(tag);
}
transaction.commit();
contentFragment = fragment;
}
}
There is no reason to call
getSupportFragmentManager().putFragment(outState, "frag",homeFragment );
A Fragment state will automatically be restored. The problem is that you're not calling the super of your onSaveInstanceState. It should simply look like this:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
Also, you're setting the tag as HomeFragment.ARG_ITEM_ID then looking for "frag". Does HomeFragment.ARG_ITEM_ID == "frag"?

Android - Dynamic Fragment problems on orientation change

I'm having a problem with dynamic fragment . If I'm not change orientation , it work fine . When I change orientation , I click on ListView item . It's not change textview .
This is DynamicActivity class
public class DynamicActivity extends Activity implements FragmentCoordinator{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dynamic);
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
ListContentFragment listContentFragment = new ListContentFragment();
DetailsContentFragment detailsContentFragment = new DetailsContentFragment();
transaction.add(R.id.listContainer, listContentFragment,"listContent");
transaction.add(R.id.detailsContainer, detailsContentFragment,"detailsContent");
transaction.commit();
}
#Override
public void onSetContentDetails(int index) {
FragmentManager fragmentManager = getFragmentManager();
DetailsContentFragment detailsContentFragment = (DetailsContentFragment)fragmentManager.findFragmentByTag("detailsContent");
detailsContentFragment.setContentDetails(index);
}
}
And DetailsContentFragment class
public class DetailsContentFragment extends Fragment {
TextView lbMess;
String[] array;
int saveIndex;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.details_content_fragment, container,false);
array = getResources().getStringArray(R.array.list_details);
lbMess = (TextView)v.findViewById(R.id.lbDetails);
int currentIndex = savedInstanceState == null ? 0 : savedInstanceState.getInt("indexContent",0);
setContentDetails(currentIndex);
return v;
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("indexContent", saveIndex);
}
public void setContentDetails(int index) {
saveIndex = index;
lbMess.setText(array[saveIndex]);
}
}
I have debug but it doesn't have any error . Please give me some advice
I found the problems are :
When the system destroys and re-creates an activity because of a run-time configuration change, the activity automatically
re-instantiates existing fragments.
This isn’t a problem for “static” fragments declared in the activity’s layout.
But for “dynamic” fragments, i need to test for this situation to prevent creating a second instance of my fragment.
I check the Bundle argument passed to my activity’s onCreate() is null.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dynamic);
if(savedInstanceState == null)
{
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
ListContentFragment listContentFragment = new ListContentFragment();
DetailsContentFragment detailsContentFragment = new DetailsContentFragment();
transaction.add(R.id.listContainer, listContentFragment,"listContent");
transaction.add(R.id.detailsContainer, detailsContentFragment,"detailsContent");
transaction.commit();
}
}
And it work fine . I think is helpful for someone have same problems .

Categories

Resources