What happens to fragments after the system kills an app? - android

"If an activity is paused or stopped, the system can drop the activity from memory by either asking it to finish, or simply killing its process.". When the user goes back to the activity, it restores it's state with a bundle.
My question is:
is it important to do this in oncreate:
if(savedinstance != null)
{
fragement = fm.findFragmentByTag("tag");
}
else
{
fragment = new Fragment();
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.webViewFrame,fragment,"tag");
ft.commit()
}
instead of just this:
fragment = new Fragment();
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.webViewFrame,fragment,"tag");
ft.commit()

If you are correctly saving the state of your activity and fragment in onSaveInstanceState(), and you want your activity to be recreated in the state it had before it was killed, you should use the first block of code you posted.
The FragmentManager saves its state, and when recreated, restores the fragments it was holding when the activity was killed. It steps them through the build-up lifecycle events using the fragment's saved state: create, createView, start, resume.
I'm pretty sure if you try running the second block of code, you will find after restart that you have two instances of your fragment in the FragmentManager--the one added when the activity was first create, and the one added after restart.
For this all to work correctly, you must carefully save the state of both your activity and fragment(s) in the onSaveInstanceState() method of each, and then in onCreate() test savedInstanceState and when not null, use the bundle to restore the state of your activity/fragment.
This is the guide for saving/restoring activity state. More info here.

Related

Is this the correct way to programmatically invoke a Fragment?

My code:
public class MainActivity extends AppCompatActivity {
private FragmentA fragmentA;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
fragmentA = FragmentA.newInstance();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_a_container, fragmentA, "FRAGMENT_A");
fragmentTransaction.commit();
}
else {
fragmentA = (FragmentA) getSupportFragmentManager().findFragmentByTag("FRAGMENT_A");
}
}
}
I don't really know what I am doing but this is currently what I do. I define a container for the Fragment and then I use a FragmentTransaction to replace it with a Fragment. The part I am confused about though is the else statement.
Should I be structuring this differently?
I thought configuration changes wiped out Activities and Fragments so why check for the Fragment in some support manager? Does this mean Fragments don't actually get destroyed? At the same time, they DO seem to get destroyed because they appear to reset unless I use onSaveInstanceState or the getArguments() approach.
Edit: What's wrong with doing this:
public class MainActivity extends AppCompatActivity {
private FragmentA fragmentA;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentA = FragmentA.newInstance();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_a_container, fragmentA, "FRAGMENT_A");
fragmentTransaction.commit();
}
}
They do get destroyed and recreated for you on configuration changes by the, in this case, SupportFragmentManager.
To answer your questions:
Should I be structuring this differently?
No, that's exactly how you should create fragments if there is no saved state and retrieve them when there is. See also my answer here;
a) so why check for the Fragment in some support manager?
Because the manager handles the lifecyle of the fragment for you when there is a configuration change.
b) Does this mean Fragments don't actually get destroyed?
No, it does get destroyed. See this diagram for a reference.
Edit to answer some of your questions from the comments:
But any member variables inside that Fragment are completely lost on configuration change unless I save them in that Fragment's onSaveInstanceState, right?
That is correct. Because your fragment is being destroyed, everything not being saved on onSaveInstanceState gets lost.
So then what exactly am I restoring?
You are not restoring anything. You are only retrieving the reference to the fragment that was previously created. You restore your variables on the onRestoreInstanceState() method of your fragment.
What's wrong with doing this (the code from the edit in the question)?
If you do that, you are adding a new fragment instance to the R.id.fragment_a_container container. So the old fragment will get lost together with the state of it you saved on onSaveInstanceState(). It will be a new fragment, with new information in it and the event onRestoreInstanceState() won't be called for it.

Resuming a dead app from background is making it behave weirdly

I am building an application which has the following structure.
MainActivity -> Fragment1
-> Fragment2 -> Fragment3
Here Fragment1 is hosted on MainActivity in FrameLayout. when user click any option on Fragment1, I am switching to Fragment2 which hosts Fragment3 on ViewPager.
Problem is, if user moves to android home without closing the app, and in a duration of time the app is killed by Android, and user tries to resume the app from recent app list, Fragment3 is displaying the blank screen.
here is the method which I am using for Fragment transaction
public void requestChangeFragment(Fragment newFragment,String tag,Fragment oldFragment, int anim_in, int anim_out){
if(isStopped()){
return;
}
mFragmentManager = getSupportFragmentManager();
android.support.v4.app.FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(anim_in,anim_out,anim_in,anim_out);
fragmentTransaction.add(R.id.main_frame_layout, newFragment, tag);
Log.i("FragMngr","Animations set in "+anim_in+" out "+anim_out);
fragmentTransaction.addToBackStack(tag);
int count = mFragmentManager.getBackStackEntryCount();
String log="";
fragmentTransaction.commit();
for(int i=0;i<count;i++){
android.support.v4.app.FragmentManager.BackStackEntry backStackEntry = mFragmentManager.getBackStackEntryAt(i);
log += "count "+i+" name "+backStackEntry.getName()+"\n";
}
Log.d("FragMngr","BackStack trace after adding is\n "+log);
if(oldFragment!=null) {
if(oldFragment.isResumed()) {
oldFragment.onPause();
}
}
topFragment = newFragment;
}
requestChangeFragment() is called from running fragments with the help of interface implemented in MainActivity.
After doing some research I identified the problem to be the saved instances which stores the current state of the fragment whenever android garbage collector kills the app when app is not in use. So when I tried to resume the dead app, it moves the fragment that was selected before pausing the app. But the fragment do not have any data which it must have received from the it Main class.
Solution : Clear the saved instances in your MainActivity in onCreate method. Or if you are storing any values in your saved instance state, then perform the operation related to the saved Instance State, clear the value and then call super.onCreate(savedInstanceState)

Android Fragment transaction in background

I am developing an application with fragments. It has a JavaScript Interface, which is called in the Main Activity and has fragment replacing logic. When application is in foreground everything works OK, but when the application is in background, fragment transaction replace doesn't work. When I return to my application, I still see the old fragment and don't see the new one.
#JavascriptInterface
public void beginCall(String toast) {
FragmentTransaction fTrans;
taskFragment = TaskFragment.newInstance(toast,"");
fTrans = getSupportFragmentManager().beginTransaction();
fTrans.replace(R.id.frgmCont, taskFragment);
fTrans.commit();
}
What is wrong? Why the fragment transaction doesn't work in background?
After some time I've found the answer: it's impossible to perform a fragment transaction after onStop, it will result in java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState. I wasn't getting that Exception as JavascriptInterface was performed in a separate thread. When I forced my code to run in Main thread, I got that error. So I need to implement a different logic, also using some of Activity Life-cycle methods, or to switch to multiple activities logic. Hope my answer will help anyone.
Some use cases or architectures might require to trigger fragment transactions while app is in background.
We created following extension function:
fun FragmentTransaction.commitWhenStarted(lifecycle: Lifecycle) {
lifecycle.addObserver(object : LifecycleObserver {
#OnLifecycleEvent(value = Lifecycle.Event.ON_START)
fun onStart() {
lifecycle.removeObserver(this)
commit()
}
})
}
Use it just like any other version of commit, commitNow, commitAllowingStateLoss.
If the activity state is already at least started the observer will be called directly and the fragment transaction is executed. The lifecycle can be taken from activity or from fragment if the transaction is executed on a childFragmentManager
transaction.commitWhenStarted(lifecycle)
FragRecordSongList FragRecordSongList = new FragRecordSongList();
FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
ft.addToBackStack(FragRecordSongList.class.getName());
ft.replace(R.id.fragContainer, FragRecordSongList, FragRecordSongList.class.getName());
ft.commit();
Try this may be help you
#lilienberg commented a great solution for fragment transactions. If you are using the navigation component you can use something like this:
fun NavController.resumedNavigation(lifecycle: Lifecycle, destination: Int) {
if(lifecycle.currentState.isAtleast(Lifecycle.State.RESUMED)){
//App is resumed, continue navigation.
navigate(destination)
} else {
//When app is resumed, remove observer and navigate to destination/
lifecycle.addObserver(object: LifecycleObserver {
#OnLifecycleEvent(value = Lifecycle.Event.ON_RESUME)
fun onResume() {
lifecycle.removeObserver(this)
navigate(destination)
}
})
}
}
You can call this function from your Activity or Fragment like this:
findNavController(R.id.my_nav_host_fragment).resumedNavigation(
lifecycle, R.id.my_navigation_action)

Fragment getting detached from Activity without obvious reason

My problem: When i close my app via long-pressing home button (Samsung S4) in the app manager, everything works fine on the next startup.
But, when i close the app in onBackPressed when its time to close it, i get a crash on the next start of the app, because "getActivty" returns null.
I checked multiple times which lifecycle methods get called during these two scenarios, and wasn't able to find any difference.
Also, i use the piece of code producing the crash in another fragment, but there it works fine.
The question is: Why returns getActivity null, when the Activity is definitely alive? Better said, why is my fragment not attached to the Activity anymore?
Relevant code (i hope)
init of main fragment:
//Declared globally in Activity
FirstFragment fr;
//In activities onCreate
fr = new FirstFragment();
getFragmentManager()
.beginTransaction()
.replace(R.id.main_fragment, fr, "first")
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.addToBackStack("")
.commit();
Switching to the second fragment is done with this method (Where the parameter "Object fragment" is "new SecondFragment")
public void changeFragment(Object fragment, String name, boolean stayMain) {
if (!stayMain) {
findViewById(R.id.title_bar_left_button).setBackgroundResource(R.drawable.ic_back);
findViewById(R.id.title_bar_left_button).setOnClickListener(backToMainListener);
}
((TextView) findViewById(R.id.navTitle)).setText(name);
findViewById(R.id.title_bar_right_button).setVisibility(View.GONE);
getFragmentManager()
.beginTransaction()
.replace(R.id.main_fragment, (Fragment)fragment, name)
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.addToBackStack("")
.commit();
}
Code causing the crash: (since getActivity returns null in described scenario
Context c = this.getActivity();
AlertDialog.Builder builder = new AlertDialog.Builder(c);
Any help is appreciated

Understanding FragmentManager and FragmentTransaction lifecycle with regards to Activity

I am trying to get a better understanding of FragmentManager and FragmentTransactions to properly develop my application. It is specifically in regards to their lifecycle, and the long-term effect of committing a FragmentTransaction(add). The reason I have a confusion over it is when I ran a sample Activity, listed at the end of the post.
I purposely created a static FragmentManager variable called fragMan and initially set it to null. It is then checked against in onCreate() if it is null and when null value is seen, the fragMan variable is set to the getFragmentManager() return value. During a configuration change, the Log.d showed that fragma was not null, but the Fragment "CameraFragment" previously added was not found in fragman and the fragman.isDestroyed() returned true. This to me meant that the Activity.getFragmentManager() returns a new instance of a FragmentManager, and that the old FragmentManager instance in fragMan had its data wiped(?)
Here is where the confusion comes in.
1) How is "CameraFragment" still associated in the Activity on a configuration change and is found in
the new instance of FragmentManager?
2) When I hit the back button on my phone to exit the Activity, I then relaunched the sample
Activity using the Apps menu. The CameraFragment was not visible anymore, and the
onCreate() check revealed that fragMan was still not null. I thought that hitting the back button
called the default finish() command, clearing the Activity from memory and that restarting it
would produce the same result as the initial launch of the sample Activity?
Thank you for any and all help you can provide!
public class MainActivity extends Activity
{
static FragmentManager fragMan = null;
FragmentTransaction fragTransaction;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (fragMan != null)
{
Log.d("log", Boolean.toString(fragMan.isDestroyed()));
if(fragMan.findFragmentByTag("Camera Preview") == null)
{
Log.d("log", "Camera Preview not found.");
}
}
else
{
fragMan = getFragmentManager();
fragTransaction = fragMan.beginTransaction();
Fragment cameraFragment = new CameraFragment();
ViewGroup root_view = (ViewGroup) findViewById(android.R.id.content);
fragTransaction.add(root_view.getId(), cameraFragment, "Camera Preview");
fragTransaction.commit();
}
Static variables in Java are kept across Activity creation/destruction - they are associated with the class itself but not a particular instance of the class.
See the official documentation here:
http://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html
Your application doesn't end when the user returns to the home screen, it just gets put in a background state. If you force stopped the application and restarted it, then the static FragmentManager will be null.
With regards to CameraFragment, unless you've set setRetainInstance(true), it will get destroyed on an orientation change.
==== EDIT
Here's a more detailed flow of what's happening...
You open the application up for the first time
Activity, say instance A1, gets created and its corresponding FragmentManager instance, FM1, also gets created
You store FM1 as a static variable
You go back to home
Activity A1 and FM1 gets destroyed because of the normal Activity lifecycle, although FM1's reference is still held onto by the static variable. At this point, FM1 loses all the fragments it contains and isDestroyed() will return true.
Starting the app again
New Activity instance A2 gets created along with its new FragmentManager instance FM2

Categories

Resources