Sticky Intents & acquiring System Service status

As mentioned in the Broadcast Receiver post, updates will only be applied when the app is visible (between onResume and onPause). Therefore system changes which occur outside this window could leave the app with outdated information on resume. Therefore we should also check at onResume what the current system status is.

Depending on the version of Android the device is running you may be able to acquire a status directly from e.g. BatteryManager:

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // If using a modern enough version of Android...
            BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE); // ...ask the appropriate service for a status check
            doSomething(batteryManager.isCharging()); // Returns a boolean you can feed back into your own methods
        } else // Otherwise use sticky intents (see below)

Certain system broadcast intents do not disappear the moment they have fired and are processed. Instead they remain available to the system until superceded by a more up-to-date intent. These are known as sticky intents, and are the way of determining system status in older Android versions:

   {
            IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); // Create a new IntentFilter for the appropriate sticky Intent
            Intent chargingStatus = registerReceiver(null, intentFilter); // The same code you would use to register a Broadcast Receiver, but leave the first parameter null
            int batteryChargingStatus = chargingStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); // Get the info you want from the sticky Intent, with a default value
            boolean isCharging = batteryChargingStatus == BatteryManager.BATTERY_STATUS_CHARGING || batteryChargingStatus == BatteryManager.BATTERY_STATUS_FULL; // This will return a boolean of true if the phone is charging or fully charged
            doSomething(isCharging); // Feed the boolean into your own methods
   }

ud851-Exercises-student\Lesson10-Hydration-Reminder\T10.06

Broadcast Receiver

Listens out for designated System Broadcast Intents via an IntentFilter (declared in the AndroidManifest.xml) so that your app can respond to changes in the system. It can be triggered even when the app is not running. There are two types:

  • Static (Manifest-declared) – triggered whenever the broadcast intent occurs, even if the app is offline
  • Dynamic (Context-registered) – tied to the app’s lifecycle

It is preferred that dynamic broadcast receivers or job scheduling is used over static broadcast receivers, as abuse of statics could result in multiple apps responding to a system event. For this reason, some broadcast intents will not let you create a corresponding static receiver. These intents have FLAG_RECEIVER_REGISTERED_ONLY flag set.

For a static broadcast receiver you will register the receiver in the AndroidManifest.xml:

<receiver android:name=".MyBroadcastReceiver"  android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
    </intent-filter>
</receiver>

Then implement the receiver like so:

public class MyBroadcastReceiver extends BroadcastReceiver {
        private static final String TAG = "MyBroadcastReceiver";
        @Override
        public void onReceive(Context context, Intent intent) {
            // Do something
        }
    }

For a dynamic receiver you register and un-register the receiver in the onResume() and onPause method overrides respectively:

public class MyBroadcastReceiver extends BroadcastReceiver {
        private static final String TAG = "MyBroadcastReceiver";
        BroadcastReceiver br = new MyBroadcastReceiver();
        @Override
        public void onReceive(Context context, Intent intent) {
            //Do something
        }
    
    @Override
    protected void onResume() {
    super.onResume();
    IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
    filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    this.registerReceiver(br, filter);
    @Override
    protected void onPause() {
    super.onPause();
    this.unRegisterReceiver(br);
    }
}

The potential issue with the above example of a Dynamic receiver is that it will only check for changes once onResume has initiated, and so if the system status changes while the app is not visible, it may hold the incorrect status when the app starts. Adding the code to the onCreate and onDestroy methods would result in unnecessary actions occurring in the background when the app is not visible. To overcome this, check for the current system status on onResume in addition to registering the broadcast receiver. See an example here.

ud851-Exercises-student\Lesson10-Hydration-Reminder\T10.05

Activity Lifecycle Stages

Activity lifecycle stages and their Callback names

General Definitions

Visible Lifecycle: The part of the Lifecycle between onStart and onStop when the Activity is visible.

Focus: An Activity is said to have focus when it’s the activity the user can interact with.

Foreground: When the activity is on screen.

Background: When the activity is fully off screen, it is considered in the background.

Lifecycle States

These are the same for both the Fragment Lifecycle and the Activity Lifecycle.

Initialized: This is the starting state whenever you make a new activity. This is a transient state — it immediately goes to Created.

Created: Activity has just been created, but it’s not visible and it doesn’t have focus (you’re not able to interact with it).

Started: Activity is visible but doesn’t have focus.

Resumed: The state of the activity when it is running. It’s visible and has focus.

Destroyed: Activity is destroyed. It can be ejected from memory at any point and should not be referenced or interacted with.

Activity Lifecycle Callbacks

OnCreate: This is called the first time the activity starts and is therefore only called once during the lifecycle of the activity. It represents when the activity is created and initialized. The activity is not yet visible and you can’t interact with it. You must implement onCreate. In onCreate you should:

Inflate the activity’s UI, whether that’s using findViewById or databinding.
Initialize variables.
Do any other initialization that only happens once during the activity lifetime.

onStart: This is triggered when the activity is about to become visible. It can be called multiple times as the user navigates away from the activity and then back. Examples of the user “navigating away” are when they go to the home screen, or to a new activity in the app. At this point, the activity is not interactive. In onStart you should:

Start any sensors, animations or other procedures that need to start when the activity is visible.
onResume: This is triggered when the activity has focus and the user can interact with it. Here you should:

Start any sensors, animations or other procedures that need to start when the activity has focus (the activity the user is currently interacting with).

onPause: The mirror method to onResume. This method is called as soon as the activity loses focus and the user can’t interact with it. An activity can lose focus without fully disappearing from the screen (for example, when a dialog appears that partially obscures the activity). Here you should:

Stop any sensors, animations or other procedures that should not run when the activity doesn’t have focus and is partially obscured.
Keep execution fast. The next activity is not shown until this completes.

onStop: This is the mirror method to onStart. It is called when you can no longer see the activity. Here you should:

Stop any sensor, animations or other procedures that should not run when the activity is not on screen.
You can use this to persist (permanently save) data, which you’ll be learning more about in Lesson 6
Stop logic that updates the UI. This should not be running when the activity is off-screen; it’s a waste of resources.
There are also restrictions as soon as the app goes into the background, which is when all activities in your app are in the background. We’ll talk more about this in Lesson 9.

onDestroy: This is the mirror method to onCreate. It is called once when the activity is fully destroyed. This happens when you navigate back out of the activity (as in press the back button), or manually call finish(). It is your last chance to clean up resources associated with the activity. Here you should:

Tear down or release any resources that are related to the activity and are not automatically released for you. Forgetting to do this could cause a memory leak! Logic that refers to the activity or attempts to update the UI after the activity has been destroyed could crash the app!

Summary of the Fragment Lifecycle

Fragments also have lifecycle states that they go between. The lifecycle states are the same as the activity states. You’ll notice that in your Android Trivia app, you’re using the onCreateView callback – while the fragment lifecycle states are the same, the callbacks are different.

A deep dive into the fragment lifecycle could be a lesson in itself. Here, we’ll just cover the basics with the summary below:

Important Fragment Callbacks to Implement

onCreate: Similar to the Activity’s onCreate callback. This is when the fragment is created. This will only get called once. Here you should:

Initialise anything essential for you fragment.

DO NOT inflate XML, do that in onCreateView, when the system is first drawing the fragment NOT reference the activity, it is still being created. Do this in onActivityCreated.

onCreateView: This is called between onCreate and onActivityCreated. when the system will draw the fragment for the first time when the fragment becomes visible again. You must return a view in this callback if your fragment has a UI. Here you should:

Create your views by inflating your XML.

onStop: Very similar to Activity’s onStop. This callback is called when the user leaves your fragment. Here you should:

Save any permanent fragment state (this will be discussed in lesson 6)

Other callbacks

onAttach: When the fragment is first attached to the activity. This is only called once during the lifecycle of the fragment.

onActivityCreated: Called when the activity onCreate method has returned and the activity has been initialized. If the fragment is added to an activity that’s already created, this still gets called. It’s purpose is to contain any code the requires the activity exists and it is called multiple times during the lifecycle of the fragment. Here you should:

Execute any code that requires an activity instance

onStart: Called right before the fragment is visible to the user.

onResume: When the activity resumes the fragment. This means the fragment is visible, has focus and is running.

onStop: Called when the Activity’s onStop is called. The fragment no longer has focus.

onDestroyView: Unlike activities, fragment views are destroyed every time they go off screen. This is called after the view is no longer visible.

Do not refer to views in this callback, since they are destroyed

onDestroy: Called when the Activity’s onDestroy is called.

onDetach: Called when the association between the fragment and the activity is destroyed.

Lifecycle Cheat sheets

What you’ve seen up to this point are the Activity Lifecycle and the Fragment Lifecycle for a single Activity or Fragment. For more complicated apps, it becomes important to understand the interactions between Activity and Fragment life cycles and multiple activities. This is outside of the scope of this lesson, but there are a series of excellent blog posts and cheat sheets posted by Googler which are helpful references for this:

The Android Lifecycle cheat sheet — part I: Single Activity – This is a visual recap of much of the material here.
The Android Lifecycle cheat sheet — part II: Multiple Activities – This shows the order of lifecycle calls when two activities interact.
The Android Lifecycle cheat sheet — part III: Fragments – This show the order of lifecycle calls when an activity and fragment interact.