android fragment destroyed after home or overview buttons are pressed - android

I'm fairly new to android programming, so please bear with me. I've tried searching all over SO, tutorialspoint, big nerd ranch, etc, but I'm not finding/understanding this: how do I make a fragment persist after a user has pressed the home or overview buttons? For example:
I have two fragments A and B in an activity with a toggle button to switch between the two fragments. By default, fragment A loads on the screen when the activity is created. If I toggle to fragment B and press either home or overview and then return to the app, fragment A is displayed. I understand why it's displaying, but what I don't understand is how to keep fragment B displayed.
I've read about the backstack, savedInstanceState, and setRetainInstance (among several others), but haven't been able to figure it out.
I'm overriding onAttach(Context context), onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState), and onViewCreated(View view, Bundle savedInstanceState) in both fragments, and onStart() and onCreate(Bundle savedInstanceState) in my activity.
fragments:
#Override
public void onAttach(Context context) {
this.context = context;
faContext = (FragmentActivity) context;
super.onAttach(context);
}// end onAttach(Activity activity)
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
Bundle bundle = this.getArguments();
if(bundle != null) {
String bundleAdminId = getString(R.string.bundle_admin_name);
adminName = bundle.getString(bundleAdminId);
}
else {
Log.d("BUNDLE_Profile", "bundle is null");
}
// Defines the xml file for the fragment
return inflater.inflate(R.layout.fragment_profile, parent, false);
}// end onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
onViewCreated only has button event handlers
activity:
#Override
public void onStart() {
super.onStart();
// start the home fragment
createFragment(new HomeFragment(), R.id.frag_container);
// update the local storage
}// end onStart()
private void createFragment(Fragment fragment, int fragContainerId) {
// make sure the passed fragment isn't null
if(fragment != null) {
FragmentTransaction ft = faContext.getSupportFragmentManager().beginTransaction();
ft.replace(fragContainerId, fragment);
ft.commit();
}
}// end createFragment(Fragment fragment)`
onCreate also has button handlers

Use tags to open a fragment.Try this function
public void openNewFragment(Fragment f, String tag) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
{
if (fragment != null) {
if (currentFragment != null) {
if (fragment.getClass().equals(currentFragment.getClass())) {
drawerLayout.closeDrawers();
return;
}
}
ft.show(fragment);
ft.hide(currentFragment);
currentFragment = fragment;
} else {
if (currentFragment != null)
ft.hide(currentFragment);
ft.add(R.id.container, f, tag);
currentFragment = f;
}
ft.commit();
}
}
In this function you have to provide fragment name and its tag.

You cannot retain the fragment position when re-launching app unless it is stored. So one way could be while switching between the fragments, store the current position of fragment in Shared Preference.While restarting the application read the previously stored fragment position and update SharedPreferences value when switched.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sharedPreferences = getSharedPreferences("fragmentPosition",MODE_PRIVATE);
if(sharedPreferences.getInt("position",0)==1) loadFragmentOne();
else loadFragmentTwo();
}
public void onToggleMethod(){
if(currentToggle ==1){
LoadFragmentTwo();
UpdateSharedPreference();
}
else{
loadFragmentOne();
UpdateSharedPreference();
}
}

Obviously the activity goes to the stopped mode and getting started when you return back to the activity from home after some time. May be because of the resources are freed from the memory.
To know more better on this just try to be on fragment b and immediately make the home press and again come back to app through the recent apps section and see the behavior.
Also if the problem is what i have stated above then.. try following
Try creating a variable that is static and try to remember the last loaded fragment and in the on resume of the activity load the fragment which was last loaded.
Else in the onstart load the fragment the using the fragment manager with a tag and also put a condition before the loading to check whether the fragment is alredy in memory if so dont load anything.
Something like this..
onStart(){
if(fragmentManager.getFragmentByTag("String tag")== null){
createFragment();
}
}
Try and let me know if this is useful.

Try to Change the onStart method like this ,
#Override
public void onStart() {
super.onStart();
// start the home fragment
if(faContext.getSupportFragmentManager().findFragmentById(R.id.frag_container) == null) {
createFragment(new HomeFragment(), R.id.frag_container);
}
// update the local storage
}// end onStart()

Related

Android-Save state of Fragment with RecyclerView [duplicate]

I've written up a dummy activity that switches between two fragments. When you go from FragmentA to FragmentB, FragmentA gets added to the back stack. However, when I return to FragmentA (by pressing back), a totally new FragmentA is created and the state it was in is lost. I get the feeling I'm after the same thing as this question, but I've included a complete code sample to help root out the issue:
public class FooActivity extends Activity {
#Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(android.R.id.content, new FragmentA());
transaction.commit();
}
public void nextFragment() {
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(android.R.id.content, new FragmentB());
transaction.addToBackStack(null);
transaction.commit();
}
public static class FragmentA extends Fragment {
#Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View main = inflater.inflate(R.layout.main, container, false);
main.findViewById(R.id.next_fragment_button).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((FooActivity) getActivity()).nextFragment();
}
});
return main;
}
#Override public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save some state!
}
}
public static class FragmentB extends Fragment {
#Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.b, container, false);
}
}
}
With some log messages added:
07-05 14:28:59.722 D/OMG ( 1260): FooActivity.onCreate
07-05 14:28:59.742 D/OMG ( 1260): FragmentA.onCreateView
07-05 14:28:59.742 D/OMG ( 1260): FooActivity.onResume
<Tap Button on FragmentA>
07-05 14:29:12.842 D/OMG ( 1260): FooActivity.nextFragment
07-05 14:29:12.852 D/OMG ( 1260): FragmentB.onCreateView
<Tap 'Back'>
07-05 14:29:16.792 D/OMG ( 1260): FragmentA.onCreateView
It's never calling FragmentA.onSaveInstanceState and it creates a new FragmentA when you hit back. However, if I'm on FragmentA and I lock the screen, FragmentA.onSaveInstanceState does get called. So weird...am I wrong in expecting a fragment added to the back stack to not need re-creation? Here's what the docs say:
Whereas, if you do call addToBackStack() when removing a fragment,
then the fragment is stopped and will be resumed if the user navigates
back.
If you return to a fragment from the back stack it does not re-create the fragment but re-uses the same instance and starts with onCreateView() in the fragment lifecycle, see Fragment lifecycle.
So if you want to store state you should use instance variables and not rely on onSaveInstanceState().
Comparing to Apple's UINavigationController and UIViewController, Google does not do well in Android software architecture. And Android's document about Fragment does not help much.
When you enter FragmentB from FragmentA, the existing FragmentA instance is not destroyed. When you press Back in FragmentB and return to FragmentA, we don't create a new FragmentA instance. The existing FragmentA instance's onCreateView() will be called.
The key thing is we should not inflate view again in FragmentA's onCreateView(), because we are using the existing FragmentA's instance. We need to save and reuse the rootView.
The following code works well. It does not only keep fragment state, but also reduces the RAM and CPU load (because we only inflate layout if necessary). I can't believe Google's sample code and document never mention it but always inflate layout.
Version 1(Don't use version 1. Use version 2)
public class FragmentA extends Fragment {
View _rootView;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (_rootView == null) {
// Inflate the layout for this fragment
_rootView = inflater.inflate(R.layout.fragment_a, container, false);
// Find and setup subviews
_listView = (ListView)_rootView.findViewById(R.id.listView);
...
} else {
// Do not inflate the layout again.
// The returned View of onCreateView will be added into the fragment.
// However it is not allowed to be added twice even if the parent is same.
// So we must remove _rootView from the existing parent view group
// (it will be added back).
((ViewGroup)_rootView.getParent()).removeView(_rootView);
}
return _rootView;
}
}
------Update on May 3 2005:-------
As the comments mentioned, sometimes _rootView.getParent() is null in onCreateView, which causes the crash. Version 2 removes _rootView in onDestroyView(), as dell116 suggested. Tested on Android 4.0.3, 4.4.4, 5.1.0.
Version 2
public class FragmentA extends Fragment {
View _rootView;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (_rootView == null) {
// Inflate the layout for this fragment
_rootView = inflater.inflate(R.layout.fragment_a, container, false);
// Find and setup subviews
_listView = (ListView)_rootView.findViewById(R.id.listView);
...
} else {
// Do not inflate the layout again.
// The returned View of onCreateView will be added into the fragment.
// However it is not allowed to be added twice even if the parent is same.
// So we must remove _rootView from the existing parent view group
// in onDestroyView() (it will be added back).
}
return _rootView;
}
#Override
public void onDestroyView() {
if (_rootView.getParent() != null) {
((ViewGroup)_rootView.getParent()).removeView(_rootView);
}
super.onDestroyView();
}
}
WARNING!!!
This is a HACK! Though I am using it in my app, you need to test and read comments carefully.
I guess there is an alternative way to achieve what you are looking for.
I don't say its a complete solution but it served the purpose in my case.
What I did is instead of replacing the fragment I just added target fragment.
So basically you will be going to use add() method instead replace().
What else I did.
I hide my current fragment and also add it to backstack.
Hence it overlaps new fragment over the current fragment without destroying its view.(check that its onDestroyView() method is not being called. Plus adding it to backstate gives me the advantage of resuming the fragment.
Here is the code :
Fragment fragment=new DestinationFragment();
FragmentManager fragmentManager = getFragmentManager();
android.app.FragmentTransaction ft=fragmentManager.beginTransaction();
ft.add(R.id.content_frame, fragment);
ft.hide(SourceFragment.this);
ft.addToBackStack(SourceFragment.class.getName());
ft.commit();
AFAIK System only calls onCreateView() if the view is destroyed or not created.
But here we have saved the view by not removing it from memory. So it will not create a new view.
And when you get back from Destination Fragment it will pop the last FragmentTransaction removing top fragment which will make the topmost(SourceFragment's) view to appear over the screen.
COMMENT: As I said it is not a complete solution as it doesn't remove the view of Source fragment and hence occupying more memory than usual. But still, serve the purpose. Also, we are using a totally different mechanism of hiding view instead of replacing it which is non traditional.
So it's not really for how you maintain the state, but for how you maintain the view.
I would suggest a very simple solution.
Take the View reference variable and set view in OnCreateView. Check if view already exists in this variable, then return same view.
private View fragmentView;
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (fragmentView != null) {
return fragmentView;
}
View view = inflater.inflate(R.layout.yourfragment, container, false);
fragmentView = view;
return view;
}
I came across this problem in a Fragment containing a map, which has too many setup details to save/reload.
My solution was to basically keep this Fragment active the whole time (similar to what #kaushal mentioned).
Say you have current Fragment A and wants to display Fragment B.
Summarizing the consequences:
replace() - remove Fragment A and replace it with Fragment B. Fragment A will be recreated once brought to the front again
add() - (create and) add a Fragment B and it overlap Fragment A, which is still active in the background
remove() - can be used to remove Fragment B and return to A. Fragment B will be recreated when called later on
Hence, if you want to keep both Fragments "saved", just toggle them using hide()/show().
Pros: easy and simple method to keep multiple Fragments running
Cons: you use a lot more memory to keep all of them running. May run into problems, e.g. displaying many large bitmaps
onSaveInstanceState() is only called if there is configuration change.
Since changing from one fragment to another there is no configuration change so no call to onSaveInstanceState() is there. What state is not being save? Can you specify?
If you enter some text in EditText it will be saved automatically. Any UI item without any ID is the item whose view state shall not be saved.
first: just use add method instead of replace method of FragmentTransaction class then you have to add secondFragment to stack by addToBackStack method
second :on back click you have to call popBackStackImmediate()
Fragment sourceFragment = new SourceFragment ();
final Fragment secondFragment = new SecondFragment();
final FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.add(R.id.child_fragment_container, secondFragment );
ft.hide(sourceFragment );
ft.addToBackStack(NewsShow.class.getName());
ft.commit();
((SecondFragment)secondFragment).backFragmentInstanceClick = new SecondFragment.backFragmentNewsResult()
{
#Override
public void backFragmentNewsResult()
{
getChildFragmentManager().popBackStackImmediate();
}
};
Kotlin and ViewBinding Solution
I am using replace() and backstack() method for FragmentTransaction. The problem is that the backstack() method calls the onCreateView of the Previous Fragment which causes in re-built of Fragment UI. Here is a solution for that:
private lateinit var binding: FragmentAdRelevantDetailsBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?
): View {
if (!this::binding.isInitialized)
binding = FragmentAdRelevantDetailsBinding.inflate(layoutInflater, container, false)
return binding.root
}
Here, since onSaveInstanceState in fragment does not call when you add fragment into backstack. The fragment lifecycle in backstack when restored start onCreateView and end onDestroyView while onSaveInstanceState is called between onDestroyView and onDestroy. My solution is create instance variable and init in onCreate. Sample code:
private boolean isDataLoading = true;
private ArrayList<String> listData;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
isDataLoading = false;
// init list at once when create fragment
listData = new ArrayList();
}
And check it in onActivityCreated:
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if(isDataLoading){
fetchData();
}else{
//get saved instance variable listData()
}
}
private void fetchData(){
// do fetch data into listData
}
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener()
{
#Override
public void onBackStackChanged()
{
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
{
//setToolbarTitle("Main Activity");
}
else
{
Log.e("fragment_replace11111", "replace");
}
}
});
YourActivity.java
#Override
public void onBackPressed()
{
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.Fragment_content);
if (fragment instanceof YourFragmentName)
{
fragmentReplace(new HomeFragment(),"Home Fragment");
txt_toolbar_title.setText("Your Fragment");
}
else{
super.onBackPressed();
}
}
public void fragmentReplace(Fragment fragment, String fragment_name)
{
try
{
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.Fragment_content, fragment, fragment_name);
fragmentTransaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);
fragmentTransaction.addToBackStack(fragment_name);
fragmentTransaction.commitAllowingStateLoss();
}
catch (Exception e)
{
e.printStackTrace();
}
}
My problem was similar but I overcame me without keeping the fragment alive. Suppose you have an activity that has 2 fragments - F1 and F2. F1 is started initially and lets say in contains some user info and then upon some condition F2 pops on asking user to fill in additional attribute - their phone number. Next, you want that phone number to pop back to F1 and complete signup but you realize all previous user info is lost and you don't have their previous data. The fragment is recreated from scratch and even if you saved this information in onSaveInstanceState the bundle comes back null in onActivityCreated.
Solution:
Save required information as an instance variable in calling activity. Then pass that instance variable into your fragment.
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Bundle args = getArguments();
// this will be null the first time F1 is created.
// it will be populated once you replace fragment and provide bundle data
if (args != null) {
if (args.get("your_info") != null) {
// do what you want with restored information
}
}
}
So following on with my example: before I display F2 I save user data in the instance variable using a callback. Then I start F2, user fills in phone number and presses save. I use another callback in activity, collect this information and replace my fragment F1, this time it has bundle data that I can use.
#Override
public void onPhoneAdded(String phone) {
//replace fragment
F1 f1 = new F1 ();
Bundle args = new Bundle();
yourInfo.setPhone(phone);
args.putSerializable("you_info", yourInfo);
f1.setArguments(args);
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, f1).addToBackStack(null).commit();
}
}
More information about callbacks can be found here: https://developer.android.com/training/basics/fragments/communicating.html
Replace a Fragment using following code:
Fragment fragment = new AddPaymentFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.frame, fragment, "Tag_AddPayment")
.addToBackStack("Tag_AddPayment")
.commit();
Activity's onBackPressed() is :
#Override
public void onBackPressed() {
android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 1) {
fm.popBackStack();
} else {
finish();
}
Log.e("popping BACKSTRACK===> ",""+fm.getBackStackEntryCount());
}
Public void replaceFragment(Fragment mFragment, int id, String tag, boolean addToStack) {
FragmentTransaction mTransaction = getSupportFragmentManager().beginTransaction();
mTransaction.replace(id, mFragment);
hideKeyboard();
if (addToStack) {
mTransaction.addToBackStack(tag);
}
mTransaction.commitAllowingStateLoss();
}
replaceFragment(new Splash_Fragment(), R.id.container, null, false);
Perfect solution that find old fragment in stack and load it if exist in stack.
/**
* replace or add fragment to the container
*
* #param fragment pass android.support.v4.app.Fragment
* #param bundle pass your extra bundle if any
* #param popBackStack if true it will clear back stack
* #param findInStack if true it will load old fragment if found
*/
public void replaceFragment(Fragment fragment, #Nullable Bundle bundle, boolean popBackStack, boolean findInStack) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
String tag = fragment.getClass().getName();
Fragment parentFragment;
if (findInStack && fm.findFragmentByTag(tag) != null) {
parentFragment = fm.findFragmentByTag(tag);
} else {
parentFragment = fragment;
}
// if user passes the #bundle in not null, then can be added to the fragment
if (bundle != null)
parentFragment.setArguments(bundle);
else parentFragment.setArguments(null);
// this is for the very first fragment not to be added into the back stack.
if (popBackStack) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else {
ft.addToBackStack(parentFragment.getClass().getName() + "");
}
ft.replace(R.id.contenedor_principal, parentFragment, tag);
ft.commit();
fm.executePendingTransactions();
}
use it like
Fragment f = new YourFragment();
replaceFragment(f, null, boolean true, true);
Calling the Fragment lifecycle methods properly and using onSavedInstanceState() can solve the problem.
i.e Call onCreate(), onCreateView(), onViewCreated() and onSavedInstanceState() properly and save Bundle in onSaveInstanceState() and resotre it in onCreate() method.
I don't know how but it worked for me without any error.
If anyone can explain it will very much appreciated.
public class DiagnosisFragment extends Fragment {
private static final String TITLE = "TITLE";
private String mTitle;
private List mList = null;
private ListAdapter adapter;
public DiagnosisFragment(){}
public DiagnosisFragment(List list, String title){
mList = list;
mTitle = title;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null){
mList = savedInstanceState.getParcelableArrayList(HEALTH_ITEMS);
mTitle = savedInstanceState.getString(TITLE);
itemId = savedInstanceState.getInt(ID);
mChoiceMode = savedInstanceState.getInt(CHOICE_MODE);
}
getActivity().setTitle(mTitle);
adapter = (ListAdapter) new HealthAdapter(mList, getContext()).load(itemId);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.diagnosis_fragment, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ListView lv = view.findViewById(R.id.subLocationsSymptomsList);
lv.setAdapter(adapter);
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
outState.putParcelableArrayList(HEALTH_ITEMS, (ArrayList) mList);
outState.putString(TITLE, mTitle);
}
}
For who has looking for solution :
#Override
public void onDestroyView() {
Bundle savedState=new Bundle();
// put your data in bundle
// if you have object and want to restore you can use gson to convert it
//to sring
if (yourObject!=null){
savedState.putString("your_object_key",new Gson().toJson(yourObject));
}
if (getArguments()==null){
setArguments(new Bundle());
}
getArguments().putBundle("saved_state",savedState);
super.onDestroyView();
}
and in onViewCreated() method :
Bundle savedState=null;
if (getArguments()!=null){
savedState=getArguments().getBundle("saved_state");
}
if (savedState!=null){
// set your restored data to your view
}

Fragment BackStack lifecycle issues

I have a fragment activity, which has a fragment A. Fragment A does some important things in onViewCreated method:
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mission1 = view.findViewById(R.id.mission1);
mission2 = view.findViewById(R.id.mission2);
mission3a = view.findViewById(R.id.mission3a);
mission3b = view.findViewById(R.id.mission3b);
imageButtonList.add(mission1);
imageButtonList.add(mission2);
imageButtonList.add(mission3a);
imageButtonList.add(mission3b);
prepareButtons();
}
OK, now, this fragment A, has a button which creates a new fragment B, but I want to add a "back" button in the new Fragment B, so I added fragment A into backstack when launching Fragment B:
FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
CampaignMissionFragment fragment = CampaignMissionFragment.newInstance(auxMission);
ft.replace(R.id.fragmentContainer, fragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(null);
ft.commit();
And in Fragment B, I added this to back button onClick():
getFragmentManager().popBackStack();
The problem is that when I press back on Fragment B, onViewCreated method of Fragment A is being called, so my imageButtonList array is getting a wrong amount of buttons inside because of the same buttons are being inserted again.
What would be the correct way to solve this issue? I thought that Fragment had similar behavior to Activities, where you can solve this issue putting the code that you don't want to execute two times in onCreate. But in this case, I can't do that because my views are not available in onCreate method of the fragment.
You may try to clear the imageButtonList in onDestroyView,
Another solution would be to check if savedInstanceState is not null and then avoid adding the button to the list if this is the case
Also, no View related operation in onCreate()
Update your fragment activity's onBackPressed as -
#Override
public void onBackPressed() {
FragmentManager fm = getFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
} else {
super.onBackPressed();
}
}
Check the savedInstanceState object to determine if this is the first time the Fragment is being initialized and only save your fields if it's the first time through:
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mission1 = view.findViewById(R.id.mission1);
mission2 = view.findViewById(R.id.mission2);
mission3a = view.findViewById(R.id.mission3a);
mission3b = view.findViewById(R.id.mission3b);
if (savedInstanceState == null) {
imageButtonList.add(mission1);
imageButtonList.add(mission2);
imageButtonList.add(mission3a);
imageButtonList.add(mission3b);
prepareButtons();
}
}
Try to Write Code in OneResume
#Override
public void onResume() {
mission1 = view.findViewById(R.id.mission1);
mission2 = view.findViewById(R.id.mission2);
mission3a = view.findViewById(R.id.mission3a);
mission3b = view.findViewById(R.id.mission3b);
imageButtonList.add(mission1);
imageButtonList.add(mission2);
imageButtonList.add(mission3a);
imageButtonList.add(mission3b);
prepareButtons();
}

Android Fragment created twice orientation change

My fragment is being created twice, even though the activity is only adding the fragment once to the content. This happens when I rotate the screen. Also, everytime the fragment's onCreateView is called, it has lost all of it's variable state.
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) { // Checking for recreation
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new AppPanelFragment())
.commit();
}
}
}
onCreate in the activity checks for the null savedInstanceState and only if null will add the fragment so I can't see why the fragment should be created twice? Putting a breakpoint in that if condition tells me that it only ever get's called once so the activity shouldn't be adding the fragment multiple times. However the onCreateView of the fragment still gets called each time orientation changes.
public class AppPanelFragment extends Fragment {
private TextView appNameText;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// This method called several times
View rootView = inflater.inflate(R.layout.fragment_app_panel, container, false);
// 2nd call on this method, appNameText is null, why?
appNameText = (TextView) rootView.findViewById(R.id.app_name);
appNameText.text = "My new App";
return view;
}
I managed to have the variable state persisted using setRetainInstance(true), but is this the real solution? I would expect the fragment to not be created on just an orientation change.
In android, when the phone's orientation is changed, the activity is destroyed and recreated. Now, i believe to fix your problem you can use the fragment manager to check to see if the fragment already exists in the back stack and if it doesn't then create it.
public void onCreated(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
mFragmentManager = getSupportFragmentManager();
AppPanelFragment fragment = (AppPanelFragment)mFragmentManager.findFragmentById(R.id.fagment_id);
if(fragment == null) {
//do your fragment creation
}
}
}
P.S. I haven't tested this but it should work once you provide the right fragment's id in the findFragmentById method.
The Fragment lifecycle is very similar to an Activity. By default, yes, they will be re-created during a configuration change just like an Activity does. That's expected behavior. Even with setRetainInstance(true) (which I would say to use with extreme caution if it contains a UI) your View will be destroyed and re-created, but in that case your Fragment instance will not be destroyed -- just the View.
I know it is a bit late to answer, but using The Code Pimp answer you can do the next thing:
If the fragment exists in the backstack we pop and remove it to add it back (an exception is thrown if it is added back without removing it, saying it already exists).
The fragment variable is a class member variable.
This method will be called in the onCreate method of the Activity:
if (savedInstanceState == null) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (fragmentManager.findFragmentById(getFragmentActivityLayoutContainerId()) == null) {
fragment = getNewFragmentInstance();
} else {
fragment = fragmentManager.findFragmentById(getFragmentActivityLayoutContainerId());
fragmentTransaction.remove(fragment);
fragmentManager.popBackStack();
fragmentTransaction.commit();
fragmentTransaction = fragmentManager.beginTransaction();
}
fragmentTransaction.add(getFragmentActivityLayoutContainerId(), fragment);
fragmentTransaction.commit();
}
The next code will be called in the fragment itself.
It is a small example for a code you could implement in your fragment to understand how it works. The dummyTV is a simple text view in the center of the fragment that receives text according to orientation (and for that we need a counter).
private TextView dummyTV;
private static int counter = 0;
#Override
protected int getFragmentLayoutId() {
return R.layout.fragment_alerts_view;
}
#Override
protected void saveReferences(View view) {
dummyTV = (TextView) view.findViewById(R.id.fragment_alerts_view_dummy_tv);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
if (savedInstanceState != null) {
dummyTV.setText(savedInstanceState.getString("dummy_string"));
} else {
dummyTV.setText("flip me!");
}
dummyTV.append(" | " + String.valueOf(counter));
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putString("dummy_string", counter++ % 2 == 0 ? "landscape" : "portrait");
}
As mentioned, on orientation change, the activity is destroyed and recreated. Also, Fragments(any) are recreated by the system.
To ensure your application restores to previous state, onSaveInstanceState() is called before the activity is destroyed.
So, you can store some information in the onSaveInstanceState() method of an activity and then restore your application to same state on orientation change.
NOTE: You need not create fragments on orientation change, as fragments are recreated.
Example from http://www.mynewsfeed.x10.mx/articles/index.php?id=15:
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ( savedInstanceState == null ){
//Initialize fragments
Fragment example_fragment = new ExampleFragment();
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.container, example_fragment, "Example");
} else{
//control comes to this block only on orientation change.
int postion = savedInstanceState.getInt("position"); //You can retrieve any piece of data you've saved through onSaveInstanceState()
//finding fragments on orientation change
Fragment example_fragment = manager.findFragmentByTag("Example");
//update the fragment so that the application retains its state
example_fragment.setPosition(position); //This method is just an example
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("position", position); //add any information you'd like to save and restore on orientation change.
}
}

Fragment View State on Screen Rotation

I have a few fragments that are loaded when a user clicks on an item in a list. Say a user has clicked on second item in the list, loading the second fragment. But, upon rotating, the screen, the first fragment in the list gets loaded. How can I make sure that the same fragment gets loaded whenever a user rotates the screen.
This is how I'm loading my fragments
private void selectItem(position) {
Fragment fragment = null;
switch (position) {
case 0:
fragment = new FirstFragment();
break;
case 1:
fragment = new SecondFragment();
break;
case 2:
fragment = new ThirdFragment();
break;
default:
break;
}
if (fragment != null) {
android.app.FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
}
else {
Log.e("NavigationActivity", "Error in creating fragment");
}
}
I'm calling selectItem(0) in onCreate of an activity.
The entire activity gets destroyed and recreated during a rotation. So if you are calling setItem(0) in Activity.onCreate, then you'll always get FirstFragment in the content frame.
Seems like the easy thing may be to just detect if you've already set a fragment in onCreate and not load the default. Either make use of onSaveInstanceState and/or mark the fragment as retained.
I don't have much experience with retained fragments or fragment management beyond initial load, so just using onSaveInstanceState to keep track of which one was loaded seems appropriate.
In your Activity, override onSaveInstanceState:
#Override
public void onSaveInstanceState(Bundle bundle)
{
bundle.putInt("which_fragment", _fragmentId);
super.onSaveInstanceState(bundle);
}
Where _fragmentId is just some numerical identifier of the particular fragment you are loading. It could even be it's layout id. Set this value in your selectItem method.
And then in onCreate:
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
_fragmentId = 0;
if (savedInstanceState != null)
{
_fragmentId = savedInstanceState.getInt("which_fragment", 0);
}
...
selectItem(_fragmentId);
}
First of all I wouldn't use positionOnTheList->Fragment dependency. I would depend on some id (final or from the resources).
Secondly I think you shouldn't create a new instance of each Fragment class when you select item from the list.
You should consider this approach:
Fragment f = fragmentManager.findFragmentById( String.valueOf(id) );
if( f == null )
f = new FragmentDependingOnId();
mCurrentlySelectedId = id;
fragmentManager.beginTransaction()
.replace( R.id.container, f , String.valueOf(id))
.commit();
Add the following method:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SELECTED_ID, mCurrentlySelectedId);
}
and in onCreate add:
if(savedInstanceState!=null){
mCurrentlySelectedId = savedInstanceState.getInt(SELECTED_ID);
selectItem(mCurrentlySelectedId);
}
When using fragment you usually use onCreateView to inflate your layout. Then you use onActivityCreated to do all the stuff you need to init listviews etc ...
In your case the problem you have is that you should use the saveInstanceState to keep track of if a fragment is loaded or not because the fragment is re-created on each rotation.
Let's look at some code
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.your_fragment_layout, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
//do nothing if the state already exists
} else {
//do something if state already exists
}
}
Note that if you need to save a given value, for example a boolean you can use
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(YOUR_BOOL_TAG, mYourBooleanVar);
}
and get it back in the onCreateView by using
mYourBooleanVar= savedInstanceState.getBoolean(YOUR_BOOL_TAG);
same applies to other types also.
EDIT
I didn't quite answered your question, so I put more details. The above code is in the fragment. However for your question, in the activity you need something like that.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
//here is the transaction to load your first fragment
}
}
and your first fragement won't reload each time.
The first time savedInstanceState will be null and you set your default fragment. Then each time you rotate savedInstanceState is not null and your default fragment is not reloaded but the one that is currently present.
Only this code is relevant for you, but I let the code above the EDIT for other people in case it can be useful to them.

Fragments and Orientation change

What is the correct way to handle an orientation change when using Fragments?
I have a landscape layout that contains 2 fragments (instantiated in code into FrameLayouts). When I switch to portrait mode (the layout of which contains only one FrameLayout that holds the left pane only), the right hand fragment is no longer required.
I am receiving an error:
E/AndroidRuntime(4519): Caused by: java.lang.IllegalArgumentException: No view found for id 0x7f060085 for fragment myFragment{418a2200 #2 id=0x7f060085}
which is assume is my activity trying to re-attach the fragment where it was before the orientation change but as the view that contains the fragment does not exist in portrait mode the error is thrown.
I have tried the following hide/remove/detach methods but still get the error. What is the correct way to tell a fragment it is not needed any more and do not try to display?
#Override
public void onCreate(Bundle b) {
super.onCreate(b);
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragholder2);
//rightPane is a framelayout that holds my fragment.
if (rightPane == null && f != null) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.hide(f); // This doesnt work
ft.remove(f); // neither does this
ft.detach(f); // or this
ft.commit;
}
}
I ran into the same problem and I think I figured out another solution. This solution may be better because you don't have to add the fragment to the back stack.
Remove the right hand side fragment from your activity in Activity.onSaveInstanceState() before calling super.onSaveInstanceState(). This works for me:
public MyActivity extends Activity
{
#Override
public onCreate(Bundle state)
{
super.onCreate(state);
// Set content view
setContentView(R.layout.my_activity);
// Store whether this is a dual pane layout
mDualPane = findViewById(R.id.rightFragHolder) != null;
// Other stuff, populate the left fragment, etc.
.
.
.
if (mDualPane)
{
mRightFragment = new RightFragment();
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.rightFragHolder, mRightFragment);
ft.commit()
}
}
#Override
public void onSaveInstanceState(Bundle state)
{
if (mDualPane)
{
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.remove(mRightFragment);
ft.commit()
}
super.onSaveInstanceState(state);
}
private boolean mDualPane;
private Fragment mRightFragment;
}
If you are retaining the fragment, try not retaining it.
setRetainInstance(false)
instead of
setRetainInstance(true)
I think I resolved it.
I added the fragment to the back stack and then before the activity closes popped it off again which effectively gets rid of it. Seems to work so far.
Usually you'll have two fragments (left/right), one main activity and one container activity for the right fragment (only when shown on phone devices). This is described in this blog entry: The Android 3.0 Fragments API
public class MyActivity extends FragmentActivity
implements MyListFragment.MyContextItemSelectedListener {
#Override
public void onCreate(final Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity);
}
// Callback from ListFragment
#Override
public void myContextItemSelected(final int action, final long id) {
if (action == R.id.men_show) {
processShow(id);
}
}
private void processShow(final long id) {
if (Tools.isXlargeLand(getApplicationContext())) {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.right);
if (fragment == null ||
fragment instanceof MyEditFragment ||
(fragment instanceof MyShowFragment && ((MyShowFragment) fragment).getCurrentId() != id)) {
fragment = new MyShowFragment(id);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.right, fragment);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.commit();
}
} else {
Intent intent = new Intent();
intent.setClass(this, MyShowActivity.class);
intent.putExtra("ID", id);
startActivityForResult(intent, MyConstants.DLG_TABLE1SHOW);
}
}
private static boolean isXlargeLand(final Context context) {
Configuration configuration = context.getResources().getConfiguration();
return (((configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE) &&
configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
}
}
If you have a two pane activity with a left and right pane and one of the panes (usually the right pane) is suppose to not show when the device switches to portrait mode, let Android do its thing and recreate the right pane. But during the onCreateView of the right pane, the first thing you should do is check if one of the layout elements used by the pane is even available. If it is not, remove the fragment using the FragmentManager and return immediately:
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState)
{
View myView = getActivity().findViewById(R.id.myView);
if (myView == null)
{
FragmentTransaction fragTransaction = getFragmentManager().beginTransaction();
fragTransaction.remove(this);
fragTransaction.commit();
return null;
}
}
Android does recreates both fragments during screen rotation. But if you add check below into onCreateView() it will prevent you from issues:
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
if (container == null) {
// Currently in a layout without a container, so no
// reason to create our view.
return null;
}
// inflate view and do other stuff
}
I took this from Android Developers blog.
You do not need this activity anymore because the fragment will be shown in-line. So you can finish the activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
// we are in landscape so you do not need this activity anymore
finish();
return;
}
if (savedInstanceState == null) {
// plugin right pane fragment
YourFragment frgm = new YourFragment();
getSupportFragmentManager().beginTransaction()
.add(..., frgm).commit();
}
}

Categories

Resources