Showing posts with label lifecycle. Show all posts
Showing posts with label lifecycle. Show all posts

Tuesday, May 6, 2014

Fragment lifecycle with respect to its Activity

What is Fragment?

Fragment represents a behavior or a portion of user interface in an ActivityWe can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. 

The Fragment class has code that looks a lot like an Activity. It contains callback methods similar to an activity, such as onCreate()onStart()onPause(), andonStop(). In fact, if you're converting an existing Android application to use fragments, you might simply move code from your activity's callback methods into the respective callback methods of your fragment.

Fragment Lifecycle

Origin : http://developer.android.com/guide/components/fragments.html#Lifecycle

Fragment lifecycle with respect to its Activity

Fragment's lifecycle is tied to its owning activity and the normal flow of fragment in a activity.

1.    MainActivity onCreate
     Fragment onAttach
     Fragment onCreate
     Fragment onCreateView
     Fragment onViewCreated
     Fragment onActivityCreated
2.    MainActivity onStart
          Fragment onStart
3.    MainActivity onResume
          Fragment onResume
          Fragment onStop
4.    MainActivity onStop
          Fragment onDestroyView
          Fragment onDestroy
          Fragment onDetach
5.    MainActivity onDestroy

What happens? If we navigates to another activity from fragment and come back to main activity?

Here we have two scenarios, where fragment life cycle works.
1.    Activity is not destroyed when user leaves it.
2.    Activity got destroyed when user leaves it.
Activity Normal flow
Activity got destroyed
MainActivity onCreate
          Fragment onAttach
          Fragment onCreate
          Fragment onCreateView
          Fragment onViewCreated
          Fragment onActivityCreated
MainActivity onStart
          Fragment onStart
MainActivity onResume
          Fragment onResume
MainActivity onCreate
          Fragment onAttach
          Fragment onCreate
          Fragment onCreateView
          Fragment onViewCreated
          Fragment onActivityCreated
MainActivity onStart
          Fragment onStart
MainActivity onResume
          Fragment onResume
          Fragment onStop
MainActivity onStop
          Fragment onStop
MainActivity onStop
          Fragment onDestroyView
          Fragment onDestroy
          Fragment onDetach
MainActivity onDestroy
MainActivity onRestart
MainActivity onStart
          Fragment onStart
MainActivity onResume
          Fragment onResume
          Fragment onAttach
          Fragment onCreate
MainActivity onCreate
          Fragment onCreateView
          Fragment onViewCreated
          Fragment onActivityCreated
MainActivity onStart
          Fragment onStart
MainActivity onResume
          Fragment onResume

In the above differentiation, the 2nd and 3rd row tells the flow of life cycle of fragment with respect to its activity.
By this flow change, we should be aware in writing our business logics.

Why, we should be aware?

Example: Assume, we have a custom action bar header, we are modifying the header depend upon the fragment selection in the fragment.
The custom header is called in the MainActivity Oncreate method as follows
         ActionBar bar = getActionBar();
        if (bar != null) {
bar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO);
          bar.setCustomView(R.layout.custom_header);
        }

        // enable ActionBar app icon to behave as action to toggle nav drawer
        bar.setDisplayHomeAsUpEnabled(true);
        bar.setHomeButtonEnabled(true);

In fragment, the custom header is updated as follows.
Create instance of the activity in fragment to update the action bar header.
onAttach(Activity)called once the fragment is associated with its activity. So, we can assign our variable inside the onAttach
public MainActivity activity;
@Override
public void onAttach(Activity activity) {
          Log.d("CHECK", " Fragment onAttach");
      super.onAttach(activity);
      this.activity = (MainActivity) activity;
}

activity.updateActionBarHeader(activity.getTitle().toString());
Usually, we will be setting headers in onCreate of activity or fragment, Since onCreate(Bundle)called to do initial creation of the fragment.
Application launches and works fine, when we placed the updateActionBarHeader in onCreate, but when application navigated from fragment and returns back, the application crashes if the activity is destroyed and the exception thrown as follows.
FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity ComponentInfo {PACKAGE_NAME/MainActivity}: java.lang.NullPointerException
          at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2059)
          at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084)
          at android.app.ActivityThread.access$600(ActivityThread.java:130)
          at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195)
          at android.os.Handler.dispatchMessage(Handler.java:99)
          at android.os.Looper.loop(Looper.java:137)
          at android.app.ActivityThread.main(ActivityThread.java:4745)
          at java.lang.reflect.Method.invokeNative(Native Method)
          at java.lang.reflect.Method.invoke(Method.java:511)
          at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
          at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
          at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
          at MainActivity.updateActionBarHeader(MainActivity.java:232)
          at MainActivity$PlanetFragment.onCreate(MainActivity.java:276)
          at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:796)
          at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1035)
          at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1017)
          at android.app.FragmentManagerImpl.dispatchCreate(FragmentManager.java:1797)
          at android.app.Activity.onCreate(Activity.java:886)
          at MainActivity.onCreate(MainActivity.java:80)
          at android.app.Activity.performCreate(Activity.java:5008)
          at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1079)
          at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2023)
          ... 11 more

So, don't try to put the logic of updating action bar in onCreateView rather than onCreate of fragment and avoid the crash.

Thanks for reading :) 
Whether this post is helpful?

Have something to add to this post? If you have any other quick thoughts/hints that you think people will find useful? Share it in the comments.

Tuesday, February 25, 2014

Android: Solution to detect when an Android app goes to the background and come back to the foreground Using Application.ActivityLifecycleCallbacks and ComponentCallbacks2

We don’t have any direct approach to find whether our application gone to background but we have few interfaces from android which helps to solve this.

The following interfaces which help us are:
  1. Application.ActivityLifecycleCallbacks
  2. ComponentCallbacks2

Both the interfaces are added in API level 14.

Application.ActivityLifecycleCallbacks: This interface helps us to trigger the life cycle of all activities. We can use this ActivityLifecycleCallbacks interface in the Custom Application class, So that we can trigger all the activity life cycles @ one location.

ComponentCallbacks2:  This class extended ComponentCallbacks interface with a new callback for finer-grained memory management. This interface is available in all application components (Activity, Service, ContentProvider, and Application).
The public method onTrimMemory triggers when the operating system has determined that it is a good time for a process to trim unneeded memory from its process.  This will happen for example when it goes in the background and there is not enough memory to keep as many background processes running as desired.

Normally, onTrimMemory will be triggered when ever application goes to background, UI should be released at this point to allow memory to be better managed but we should never compare to exact values of the level, since new intermediate values may be added -- we will typically want to compare if the value is greater or equal to a level you are interested in.

Constants
Return type
Level 
Description
Constant Value
int
Level for onTrimMemory(int): the process has gone on to the LRU list. This is a good opportunity to clean up resources that can efficiently and quickly be re-built if the user returns to the app.
40
int
Level for onTrimMemory(int): the process is nearing the end of the background LRU list, and if more memory isn't found soon it will be killed.
80
int
Level for onTrimMemory(int): the process is around the middle of the background LRU list; freeing memory can help the system keep other processes running later in the list for better overall performance.
60
int
Level for onTrimMemory(int): the process is not an expendable background process, but the device is running extremely low on memory and is about to not be able to keep any background processes running.
15
int
Level for onTrimMemory(int): the process is not an expendable background process, but the device is running low on memory.
10
int
Level for onTrimMemory(int): the process is not an expendable background process, but the device is running moderately low on memory.
5
int
Level for onTrimMemory(int): the process had been showing a user interface, and is no longer doing so. Large allocations with the UI should be released at this point to allow memory to be better managed.
20

By using the above 2 interfaces, we can write our logic to trigger when application goes to background in the custom application class.

Create AppStatusApplication.java class which extends Application.java and two interfaces classes ActivityLifecycleCallbacks, ComponentCallbacks2 as follows

AppStatusApplication.java 

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Application;
import android.app.Application.ActivityLifecycleCallbacks;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;

/**
 * AppStatusApplication helps us to find, whether the application came to
 * foreground or not by using interfaces like ActivityLifecycleCallbacks,
 * ComponentCallbacks2 and Broadcast receiver.
 *
 * @author Vardhan
 *
 */
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public class AppStatusApplication extends Application implements
              ActivityLifecycleCallbacks, ComponentCallbacks2 {

       private static String TAG = AppStatusApplication.class.getName();

       public static String stateOfLifeCycle = "";

       public static boolean wasInBackground = false;

       @Override
       public void onCreate() {
              super.onCreate();
              registerActivityLifecycleCallbacks(this);
...
       }

       @Override
       public void onActivityCreated(Activity activity, Bundle arg1) {
              wasInBackground = false;
              stateOfLifeCycle = "Create";
       }

       @Override
       public void onActivityStarted(Activity activity) {
              stateOfLifeCycle = "Start";
       }

       @Override
       public void onActivityResumed(Activity activity) {
              stateOfLifeCycle = "Resume";
       }

       @Override
       public void onActivityPaused(Activity activity) {
              stateOfLifeCycle = "Pause";
       }

       @Override
       public void onActivityStopped(Activity activity) {
              stateOfLifeCycle = "Stop";
       }

       @Override
       public void onActivitySaveInstanceState(Activity activity, Bundle arg1) {
       }

       @Override
       public void onActivityDestroyed(Activity activity) {
              wasInBackground = false;
              stateOfLifeCycle = "Destroy";
       }

       @Override
       public void onTrimMemory(int level) {
              if (stateOfLifeCycle.equals("Stop")) {
                     wasInBackground = true;
              }
              super.onTrimMemory(level);
       }

       ...
}

Then create two activities to check whether application will trigger when app comes to foreground or not.
Here my two activities are MainActivity.java and NavigatedActivity.java

MainActivity.java

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

/**
 * MainActivity is used to check the life cycle flow and to test the application
 * status.
 *
 * @author Vardhan
 *
 */
public class MainActivity extends Activity {

       private static final String TAG = MainActivity.class.getName();

       @Override
       protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              Log.d(TAG, TAG + " onCreate");
              setContentView(R.layout.main);
       }

       @Override
       protected void onStart() {
              super.onStart();
              Log.d(TAG, TAG + " onStart");

              if (AppStatusApplication.wasInBackground) {
                     Toast.makeText(getApplicationContext(),
                                  "Application came to foreground", Toast.LENGTH_SHORT)
                                  .show();
                     AppStatusApplication.wasInBackground = false;
              }

              ((Button) findViewById(R.id.btn))
                           .setOnClickListener(new OnClickListener() {

                                  @Override
                                  public void onClick(View arg0) {
                                         startActivity(new Intent(getApplicationContext(),
                                                       NavigatedActivity.class));

                                  }
                           });
       }
}
NavigatedActivity.java
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

/**
 * NavigatedActivity is used to check the life cycle flow when we shift between
 * activities.
 *
 * @author Vardhan
 *
 */
public class NavigatedActivity extends Activity {

       private static final String TAG = NavigatedActivity.class.getName();

       @Override
       protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              Log.d(TAG, TAG + " onCreate");
              setContentView(R.layout.navigated_main);
       }

       @Override
       protected void onStart() {
              super.onStart();
              Log.d(TAG, TAG + " onStart");
              if (AppStatusApplication.wasInBackground) {
                     Toast.makeText(getApplicationContext(),
                                  "Application came to foreground", Toast.LENGTH_SHORT)
                                  .show();
                     AppStatusApplication.wasInBackground = false;
              }
       }
}

By using this three classes we can trigger whether application come to foreground or not.
If User wants to trigger @ screen switch off then we can use broadcast receiver in the custom application.

// Initiate
ScreenOffReceiver screenOffReceiver = new ScreenOffReceiver();

// declare in onCreate class of AppStatusApplication.java
registerReceiver(screenOffReceiver, new IntentFilter(
                           "android.intent.action.SCREEN_OFF"));

// Inner Class
class ScreenOffReceiver extends BroadcastReceiver {

       @Override
       public void onReceive(Context context, Intent intent) {
              wasInBackground = true;
       }
}

Screen Shot:

Source Code
You can download the source code by clicking here: AppStatusUsingActivityCallBacksICS-SourceCode.  This project is built using eclipse IDE. Unzip and import the project into Eclipse, it’s a good idea to use the Project by clean and rebuild from the project menu. It works in all API levels above API 14.

Have something to add to this post? Share it in the comments.