Today we’re going to take a look at Android’s most basic and in the same time most important concept, which is an Activity. To create even the simplest “HelloWorld” app one should know what are the Activities and how to work with them.
What is an Activity ?
The simplest, an Activity is a single screen in Android application. The concept of Activities is unusual in programming and it’s specific to Android platform.
Every Activity in Xamarin consists of two components:
- UI (XML) – layout with user controls defined
- Code (C#) – implementing the behavior
Let’s consider email client application – it’d probably have several Activities: “New email”, “List of emails”, “Email details”, “Settings” – notice all of them having both UI and behavior. We’d probably need “Email sending service”, which doesn’t necessarily need UI, so it wouldn’t be created as an Activity, but rather as a separate class realizing concrete task.
As we can already see, every Android app contains several Activities:
In most programming platforms, the application has a static method which is called on the application’s startup. This is not the case in Android – do you remember our MainActivity class from the previous post ? It’s decorated with ActivityAttribute which registers the Activity within the Android system, but what’s more important by having MainLauncher attribute’s property set to true it tells the system it’s the startup Activity for the application within which it’s registered. It means nothing more than when our app is launched, Android creates MainActivity as the first one from all Activities in our application. Apart from that, OS operates the MainActivity as all other Activities.
Activities Back Stack
Android app starts its own Activities from other Activities. It may also start Activities that are defined by different apps, e.g. your Android app can offer a possibility to send an email. You don’t have to implement your own email sending client for that – it’s possible to use already existing email sending Activities (using Android Intents, which we’ll also cover one day) registered within another apps installed in the system which expose such “external calling” possibility. Then, this external app launches looking as if it was the part of your application. This is very powerful!
When your email is finally sent, you are redirected back to your app from which you triggered email’s sending. How is that possible?
In order to ensure such way of functioning, Android keeps all the Activities needed to perform a certain job (even if they come from different apps) in the same task. The “scope” of a task (when new task is created, what Activities it contains etc.) depends, among others, on Android version (e.g. starting from Android 7.0 many apps can be started in multiple windows in the same time on a single screen – in that case, the system keeps a separate task for each window).
Within a particular task, several Activities are started. Each started Activity is pushed to the Activities Back Stack. The Activity being on the top of the stack has focus (is visible to the user). The previous Activities remain on the stack, but are stopped or even destroyed by Android system in certain cases (few more words about it in the next chapter).
Activities Back Stack is a LIFO objects structure: when user presses Back button the current Activity is popped from the top of the stack and the state of the previous Activity is restored. The following figure visualizes this behavior:
Android Activities Lifecycle
We already know the Activities are kept on Back Stack. We also need to know that Android OS may try to restart the application (after it crashed, for instance) at the last opened Activity. The OS may also pause the Activities when they’re not active or kill them when the device is low on memory. All those possible operations and states’ changes form Android Activities Lifecycle, which is a set of defined states in which every Activity may be:
Let’s see what those states mean:
- Running – Activity is in the foreground (top of the activities stack); highest-priority Activity for OS
- Paused – Activity is in this state when still visible, but covered by another non-full sized Activity or when the device enters sleep mode; second-highest-priority Activity for OS
- Backgrounded/Stopped– Activity enters Backgrounded state when it’s overtaken by another, completely new Activity which is pushed on the top of the back stack; lowest-priority Activity for OS, which will be killed firstly in order to free resources
- Restarted – this state is not visible on the diagram, however it’s possible that Android (e.g. user using task manager or similar app) kills the app being in any of above-mentioned states; if the user wants to go back to this Activity later, it must be restarted (previous state must be retrieved).
Handling states changes – lifecycle methods
Android (and Xamarin) provides SDK methods that are called by the OS each time an Activity’s state changes. Those methods may be overridden and implemented for each Activity in order to react on states changes and ensure application’s stability. The following diagram visualizes the dependencies and flow of methods being called:
Let’s see in what cases the particular methods should be implemented:
- OnCreate() – called when an Activity is created; used for initializing views and variables
- OnStart() – called immediately after OnCreate() finishes; UI refreshing can be handled here
- OnResume() – called after OnStart() finishes and also when Activity is restarted after being paused
- OnPause() – called when OS is about to pause or move the Activity to the background; here all resources-consuming objects should be cleaned-up, unsaved changes should be stored in some kind of persistent storage to be able to restore it when the Activity is revealed
- OnStop() – called when the Activity stops being visible to the user or is destroyed (e.g. when OS needs to release some resources)
- OnDestroy() – final method called on Activity just before it’s destroyed; it may not be called in some cases, so it’s better to clean-up resources in OnPause() and OnStop() methods.
Let’s see some code
In order to really test those lifecycle methods, I added a stunning functionality to MoneyBack app:
As you could already realize, you can enter amount of money spent, number of people involved and it will make a simple division operation to split the costs equally. That’s already kinda costs splitting, right ?! 🙂
I implemented the lifecycle-control methods to check how it really behaves.
OnCreate
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected override void OnCreate(Bundle bundle) | |
{ | |
base.OnCreate(bundle); | |
// Set our view from the "main" layout resource | |
SetContentView(Resource.Layout.Main); | |
_amount = 0.00m; | |
_result = 0.00m; | |
_numberOfPeople = 0; | |
InitializeUserControls(); | |
} | |
// … | |
private void InitializeUserControls() | |
{ | |
_inputAmount = this.FindViewById<EditText>(Resource.Id.inputAmount); | |
_inputNumberOfPeople = this.FindViewById<EditText>(Resource.Id.inputNumberOfPeople); | |
_txtResultDecimal = this.FindViewById<EditText>(Resource.Id.txtResultDecimal); | |
_btnCalculate = this.FindViewById<Button>(Resource.Id.btnCalculate); | |
} |
Here I only initialize my view from layout, Activity’s private variables and instantiate user controls. Nothing more for this simple example.
OnStart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected override void OnStart() | |
{ | |
base.OnStart(); | |
RefreshUserInputsFromVariables(); | |
} | |
// … | |
private void RefreshUserInputsFromVariables() | |
{ | |
_inputAmount.SetText(_amount.ToString(CultureInfo.InvariantCulture), TextView.BufferType.Editable); | |
_txtResultDecimal.SetText(_result.ToString(CultureInfo.InvariantCulture), TextView.BufferType.Editable); | |
_inputNumberOfPeople.SetText(_numberOfPeople.ToString(CultureInfo.InvariantCulture), | |
TextView.BufferType.Editable); | |
} |
In OnStart() method I just refresh UI elements – in that case initialize their Text properties with values stored in private variables.
OnResume
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected override void OnResume() | |
{ | |
base.OnResume(); | |
InitializeUserControlsEvents(); | |
// TODO: retrieving variables (amount, number of people) from persistent storage (file, database) | |
} | |
// … | |
private void InitializeUserControlsEvents() | |
{ | |
_btnCalculate.Click += _btnCalculate_Click; | |
} |
As we know, OnResume method is called every time our Activity is brought to the top of Activities stack (either after OnCreate -> OnStart or when revealing it after being backgrounded). That’s a very good place to initialize dependencies to external services (e.g. open communication with DB or remote service). In my case, I just subscribed to Click events of the button. If I had any persistent storage present (for instance local DB) I would in this place retrieve recently saved values from it to my Activity’s variables to restore its state (OnResume might be called when Activity class’s state is already cleaned up).
OnPause
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected override void OnPause() | |
{ | |
base.OnPause(); | |
DetatchUserControlsEvents(); | |
// TODO: saving variables (amount, number of people) to persistent storage (file, database) | |
} | |
// … | |
private void DetatchUserControlsEvents() | |
{ | |
_btnCalculate.Click -= _btnCalculate_Click; | |
} |
In OnPause method I need to be consistent with what I do in OnResume – when my app is taken to the background, I don’t need to wait for my button’s click events anymore, so let’s detach from it (maybe button’s Click event is not the best example here, but it’s just to visualize the purpose of the method). When my app is resumed, we will subscribe to this event again. Again, if I had any persistent storage, I’d save all variables’ (which are necessary for restoring Activity’s state) values here to this storage, so they are able to be retrieved back as soon as OnResume is called.
OnStop & OnDestroy
I didn’t implement those two methods in my app, it’s too simple to need anything in those places.
I deployed the app, attached with debugger and checked the order of methods’ calls – all of them were called as described in previous paragraph.
Summary
It’s vital to understand how Android Activities and their states work. Even though it seems to be Android-specific, I’d rather say such approach is reasonable and even required by hardware on which mobile apps run (BTW, similar states seem to exist in iOS apps).
When building Android app we need to think about Activities, especially make sure that each Activity in our application properly implements necessary lifecycle methods.
Do you have or know any best practices while working with Activities and managing their lifecycle? I’d appreciate if you share it in the comments 🙂
Thanks for the detailed description of the states, I learned a few things :).
How did you do to show only the numeric keyboard we can see on your screenshot, instead of the regular one?
Cheers
Hey,
numeric keyboard appeared when clicking on a text field with amount to enter, which has underlying control of type EditText, and this control has a property inputType set to “numberDecimal” in that case. That’s why Android automatically opens up a numeric keyboard for your input 😉
[…] + View Here […]