Android Fragments - Should we really save state? - android

I have implemented a simple activity with this code:
public class MainActivity extends AppCompatActivity implements Fragment_1.Operations{
Fragment_1 fragment_1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragmentactivity);
fragment_1=(Fragment_1)getSupportFragmentManager().findFragmentByTag("fragment_1");
}
//called on buttonclick, fired from a button existing in R.layout.fragmentactivity
public void createFragment(View view){
if (getSupportFragmentManager().findFragmentByTag("fragment_1")==null){
fragment_1=new Fragment_1();
FragmentTransaction transaction=getSupportFragmentManager().beginTransaction();
transaction.add(R.id.fragmentactt,fragment_1,"fragment_1");
transaction.commit();
getSupportFragmentManager().executePendingTransactions();
}
else{
fragment_1=(Fragment_1)getSupportFragmentManager().findFragmentByTag("fragment_1");
}
//Simply adding item to the listview contained in fragment_1.
fragment_1.add("Project #1");
fragment_1.add("Project #2");
fragment_1.add("Project #3");
}
//callback of interface "Operations"
#Override
public void buttonClicked() {
FragmentTransaction transaction=getSupportFragmentManager().beginTransaction();
if (fragment_1.isAdded()){
transaction.remove(fragment_1);
//transaction.addToBackStack(null);
transaction.commit();
getSupportFragmentManager().executePendingTransactions();
}
}
}
Well, the doubt came from the fact that no "onSaveInstanceState" needed to be implemented, everything got "saved" without any problems.
So, why should i use putfragment and getFragment? Do these methods need to be called in order to avoid that, when OS kills app process, they would be lost? This is the only reason i can imagine to force onSaveInstanceState and onRestoreInstanceState methods.
Any help is appreciated.

Activity and fragment lifecycles are linked so when any callback method such as onResume is called for the activity, it is called for the fragment too.
putFragment and getFragment help the activity to manage its fragment child's lifecycle. The activity also has to save instance state.
In order to be activity independant, a fragment can manage his own instance state.

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.

Similar onBackPressed() for Fragments?

Hello my Android application is using fragments. I am wondering if there is a way I can use the idea of the onBackPressed() method in my app with fragments. I have previous and next buttons, but as of now I am just creating new fragments and replacing, and none of the data gets saved. Is there a way to save my data/go back once I have gone forward?
The concept of Fragment is different of Activity.
One Activity could have a many Fragments, read that:
A Fragment represents a behavior or a portion of user interface in an
Activity. You can combine multiple fragments in a single activity to
build a multi-pane UI and reuse a fragment in multiple activities. You
can think of a fragment as a modular section of an activity, which has
its own lifecycle, receives its own input events, and which you can
add or remove while the activity is running (sort of like a "sub
activity" that you can reuse in different activities).
See more here: http://developer.android.com/guide/components/fragments.html
SOLUCTION
So if you wanna handle the onBackPressed behavior in you Fragment you could do that:
package com.example.stackoverflowsandbox;
import android.app.Activity;
import android.app.Fragment;
public class MainActivity extends Activity {
public class MyFragment extends Fragment {
public void onBackPressed() {
// your code here...
}
}
private MyFragment myFragment;
#Override
public void onBackPressed() {
// super.onBackPressed(); // comment to not back
this.myFragment.onBackPressed(); // the onBackPressed method of Fragment is a custom method
}
}
Sorry, do not know if I understand your question, but if the idea and have control of direct backbutton in its fragment, and from it to perform some task of data persistence, you can add your fragment to control stack FragmentManager, the as follows.
FragmentManager fm = getSupportFragmentManager();
MyFragment mMyFragment = new MyFragment();
fm.beginTransaction()
.add(mMyFragment, "mMyFragment")
.addToBackStack( null )
.commit();
In the fragment you need to implement the interface OnBackStackChangedListener
In Fragment:
public class MyFragment extends Fragment implements OnBackStackChangedListener {
#Override
public void onBackStackChanged() {
//your code here
}
}
If you just keep the values
public class MyFragment extends Fragment {
String valeu;
#Override
public void onCreate( final Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
if ( savedInstanceState != null ) {
this.valeu = savedInstanceState.getString( "key" );
}
}
#Override
public void onSaveInstanceState( final Bundle outState ) {
super.onSaveInstanceState( outState );
outState.putString( "key", "Your content" );
}
}
while moving to front fragment, save the state of previous fragment using onSaveInstanceState()
while moving back restore the state in onCreate() or onCreateView() in the previous fragment

how to wait until a fragment is removed

I have an activity with dynamic fragments in it. I need to run some code after a fragment is removed but remove(myFragment).commit() is executed asynchronously and i cant know when exactly the fragment is removed.Here is my code:
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.remove(myFragment).commit();
//wait until the fragment is removed and then execute rest of my code
From the documentation:
public abstract int commit ()
Schedules a commit of this transaction. The commit does not happen
immediately; it will be scheduled as work on the main thread to be
done the next time that thread is ready.
What if you use the fragment's onDetach method to call the activity and tell it its done?
class MyFrag extends Fragment {
private Activity act;
#Override
public void onAttach(Activity activity) {
act = activity;
}
#Override
public void onDetatch() {
act.fragGone();
}
}
And in the activity:
public void fragGone() {
//do something, update a boolean, refresh view, etc.
}
You could try using the onDetached() callback of the fragment. This will be called whenever it is removed from its Activity.
Use onAttach() to check when the fragment is attached to the Activity and use onDettach() to check when the fragment is dettached to the activity.
Using the onDettach() you can also check to update or not views, data, etc in this way:
#Override
public void onDetach() {
super.onDetach();
synchronized (mLock) {
mReady = false;
}
}

Fragment: which callback invoked when press back button & customize it

I have a fragment:
public class MyFragment extends Fragment{
...
#Override
public View onCreateView(...){...}
...
}
I instantiate it:
MyFragment myFragment = new MyFragment();
I use the above fragment to replace the current fragment:
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
// replace fragment
fragmentTransaction.replace(R.id.fragment_placeholder, myFragment, "myTag");
// NOTE: I did not add to back stack
Now, myFragment is showing on the screen. NOTE: I did not add myFragment to back stack.
My two questions:
1. If now, I press mobile phone back button, which fragment's life cycle callback will be invoked??
2. How can I customize the back button click listener in MyFragment class? (please do not suggest me to do myFragment.getView().setOnclickListener, but do it in MyFragment class)
Question 1: See http://developer.android.com/reference/android/app/Fragment.html#Lifecycle:
"As a fragment is no longer being used, it goes through a reverse series of callbacks:
onPause() - fragment is no longer interacting with the user either because its activity is being paused or a fragment operation is
modifying it in the activity.
onStop() - fragment is no longer visible to the user either because its activity is being stopped or a fragment operation is modifying it
in the activity.
onDestroyView() - allows the fragment to clean up resources associated with its View.
onDestroy() - called to do final cleanup of the fragment's state.
onDetach() - called immediately prior to the fragment no longer being associated with its activity."
Question 2: If you must know that it was the back button specifically that is triggering the callbacks, You can capture the back button press in your Fragment's Activity and use your own method to handle it:
public class MyActivity extends Activity
{
//...
//Defined in Activity class, so override
#Override
public void onBackPressed()
{
super.onBackPressed();
myFragment.onBackPressed();
}
}
public class MyFragment extends Fragment
{
//Your created method
public void onBackPressed()
{
//Handle any cleanup you don't always want done in the normal lifecycle
}
}
androidx.activity 1.0.0-alpha01 is released and introduces ComponentActivity, a new base class of the existing FragmentActivity and AppCompatActivity.
You can now register an OnBackPressedCallback via addOnBackPressedCallback to receive onBackPressed() callbacks without needing to override the method in your activity.

How to determine fragment restored from backstack

Been searching for this issue for a while to no avail now:
How to determine fragment is being restored from backstack?
I'm using the compatibility library and a ListFragment inside a FragmentActivity. When an item inside ListFragment is selected, a new Fragment is started to replace the ListFragment.
I noticed that when the FragmentActivity gets paused, the Fragment's onSaveInstanceState is called. But when the Fragment is put into the back stack via FragmentTransaction, onSaveInstanceState doesn't get called, then the lifecycle methods onCreateView and onActivityCreated gets called with null savedInstanceState Bundle.
I'm asking this because I want to load some data when the Fragment is created or restored, but not so when user comes back via. backstack.
I've looked at How to check if Fragment was restored from a backstack?
but want to add more details in hopes this would incite an answer.
Edit:
just noticed http://developer.android.com/reference/android/app/Fragment.html#onSaveInstanceState(android.os.Bundle)
says
Note however: this method may be called at any time before onDestroy(). There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.
So onSaveInstanceState is definitely out of the question...
I think that most simple way is do this for example in onViewCreated() method:
if (savedInstanceState == null && !mAlreadyLoaded) {
mAlreadyLoaded = true;
// Do this code only first time, not after rotation or reuse fragment from backstack
}
Because when android put fragment on backstack, it only destroy its view, but don't kill instance itself, so mAlreadyLoaded will be still true when fragment will be restored from backstack.
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
public void onBackStackChanged() {
Log.i(TAG, "back stack changed ");
int backCount = getSupportFragmentManager().getBackStackEntryCount();
if (backCount == 0){
// block where back has been pressed. since backstack is zero.
}
}
});
use this addOnBackStackChangedListener.
When a fragment goes to back-stack onDestroyView() called. Not onDestroy().
And when a fragment pops from back-stack onCreateView() called. Not onCreate().
So add a boolean mIsRestoredFromBackstack to fragment and follow as below:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mIsRestoredFromBackstack = false;
}
#Override
public void onResume()
{
super.onResume();
if(mIsRestoredFromBackstack)
{
// The fragment restored from backstack, do some work here!
}
}
#Override
public void onDestroyView()
{
super.onDestroyView();
mIsRestoredFromBackstack = true;
}
MAJOR EDIT: Oct 15 2013
The previous explanation (kept below for reference) fails when the application is put to the background and brought back to the foreground.
Instead, it is better to compare the current size of the backstack with the one when the fragment was created & put into the backstack.
Take a good look at Figure 2 in http://developer.android.com/guide/components/fragments.html#Creating
What this figure tells you is that when a fragment is restored from the backstack, its onCreate() is not called, while its onCreateView() is.
So, you may want to do something like this:
public class MyFragment extends Fragment {
int mBackStackSize = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBackStackSize = getFragmentManager().getBackStackEntryCount();
}
public boolean isRestoredFromBackstack() {
return mBackStackSize > getFragmentManager().getBackStackEntryCount();
}
}
If you added fragment to backstack, and after some manipulation you hide it using fragmentTransaction.hide(fragment) and then restore it from backstack like fragmentTransaction.show(fragmentManager.findFragmentByTag(fragment.getName())); you can override onHiddenChanged(boolean hidden)
#Override
public void onHiddenChanged(boolean hidden) {
// TODO Auto-generated method stub
super.onHiddenChanged(hidden);
if (!hidden) {
//fragment became visible
//your code here
}
}
In some cases you can use isVisible method to understand is it first showing of a fragment or is it restored from the backstack.

Categories

Resources