Wednesday 30 December 2015

Understanding Android Activity and Fragment Lifecycle

Android activity lifecycle is pretty simple to understand if you understand the different states an activity goes through when it starts and when it tears down.

An activity has six states:
  1. Created
  2. Started
  3. Resumed
  4. Paused
  5. Stopped
  6. Destroyed


And there are seven callback methods used by the activity to move between different states:

  • onCreate() : This is the first callback android activity receives, when you start an activity. Here, you build and wire up your UI, initialize your global variables etc. Once that's done, your activity has been created.
  • onStart() :  This is called when the activity is becoming visible to the user.
  • onResume() : This is called when the activity will start interacting with the user. Here, at this point, the activity is in the foreground and the user can interact with it. The resumed state is sometimes referred to as the "running" state.
  • onPause() : This callback starts when the current activity is partially obscured by another activity or a dialog. This is typically used to commit unsaved changes to persistent data, stop animations, remove runnables and other things that may be consuming CPU, etc. The paused activity does not receive user input and cannot execute any code.
  • onStop() : This is called when the activity is no longer visible to the user and goes in the background, because another activity has been resumed and is covering this one. This may happen either because a new activity is being started, an existing one is being brought in front of this one, or this one is being destroyed. While stopped, the activity instance and all its state information such as member variables is retained, but it cannot execute any code.
  • onDestroy() : This is the final callback android activity receives, before it is destroyed. It can happen either because activity is finishing(someone called finish() on it) or because the system is temporarily destroying this instance of the activity to save space. Most apps don't need to implement this method because local class references are destroyed with the activity and your activity should perform most cleanup during onPause() and onStop(). However, if your activity includes background threads that you created during onCreate() or other long-running resources that could potentially leak memory if not properly closed, you should kill them during onDestroy(). 
         So, that's sum up the basic activity lifecycle.

   Callbacks that are executed in following scenarios:
    1)When Activity starts and becomes visible:
        onCreate()->onStart()->onResume()
    
    2)When phone/device rotates:
        onPause()->onStop()->onDestroy()->onCreate()->onStart()->onResume()
     
    3)When you press home button (activity goes in background, becomes invisible):
        onPause()->onStop()

    4)When you choose your app from recent apps(your activity comes again to foreground):
        onRestart()->onStart()->onResume()

    5)When you press the back button on your activity:
       onBackPressed()->finish()->onPause()->onStop()->onDestroy()

    6)When you add a fragment to your activity using add():
        Fragment fragmentA = FragmentA.newInstance("hello", "frag A");
    FragmentManager fragmentManager = getSupportFragmentManager();
    fragmentManager.beginTransaction()
        .add(R.id.container, fragmentA)
        .addToBackStack(null)
        .commit();
    
    Callbacks:
      FragmentA: newInstance()
      FragmentA: FragmentA()
      FragmentA: onAttach()
      FragmentA: onCreate()
      FragmentA: onCreateView()
      FragmentA: onViewCreated()
      FragmentA: onActivityCreated()
      FragmentA: onStart()
      FragmentA: onResume()

   What addToBackStack(null/String) means:--

  • It means adding this transaction(operation : add fragment A to the container) to the Back Stack.This means that the transaction will be remembered after it is committed(as added in back stack), and will reverse its operation when later popped off the stack.
  • If you want to navigate to the previous fragment add it to backstack. 

    7)When you press home button on a fragment that is added to an activity :
           MainActivity: onPause()
           FragmentA: onPause()
           MainActivity: onStop()
           FragmentA: onStop()


    8)When you choose your app from recent apps(your fragment comes again to foreground):
           MainActivity: onRestart()
           MainActivity: onStart()
           FragmentA: onStart()
           MainActivity: onResume()
           FragmentA: onResume()

    9)When you press back button on your fragment:
         MainActivity: onBackPressed()
         FragmentA: onPause()
         FragmentA: onStop()
         FragmentA: onDestroy()
         FragmentA: onDetach()

   Back button pressed on FragmentA:--


  • And so when you press back button on the FragmentA, then MainActivity onBackPressed() will call fm.popBackStack() and the top back stack entry(transaction) will be popped from the stack. 
  • And when the transaction will be popped out from the stack, its operation will be reversed(operation : remove fragment A from the container). And so, the fragment will be removed and destroyed.


   10)When you remove a fragment from an activity using remove():
         FragmentManager fm = getSupportFragmentManager();
     Fragment fragA = fm.findFragmentById(R.id.container);

     if (fragA == null) {
        return;
     }
     // Using popBackStack function to remove fragment A from main activity     
     // if (fm.getBackStackEntryCount() > 0) {     
     //      fm.popBackStack();     
     // }     
     if (fragA instanceof FragmentA) {
         fm.beginTransaction().remove(fragA).commit();
     }

    Callbacks:--
     FragmentA: onPause()
     FragmentA: onStop()

  
  11)Now, suppose we have another fragment B to be added in that same container where fragment A has been added in the MainActivity's container.


 case R.id.btn_add_fragment_A:

   FragmentA fragmentA = FragmentA.newInstance("hello", "frag A");

   Log.d(TAG, "getting fragmentManager inside btn_add_fragment_A click");
   fragmentManager = getSupportFragmentManager();
   fragmentManager.beginTransaction()
           .replace(R.id.container, fragmentA)
           .addToBackStack(null)
           .commit();

   break;

 Callbacks:-
 FragmentA: newInstance()
 FragmentA: FragmentA()
 MainActivity: getting fragmentManager inside btn_add_fragment_A click
 FragmentA: onAttach()
 FragmentA: onCreate()
 FragmentA: onCreateView()
 FragmentA: onViewCreated()
 FragmentA: onActivityCreated()
 FragmentA: onStart()
 FragmentA: onResume()

 case R.id.btn_add_fragment_B:
   fragmentB = FragmentB.newInstance("hello", "frag B");
   Log.d(TAG, "getting fragmentManager inside btn_add_fragment_B click");
   fragmentManager = getSupportFragmentManager();
   fragmentManager.beginTransaction()
       .replace(R.id.container, fragmentB)
       .addToBackStack(null)
       .commit();
   break;

 Callbacks:-
 FragmentB: newInstance()
 FragmentB: FragmentB()
 MainActivity: getting fragmentManager inside btn_add_fragment_B click
 FragmentA: onPause()
 FragmentA: onStop()
 FragmentB: onAttach()
 FragmentB: onCreate()
 FragmentB: onCreateView()
 FragmentB: onViewCreated()
 FragmentB: onActivityCreated()
 FragmentB: onStart()
 FragmentB: onResume()

 Note : When fragment B is added, FragmentAs onPause() and onStop() is  called but not onDetach() & onDestroy() because fragment A’s view is just removed from container and fragment A is not getting destroyed like it does on back press. Fragment B’s view is added to the container now.

 case R.id.btn_remove_fragment_B:
   Log.d(TAG, "getting fragmentManager inside btn_remove_fragment_B click");
   fragmentManager = getSupportFragmentManager();
   fragmentManager.popBackStack();
   break;

   Added fragment A =>  .replace(R.id.container, fragmentA) => addToBackStack(null)
   Added fragment B =>  .replace(R.id.container, fragmentB) => addToBackStack(null)
   Remove fragment B => fragmentManager.popBackStack();

 MainActivity: getting fragmentManager inside btn_remove_fragment_B click
 FragmentB: onPause()
 FragmentB: onStop()
 FragmentB: onDestroy()
 FragmentB: onDetach()
 FragmentA: onCreateView()
 FragmentA: onViewCreated()
 FragmentA: onActivityCreated()
 FragmentA: onStart()
 FragmentA: onResume()

 Explanation: Why while removing and destroying fragment B using popBackStack(), fragment A view was created again and resumed?
Ans: When you added fragment B, you used replace() and addToBackStack(), so all the  fragments were removed from the container, and fragment B was added to the container. And, also this transaction was recorded in the Back stack.  So, when fm.popBackStack() is called after fragment B has been added, first the transaction is popped out from the back stack and so the operations reverted itself, therefore fragment b is removed from the container and destroyed and all other fragments are added, for our case fragment A's view is added to the container. Also noted that fragment A's onAttach & onCreate() is not called because it has already been created & attached to the MainActivity earlier.



  References:
  http://developer.android.com/training/basics/activity-lifecycle/starting.html
  http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle
  http://developer.android.com/training/basics/activity-lifecycle/stopping.html