core

Android Application Class Example

We all know there is an Application class in the Android API, that is usually used for maintaining global application state but how does it work? In this article, let’s explore it with the help of examples and discuss its advantages & disadvantages.
 
 
 
 
 
 
 
 
 

1. Introduction

Application class is a base class within an Android application, that comprises of different components such as Activity, Service, Broadcast Receivers & Content Providers. These components communicate with each other with the help of Intents, Callbacks & other methods. Often, components depend on other components, for eg: initiating an activity on a button click.

In an application, there is often a need of making data available to all the components. For example, data entered by a user, needs to exist somewhere to let other components consume it.

Now, where do we keep the data so that it’s globally available? One may use Intent to pass information between components, but it can’t be accessed by every component. Components go through a lifecycle, that means they are not always available to provide the data. We need something global, that can be accessed by any component at any time. The answer to the question is Application class, that serves the purpose of maintaining the global application state.

You can also create a custom application class and add it to Android Manifest file, that will instantiate the class when the application process starts. Let’s discuss this later in the article.

Now, its time to talk about the Callback methods available in the Application class.

2. Callback Methods

Here are the Callback methods:

  • onConfigurationChanged – void onConfigurationChanged(Configuration newConfig)
    This method is called by the system when the device configuration changes, while running the application. When the configuration changes, only an Activity restarts (OnCreate() is called, followed by OnStart() and OnResume()), leaving the other components in the same state as they were before the configuration changed. The resource values are always up to date in this method, and can be retrieved by other components at any time.

    See an example below where we check the device orientation.

    onConfigurationChanged() example

    @Override
    
    public void onConfigurationChanged (Configuration newConfig){
    
    super.onConfigurationChanged(newConfig);
    
    if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){
    Toast.makeTest(context, "Landscape", Toast.LENGTH_SHORT).show;
    }else if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
    Toast.makeTest(context, "Portrait", Toast.LENGTH_SHORT).show;
    }
    }
  • onCreate – void onCreate()
    This method is called when the application is starting, before any activity, service, or receiver objects (excluding content providers) have been created. Implementations should be as quick as possible (for example using lazy initialization of state) since the time spent in this function directly impacts the performance of starting the first activity, service, or receiver in a process. If you override this method, be sure to call super.onCreate().

    Signature of onCreate()

    @Override
    
    public void onCreate (){
     super.onCreate();
     Log.d("Application Class", "ONCREATE");
    }

    Put the logs in the OnCreate() method in Activity and Application Class. When you run the code, check the logcat and you will see that Application Class is called before any Activity.

  • onLowMemory – void onLowMemory()
    This is called when the overall system is running low on memory, and actively running processes should trim their memory usage. While the exact point at which this will be called is not defined, generally it will happen when all background process have been killed. That is, before reaching the point of killing processes hosting service and foreground UI that we would like to avoid killing. You should implement this method to release any caches or other unnecessary resources you may be holding on to. The system will perform a garbage collection for you after returning from this method.

    Signature of onLowMemory()

    @Override
    public void onLowMemory() {
     super.onLowMemory();
    }
  • onTerminate – void onTerminate()
    This method is for use in emulated process environments. It will never be called on a production Android device, where processes are removed by simply killing them; no user code (including this callback) is executed when doing so.
  • onTrimMemory – void onTrimMemory(int level)
    When building the Android application, the one thing developer has to keep in mind is Memory. It is the most crucial part of the application process and a developer’s duty to check if there is any memory leak or if there is need to free up resources not used by application anymore. Denying this, may lead to unexpected killing of the application process by the system. This method is called 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. You should never compare to exact values of the level, since new intermediate values may be added — you will typically want to compare if the value is greater or equal to a level you are interested in.

    To retrieve the process’s current trim level at any point, you can use ActivityManager.getMyMemoryState(RunningAppProcessInfo).

    The different levels are:

    TRIM_MEMORY_RUNNING_MODERATE level means the device is beginning to run low on memory but not killable.

    TRIM_MEMORY_RUNNING_LOW level means the device is running much lower on memory and not killable. It is better to start releasing unused resources.

    TRIM_MEMORY_RUNNING_CRITICAL level means the device is running extremely low on memory and the system will begin killing background processes.

    TRIM_MEMORY_UI_HIDDEN level means the application’s UI is no longer visible, so this is a good time to release large resources that are used only by your UI.

    TRIM_MEMORY_MODERATE level means the process is around the middle of the background LRU list and if the system further gives memory warning then there is a chance for your process to be killed.

    TRIM_MEMORY_BACKGROUND level means the process has gone on to the LRU list and it’s a good opportunity to clean up the resources before user returns to the app.

    TRIM_MEMORY_COMPLETE level means the process is nearing the end of the background LRU list, and if more memory isn’t found soon, it will be killed.

    Note: This callback was added in API level 14. For older versions, you can use onLowMemory().

  • registerActivityLifecycleCallbacks– void registerActivityLifecycleCallbacks (Application.ActivityLifecycleCallbacks callback). It allows you to handle state change of each activity in your program.
     
    Signature of registerActivityLifecycleCallbacks

    // set ActivityLifecycleCallbacks
    Application app = getApplication();
    app.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks(){
        @Override
        public void onActivityCreated(Activity activity, Bundle bundle) {}
     
        @Override
        public void onActivityStarted(Activity activity) {}
     
        @Override
        public void onActivityResumed(Activity activity) {}
     
        @Override
        public void onActivityPaused(Activity activity) {}
     
        @Override
        public void onActivityStopped(Activity activity) {}
     
        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {}
     
        @Override
        public void onActivityDestroyed(Activity activity) {}
    });
    
  • registerComponentCallbacks – void registerComponentCallbacks (ComponentCallbacks callback)
    Add a new ComponentCallbacks to the base application of the Context, which will be called at the same times as the ComponentCallbacks methods of activities and other components are called. Note that you must be sure to use unregisterComponentCallbacks(ComponentCallbacks) when appropriate in the future; this will not be removed for you.

    Signature of registerComponentCallbacks

    // set ComponentCallbacks with out overriding
       app.registerComponentCallbacks(new ComponentCallbacks2() {
        @Override
        public void onConfigurationChanged(Configuration configuration) {
             //your code goes here
        } 
        @Override
        public void onLowMemory() {
        // use it only for older API version 
        }
        @Override
        public void onTrimMemory(int level) { 
              //code
        }
    });
  • registerOnProvideAssistDataListener
    Callback used Application.OnProvideAssistDataListener . You may know, there is a Now On Tap functionality in Android, that is visible by long pressing the home button. A small window from the bottom of the display, slides up to show you the relevant information. The information you get, depends on what you’re viewing on your screen at that time (for eg: Music app displays information about the song on the screen). To provide additional information to the assistant, your app provides global application context by registering an app listener using registerOnProvideAssistDataListener() and supplies activity-specific information with activity callbacks by overriding onProvideAssistData() and onProvideAssistContent(). It allows you to place anything, into the Bundle, you would like to appear in the Intent.EXTRA_ASSIST_CONTEXT part of the assist Intent.
     
    In the example below, a music app provides structured data to describe the music album that the user is currently viewing.

    onProvideAssistContent Example

    @Override
    public void onProvideAssistContent(AssistContent assistContent) {
      super.onProvideAssistContent(assistContent);
    
      String structuredJson = new JSONObject()
           .put("@type", "MusicRecording")
           .put("@id", "https://example.com/music/recording")
           .put("name", "Album Title")
           .toString();
    
      assistContent.setStructuredData(structuredJson);
    }
  • unregisterComponentCallbacks – void unregisterComponentCallbacks (ComponentCallbacks callback)
    The method removes a ComponentCallbacks object that was previously registered with registerComponentCallbacks(ComponentCallbacks). The other callbacks unregisterActivityLifecycleCallbacks and unregisterOnProvideAssistDataListener does the same thing removing the corresponding object that was previously registered.

3. Creating the Application

Consider a scenario, where you have two screens that display the Game score. Up to date score has to be available on all the screens. This requires the Game score to be globally stored & available. In this example screens are two different activities: FirstScreen & SecondScreen; and the Application class (Global State) provides the most up to date score.

To create a custom application class, Application class needs to be extended and must be declared in the Manifest.

Let’s start building the application and see how code handles the application state.

3.1 The Activity File

This is the first screen.

FirstGameScreenActivity.java

public class FirstGameScreenActivity extends Activity {

    Button score,upScore,downScore;
    GlobalState state;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first_game_screen);
        state = ((GlobalState) getApplicationContext());
        score= (Button)findViewById(R.id.score_button);
        upScore= (Button)findViewById(R.id.up_button);
        downScore= (Button)findViewById(R.id.down_button);

        score.setText(String.valueOf(state.getGameScore()));

    }

    @Override
    protected void onResume() {
        super.onResume();

    }

    public void incrementScore(View view) {
        state.incrementScore();
        score.setText(String.valueOf(state.getGameScore()));
    }

    public void decrementScore(View view){
        state.decrementScore();
        score.setText(String.valueOf(state.getGameScore()));
    }

    public void nextScreen(View view) {
        Intent intent = new Intent(this, SecondGameScreenActivity.class);
        startActivity(intent);
        finish();
    } }

This is the second screen.

SecondGameScreenActivity.java

public class SecondGameScreenActivity extends Activity {

    Button score,upScore,downScore;
    GlobalState state;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second_game_screen);
        state = ((GlobalState) getApplicationContext());
        score= (Button)findViewById(R.id.score_button);
        upScore= (Button)findViewById(R.id.up_button);
        downScore= (Button)findViewById(R.id.down_button);

        score.setText(String.valueOf(state.getGameScore()));
    }

    @Override
    protected void onResume() {
        super.onResume();

    }

    public void incrementScore(View view) {
        state.incrementScore();
        score.setText(String.valueOf(state.getGameScore()));
    }

    public void decrementScore(View view){
        state.decrementScore();
        score.setText(String.valueOf(state.getGameScore()));
    }

    public void previousScreen(View view) {
        Intent intent = new Intent(this, FirstGameScreenActivity.class);
        startActivity(intent);
        finish();
} }

This is the Custom Application class that extends Application Class.

GlobalState.java

public class GlobalState extends Application {

    private int gameScore = 0;

    public int getGameScore() {
        return gameScore;
    }

    public void setGameScore(int gameScore) {
        this.gameScore = gameScore;
    }

    public void incrementScore(){
        gameScore++;
    }
    public void decrementScore(){gameScore--;}

}

3.2 The Layout File

These are the two layout files. I have used the recently added Constraint Layout for convenience. It allows flexibility in positioning and sizing widgets. For more info refer Constraint Layout.

activity_first_game_screen.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorBlack">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="54dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginStart="46dp"
        android:layout_marginTop="28dp"
        android:text="GAME SCREEN"
        android:textAlignment="center"
        android:textColor="@color/colorWhite"
        android:textSize="40sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <Button
        android:id="@+id/score_button"
        android:layout_width="46dp"
        android:layout_height="0dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginBottom="17dp"
        android:text=""
        app:layout_constraintBottom_toTopOf="@+id/up_button"
        app:layout_constraintEnd_toEndOf="@+id/up_button"
        app:layout_constraintStart_toEndOf="@+id/up_button"  />

    <Button
        android:id="@+id/up_button"
        android:layout_width="47dp"
        android:layout_height="41dp"
        android:layout_marginEnd="9dp"
        android:layout_marginStart="138dp"
        android:layout_marginTop="136dp"
        android:backgroundTint="@color/colorGreen"
        android:onClick="incrementScore"
        android:text="+"
        android:textAlignment="center"
        android:textColor="@color/colorWhite"
        android:textSize="20sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toStartOf="@+id/down_button"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <Button
        android:id="@+id/down_button"
        android:layout_width="47dp"
        android:layout_height="41dp"
        android:layout_marginEnd="151dp"
        android:layout_marginTop="136dp"
        android:backgroundTint="@color/colorRed"
        android:onClick="decrementScore"
        android:text="-"
        android:textAlignment="center"
        android:textColor="@color/colorWhite"
        android:textSize="18dp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/up_button"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <Button
        android:id="@+id/next_screen_button"
        android:layout_width="235dp"
        android:layout_height="56dp"
        android:layout_marginBottom="7dp"
        android:onClick="nextScreen"
        android:text="Next Screen"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/score_button" />


</android.support.constraint.ConstraintLayout>

Here we have the second layout for second screen.

activity_second_game_screen.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorBlack">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="54dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginStart="46dp"
        android:layout_marginTop="28dp"
        android:text="GAME SCREEN"
        android:textAlignment="center"
        android:textColor="@color/colorWhite"
        android:textSize="40sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <Button
        android:id="@+id/score_button"
        android:layout_width="46dp"
        android:layout_height="0dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginBottom="17dp"
        android:text=""
        app:layout_constraintBottom_toTopOf="@+id/up_button"
        app:layout_constraintEnd_toEndOf="@+id/up_button"
        app:layout_constraintStart_toEndOf="@+id/up_button"  />

    <Button
        android:id="@+id/up_button"
        android:layout_width="47dp"
        android:layout_height="41dp"
        android:layout_marginEnd="9dp"
        android:layout_marginStart="138dp"
        android:layout_marginTop="136dp"
        android:backgroundTint="@color/colorGreen"
        android:onClick="incrementScore"
        android:text="+"
        android:textAlignment="center"
        android:textColor="@color/colorWhite"
        android:textSize="20sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toStartOf="@+id/down_button"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <Button
        android:id="@+id/down_button"
        android:layout_width="47dp"
        android:layout_height="41dp"
        android:layout_marginEnd="151dp"
        android:layout_marginTop="136dp"
        android:backgroundTint="@color/colorRed"
        android:onClick="decrementScore"
        android:text="-"
        android:textAlignment="center"
        android:textColor="@color/colorWhite"
        android:textSize="18dp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/up_button"
        app:layout_constraintTop_toBottomOf="@+id/textView" />


    <Button
        android:id="@+id/next_screen_button"
        android:layout_width="235dp"
        android:layout_height="56dp"
        android:layout_marginBottom="7dp"
        android:onClick="previousScreen"
        android:text="Previous Screen"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/score_button" />

</android.support.constraint.ConstraintLayout>

4. Manifest

The manifest file provides the most important information about the application to the Android system, before it runs. It defines the structure and metadata of an application and its components.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.archigupta.applicationclassexamle">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:name="GlobalState" .  // declare the application class
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".FirstGameScreenActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".SecondGameScreenActivity">

        </activity>
    </application>

</manifest>

5. Running the Application

See screenshots here. First Screen and Second Screen are the two game screens.

First Screen and Second Screen respectively

Now incrementing the score from First Screen and moving on to the next screen by pressing Next Screen button, will update the score on Second Screen.

Updated Score on Both Screens

6. Limitations

As we have seen above, putting the data in the Application class saves the overhead of passing it between objects. Besides this, if you store mutable objects in Application class, data might disappear or become invalid at any time. Also, there might be a possibility where your app may crash with Null Pointer Exception. The application object is not guaranteed to stay in memory forever, at some point in time, it will get killed. When the application restarts, it never gets restarted from scratch. Instead, a new Application object is created and starts the Activity, user was on before. It is better to store any mutable shared data using persistence strategies such as files, SharedPreferencesSQLite and so on.

To prevent data loss, use one of the following methods:

  • Explicitly pass the data through Intents
  • Store the global data in memory
  • Perform Null check before retrieving the value of an object

7. Conclusion

We have now seen how to use global data in an application by using Application class. Despite of its usage, storing data in Application class is error prone and might crash your application, under certain circumstances.

8. Download the Source Code

This was an example of Application class.

Download
You can download the full source code of this example here: Application Class Example

Archi Gupta

Archi is a passionate Software developer who can think out of the box. She began her career as an Android developer with one start up and since then developed interest in this field. In her leisure time, she loves solving coding problems and building mobile applications. Apart from this she holds her Master degree in Computer Science
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
JoeHx
6 years ago

It’s one of my goals this year to make & publish a (simple) Android Application. Thanks for this post, it’s really helpful!

Back to top button