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.
Related
I have a Main Activity with multiple Fragments. And this is how how I switch the fragments
public void openFragment(final Fragment fragment, String title, String tag){
getSupportFragmentManager().popBackStack();
getSupportFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.add(R.id.container, fragment, tag)
.addToBackStack(null)
.commit();
getSupportActionBar().setTitle(title);
}
I have an INT variable in the Main Activity that I update from each of the Fragment to determine which Fragment was active before an orientation change
private int ACTIVE_FRAGMENT = 0;
During saved instance on the Activity, I save the INT like so
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt(Constants.ACTIVE_EVENT_ID, ACTIVE_FRAGMENT);
super.onSaveInstanceState(outState);
}
And onRestoreInstanceState I go back to the Active Fragment like this
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null){
ACTIVE_FRAGMENT = savedInstanceState.getInt(Constants.ACTIVE_EVENT_ID);
if (ACTIVE_FRAGMENT > 0){
FragmentManager fm = getSupportFragmentManager();
switch (ACTIVE_FRAGMENT) {
case 1:
EventSetupFragmentOne fragmentOne = (EventSetupFragmentOne) fm.findFragmentByTag(Constants.SETUP_FRAGMENT_ONE_TAG);
if (fragmentOne != null) {
openFragment(fragmentOne, getString(R.string.event_setup_title_1), Constants.SETUP_FRAGMENT_ONE_TAG);
}
break;
}
}
}
}
This goes back to the Active Fragment but then throws a No Actitiy found exception
Caused by: java.lang.IllegalStateException: No activity
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1125)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1120)
at android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:1929)
What can I do differently to go back to an Active Fragment after a configuration change?
You don't have to call openFragment on configuration changes.
Fragments will be recreated by the framework.
That's why in onCreate method you don't (re)initialize your Fragments if savedInstanceState == null.
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();
}
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"?
I am useing This Sort of Code To Handle Three Fragment in Main Activity ...
FragmentA is Fixed it One Frame .. I change FragmentB and FragmentC on Button Click on FragmentA.
his Code is Running well either in Portrait or Landscape view .Here is The Code bellow.
public class MainActivity extends FragmentActivity implements
OnSwitchClickListener {
FragmentManager manager;
FragmentA fragA;
FragmentB fragB;
FragmentC fragC;
boolean fragBSet = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragA = new FragmentA();
fragB = new FragmentB();
fragC = new FragmentC();
manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.add(R.id.a_container, fragA, "frag A");
if (fragBSet) {
ft.add(R.id.bc_container, fragC, "frag C");
} else {
ft.add(R.id.bc_container, fragB, "frag B");
}
fragBSet = true;
ft.commit();
}
#Override
public void onSwitchClick(View v) {
Toast.makeText(getApplicationContext(), "Switch clkick from Activity",
Toast.LENGTH_LONG).show();
FragmentTransaction ft = manager.beginTransaction();
if (fragBSet) {
ft.remove(fragB);
ft.add(R.id.bc_container, fragC, "frag C");
fragBSet = false;
} else {
ft.remove(fragC);
ft.add(R.id.bc_container, fragB, "frag B");
fragBSet = true;
}
ft.commit();
}
This Code is Running well either in Portrait or Landscape view ... But When Ever I change the Orientation The Two Fragments Override One Another.
Need Solution.
When your orientation change, your Activity is recreated.
"In the scenario where a user rotates their device, Android will destroy your
application’s activity(s). Before destroying them it calls, onSaveInstanceState,
allowing developers to persist data. Once the activity is recreated post
rotation, the OS will call onRestoreInstanceState giving developers a chance to
restore the application state pre-rotation."
You should try to save your Fragments State.
putFragment: Put a reference to a fragment in a Bundle. This Bundle can be persisted as saved state, and when later restoring getFragment(Bundle, String) will return the current instance of the same fragment.
#Override
protected void onSaveInstanceState(Bundle outState) {
FragmentManager manager = getFragmentManager();
manager.putFragment(outState, MyFragment.TAG, mMyFragment);
}
getFragment: Retrieve the current Fragment instance for a reference previously placed with putFragment(Bundle, String, Fragment).
private void instantiateFragments(Bundle inState) {
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
if (inState != null) {
mMyFragment = (MyFragment) manager.getFragment(inState, MyFragment.TAG);
} else {
mMyFragment = new MyFragment();
transaction.add(R.id.fragment, mMyFragment, MyFragment.TAG);
transaction.commit();
}
}
restoreFragment:
#Override
protected void onRestoreInstanceState(Bundle inState) {
instantiateFragments(inState);
}
Hope this helps.
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 .