Fragment setRetainInstance not works (Android support lib) [duplicate] - android

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Android fragments setRetainInstance(true) not works (Android support library)
I wrote a simple test project, but I cant understand why I always receive savedInstanceState = null in lifecycle methods onCreate, onCreateView and onActivityCreated. I change the screen orientation, see the log, but state not saved.
Tell me please where is my mistake.
Thanks.
The code of fragment class is:
public class TestFragment extends Fragment {
private String state = "1";
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (savedInstanceState != null) {
//never works
state = savedInstanceState.getString("state");
}
//always prints 1
Toast.makeText(getActivity(), state, Toast.LENGTH_SHORT).show();
return inflater.inflate(R.layout.fragment_layout, container, false);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("state", "2");
Log.e("", "saved 2");
}
}
EDIT
When i try to use setRetainInstance i have no result again((( I change a state to 2 using btn1, but after changing orientation i see 1 when pressing on btn2. Help please(
public class TestFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
setRetainInstance(true);
super.onCreate(savedInstanceState);
}
private String state = "1";
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_layout, container, false);
//button for changing state
((Button)view.findViewById(R.id.button1)).setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
state = "2";
}
});
//button for showing state
((Button)view.findViewById(R.id.button2)).setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
Toast.makeText(getActivity(), state, Toast.LENGTH_SHORT).show();
}
});
return view;
}
}

I have seen the same issue. But you can save the instate state in an other way, thanks to the method setRetainInstance(true), all your class data are saved automatically.
#Override
public void onCreate(Bundle savedInstanceState) {
setRetainInstance(true);
super.onCreate(savedInstanceState);
}
EDIT
Try to also add these params when you declare your activities :
<activity
android:name="MyActivity"
android:configChanges="orientation|keyboardHidden|screenSize" />

Related

Show dialog above view pager that has nested fragments

I have setup a very simple test project https://github.com/ArtworkAD/ViewPagerDialogTest to evaluate following situation: the main activity has a view pager which hosts a single fragment using support fragment manager:
public class MainActivity extends AppCompatActivity {
// ...
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
// ...
tabLayout.setupWithViewPager(viewPager);
}
#Override
protected void onResume() {
super.onResume();
MainActivity.CustomDialog dialog = (MainActivity.CustomDialog) getSupportFragmentManager().findFragmentByTag(MainActivity.CustomDialog.TAG);
if (dialog == null) {
new MainActivity.CustomDialog().show(getSupportFragmentManager().beginTransaction(), MainActivity.CustomDialog.TAG);
}
}
// ...
}
When the activity is resumed a dialog fragment is shown inside the main activity.
The single fragment inside the view pager is defined like this:
public class RootFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.root_fragment, container, false);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();
}
return root;
}
}
This root fragment allows us to stack other fragments on the "root_frame". So we stack another and another:
public class FirstLevelFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setRetainInstance(true);
View root = inflater.inflate(R.layout.first_level_fragment, container, false);
root.findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
SecondLevelFragment f = (SecondLevelFragment) getActivity().getSupportFragmentManager().findFragmentByTag("NESTED");
if (f == null) {
getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
}
}
});
return root;
}
public static class SecondLevelFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setRetainInstance(true);
return inflater.inflate(R.layout.second_level_fragment, container, false);
}
}
}
This works great! The stacking idea is taken from https://stackoverflow.com/a/21453571/401025 . However when dialog is shown and the users goes to the second level fragment and rotates the screen I get following exception:
E/AndroidRuntime: java.lang.RuntimeException: Unable to start activity
ComponentInfo{de.azzoft.viewpagerdialogtest/de.azzoft.viewpagerdialogtest.MainActivity}:
java.lang.IllegalArgumentException: No view found for id 0x7f0c0083
(de.azzoft.viewpagerdialogtest:id/root_frame) for fragment
SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}
E/AndroidRuntime: Caused by: java.lang.IllegalArgumentException: No
view found for id 0x7f0c0083
(de.azzoft.viewpagerdialogtest:id/root_frame) for fragment
SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}
Full stack trace: https://github.com/ArtworkAD/ViewPagerDialogTest/blob/master/README.md
Without the dialog appearing everything works great. You can test it by downloading the test project.
It seems that the dialog, which is actually a fragment, messes up fragment hierarchy when it is added to the activity. Any ideas how to fix this?
It is important that the second fragment is retained.
No view found for id 0x7f0c0083 (de.azzoft.viewpagerdialogtest:id/root_frame) for fragment SecondLevelFragment
When Activity recreates on rotate, the Activity FragmentManger tries to add the SecondLevelFragment into R.id.root_frame . But the root_frame view is not in Activity layout, its in FirstLevelFragment layout. Thats why the app crashes.
You have to make two changes to fix this issue.
Add the FirstLevelFragment into the RootFragment using the getChildFragmentManager
getChildFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();
Add the SecondLevelFragment using FragmentManager
getFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
Finally remove the setRetainInstance from FirstLevelFragment and SecondLevelFragment as nested fragments doesn't required to set retain.
If you need to pop back the SecondLevelFragment on back press you need to pass the back press the event to RootFragment and pop from back stack.
Override the back press on activity
#Override
public void onBackPressed() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.viewpager);
if(fragment instanceof RootFragment){
boolean handled = ((RootFragment)fragment).onBackPressed();
if(handled){
return;
}
}
super.onBackPressed();
}
And handle the back press on RootFragment
public boolean onBackPressed() {
int count = getChildFragmentManager().getBackStackEntryCount();
if(count > 0){
getChildFragmentManager().popBackStackImmediate();
return true;
}
return false;
}
I created a Pull request to your repository . please check
https://github.com/ArtworkAD/ViewPagerDialogTest/pull/1
Let me know if any questions.
If you override onDismiss so resolved crash. enjoy it.
#Override
protected void onResume() {
super.onResume();
DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag(TAG);
if(dialog == null){
CustomDialog.newInstance().show(getSupportFragmentManager(), TAG);
}
}
public static class CustomDialog extends DialogFragment {
public static CustomDialog newInstance() {
CustomDialog d = new CustomDialog();
return d;
}
#Override
public void onDismiss(DialogInterface dialog) {
// super.onDismiss(dialog);
Toast.makeText(getActivity(), "onDismiss", Toast.LENGTH_LONG).show();
}
#Override
public void onCancel(DialogInterface dialog) {
// super.onCancel(dialog);
Toast.makeText(getActivity(), "onCancel", Toast.LENGTH_LONG).show();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Dialog");
builder.setMessage("This is a message!");
builder.setPositiveButton("Okay", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();
}
});
builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();
}
});
return builder.show();
}
}
If you want to keep the state of your Fragments you should use a FragmentStatePagerAdapter.
From the docs:
Implementation of PagerAdapter that uses a Fragment to manage each
page. This class also handles saving and restoring of fragment's
state.
If you use this you can also remove the setRetainInstance(true) calls.
Well, I had downloaded your Test app and it seems that I have fixed the problem.
In your FirstLevelFragment class, comment the following line
//if (nestedNestedFragment == null) {
getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
//}
And
Comment setRetainInstance(true); in SecondLevelFragment
I think you missed setContentView() in onCreate() of your Activity. See your Fragment can not be added without a View hierarchy. Your Fragment is hosted by an activity. So you need to set the content to the activity first.
Hope this Helps,
Thanks.

Android Fragment onCreateView - inflating every time it's called?

I have been putting some debugging into my app to try to work out what is happening with the lifecycle.
I have some tabs and each tab content is a different fragment, each time a tab is changed onCreateView is called in the corresponding fragment.
In some of my onCreateViews I am currently mocking up some data and injecting table rows etc and then inflating the view every time. Like in the example below:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(LOG_TAG, "******************* onCreateView() is being called in the Container Fragment *********************");
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_patient, container, false);
mTabHost = (FragmentTabHost)rootView.findViewById(android.R.id.tabhost);
mTabHost.setup(getActivity(), getChildFragmentManager(), android.R.id.tabcontent);
//Set up tabs
mTabHost.addTab(mTabHost.newTabSpec("home_addresses").setIndicator("Home Addresses"), HomeAddressesFragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("postal_addresses").setIndicator("Postal Addresses"), PostalAddressesFragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("temp_addresses").setIndicator("Temporary Addresses"), TemporaryAddressesFragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("email_other").setIndicator("Email / Other"), TemporaryAddressesFragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("tel_fax").setIndicator("Telephone & Fax"), TemporaryAddressesFragment.class, null);
//Set to home addresses tab
mTabHost.setCurrentTab(0);
tempAddressBtn = (Button)rootView.findViewById(R.id.temp_addr_tab_btn);
tempAddressBtn.setTransformationMethod(null);
tempAddressBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
tabHandler(v);
}
});
postalAddressBtn = (Button)rootView.findViewById(R.id.postal_addr_tab_btn);
postalAddressBtn.setTransformationMethod(null);
postalAddressBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
tabHandler(v);
}
});
homeAddressBtn = (Button)rootView.findViewById(R.id.home_addr_tab_btn);
homeAddressBtn.setTransformationMethod(null);
homeAddressBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
tabHandler(v);
}
});
telFaxBtn = (Button)rootView.findViewById(R.id.tel_fax_tab_btn);
telFaxBtn.setTransformationMethod(null);
telFaxBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
tabHandler(v);
}
});
emailOtherBtn = (Button)rootView.findViewById(R.id.email_other_tab_btn);
emailOtherBtn.setTransformationMethod(null);
emailOtherBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
tabHandler(v);
}
});
return rootView;
}
My question is should I be doing this each time the onCreateView method is called? Is the view already cached somewhere?
I am seeing code in tutorials that looks a bit like:
#Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(R.layout.pager_activity);
if (savedInstance == null) {
PagerFragment frag = PagerFragment.newInstance(buildPagerData());
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction().add(R.id.layout_fragments, frag, PAGER_TAG).commit();
}
findViewById(R.id.btnFragments).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
changeFragmentVisibility();
}
});
}
I know this Fragment in the example above extends from FragmentActivity and mine just extend from Fragment but it is using the fragment manager and checking if the state is null before deciding to instantiate a new fragment.
Edit: I am guessing as well that as this happens in onCreate and not onCreateView it happens less frequently?
I am wondering if this is something that I should be doing or if its ok to continue with the way I am going?
In my opinion you should manage your tabs from the parent Activity. Each Fragment should be responsible for one part of the UI.
Another advantage of that approach is that each Fragment layout will be a bit easier. And it will be easier to apply different containers if needed (e.g ViewPager).

Is it possible to convert Blank Activities to Fragment Activities

I currently have an Unit Converter app that I'm working in.
Here I've used multiple Blank Activities. Where each Unit's Activity can be opened using MainActivity. But now I want to make it tablet friendly.
Hence I want to use FragmentActivity now. Is it possible to convert the Blank Activities to Fragment Activities.?
All you need to do is take all View-specific logic from the Activity to a Fragment, then load the Fragment in your Activity.
For example,
public class MainActivity extends Activity {
#InjectView(R.id.button)
public Button button;
#OnClick(R.id.button)
public void onButtonClick(View view) {
Toast.makeText(this, "Hello!", Toast.LENGTH_SHORT).show();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
}
}
This type of logic goes in
public class MainFragment extends Fragment {
#InjectView(R.id.button)
public Button button;
#OnClick(R.id.button)
public void onButtonClick(View view) {
Toast.makeText(this, "Hello!", Toast.LENGTH_SHORT).show();
}
#Override
public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
ButterKnife.inject(this, view);
return view;
}
}
And your Activity needs to display this fragment either statically, or dynamically. If you go dynamical, you'll need the following lines in your Activity:
public class MainActivity extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
if(savedInstanceState == null) {
fm.beginTransaction()
.add(R.id.container, new MainFragment())
.commit();
}
fm.addOnBackStackChangedListener(new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(getSupportFragmentManager().getBackStackEntryCount() == 0) finish();
}
});
}
}
If you go static, then you need to specify the fragments in your layout XML for the activity.
http://developer.android.com/guide/components/fragments.html#Adding
I would visit the Android website as they give a fairly good explanation on how fragments work.
You can learn how to add them to your existing application by another Android link here.

Fragment Transaction inside worker thread

Basically i tried doing a fragment transaction inside a background thread, and i get can't perform this action after onSavedInstanceState. This is what i have done:
I have an activity with two fragments, Fragment A (Contains UI all elements) and Fragment B(contains progress bar) . When the activity is created fragment A is added so the user sees UI elements(Buttons and Edittexts). Then when a button is clicked in Fragment A, (1) a background task is started in the parent activity, and (2) Fragment A is replaced with Fragment B. All this works fine.
But when the task is finished if the task was successful i would like to start a new activity and if it fails i want the current fragment to be replaced with the original one. I have no problem with starting the activity, but when i try to do the fragment transaction, the app crashes saying can't perform this action after onSavedInstanceState.
I did a lot of research on this and found a lot of useful things here, here, but i still haven't been able to solve my problem. The first link is a question on SO, one of the answers was to shift calling the fragment transaction to the onResume method of the activity, but i don't know how to do this. Also i tried using commitAllowingStateLoss and the app still crashes, but the logcat says Activity has been destroyed.
Note: If there is no configuration change, everything runs smoothly.
Here is my Code:
Containing Activity
public class MainActivity extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("tag", "mainActivity onCreate");
Fragment taskf =getSupportFragmentManager().findFragmentByTag("taskfrag");
//Put the Fragment A if Fragment B is not showing
if(savedInstanceState == null){
if(taskf == null){
Log.i("tag", "MainFragment added");
FragmentTransaction fragmentTransaction= getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.wrapper, new MainFragment() , "mainfrag").commit();
}
}
}
//Fragment B is added here
public void addFragment(){
Log.i("tag", "fragment added");
this.getSupportFragmentManager().beginTransaction().replace(R.id.wrapper, new TaskFragment(), "taskfrag").commit();
}
// My dummy Task
public void startTask(){
Log.i("tag", "start Task");
new Thread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
Log.i("tag", "publishProgress(" + i + "%)");
SystemClock.sleep(100);
}
getSupportFragmentManager().beginTransaction().replace(R.id.wrapper, new MainFragment(), "mainfrag").commitAllowingStateLoss();
}
}).start();
}
}
Fragment A
public class MainFragment extends Fragment {
View fView;
ProgressBar bar;
TextView textView;
Button button;
#Override
public void onCreate(Bundle savedInstanceState) {
Log.i("tag", "onCreate in Main Fragment ");
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
Log.i("tag", "onCreate View in Mainfragment");
fView = inflater.inflate(R.layout.mainfragment, container, false);
textView = (TextView) fView.findViewById(R.id.textview);
button = (Button) fView.findViewById(R.id.button);
//Button Click Listener
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
Log.i("tag", "button clicked");
((MainActivity)getActivity()).startTask();
((MainActivity)getActivity()).addFragment();
}
});
return fView;
}
}
Fragment B
public class TaskFragment extends Fragment{
View fview;
ProgressBar bar;
#Override
public void onCreate(Bundle savedInstanceState) {
Log.i("tag", "TaskFragment Created");
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
Log.i("tag", "onCreateView in TaskFragment");
fview = inflater.inflate(R.layout.progressfragment, container, false);
bar = (ProgressBar) fview.findViewById(R.id.sendingSmsProgress);
return fview;
}
}
Thanks.

Button in tab fragment

I was following tutorials on how to make Tabs using fragments.
each tab has this in the java files:
public class rdfri extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (container == null) {
// We have different layouts, and in one of them this
// fragment's containing frame doesn't exist. The fragment
// may still be created from its saved state, but there is
// no reason to try to create its view hierarchy because it
// won't be displayed. Note this is not needed -- we could
// just run the code below, where we would create and return
// the view hierarchy; it would just never be used.
return null;
}
return (LinearLayout)inflater.inflate(R.layout.rdfri, container, false);
}
}
I would like to try and get a imageButton in the fragment. I figured image bottons work with this code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.rdmon);
ImageButton rainbowbook = (ImageButton) findViewById(R.id.imageButton1);
rainbowbook.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent myIntent = Intent(rdmon.this, RainbowBookActivity.class);
rdmon.this.startActivity(myIntent);
}
});
}
So how would I go about getting the button code in the fragment code?
Thanks.
Put the button code in the overridden onActivityCreated method in your fragment class.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ImageButton rainbowbook = (ImageButton) findViewById(R.id.imageButton1);
rainbowbook.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent myIntent = Intent(rdmon.this, RainbowBookActivity.class);
rdmon.this.startActivity(myIntent);
}
});
}
This assumes of course that your imagebutton is contained in your fragment layout.

Categories

Resources