UNIT 3 Android Developer Fundamentals (V2)
UNIT 3 Android Developer Fundamentals (V2)
Fundamentals
(Version 2) course
Last updated Tue Sep 11 2018
developer.android.com/courses/adfv2
Note: This course uses the terms "codelab" and "practical" interchangeably.
We advise you to use the online version of this course rather than
this static PDF to ensure you are using the latest content.
See developer.android.com/courses/adf-v2.
1
Android Developer Fundamentals Course (V2) – Unit 3
7.1 : AsyncTask
8.1 : Notifications
8.3 : JobScheduler
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 2
Android Developer Fundamentals Course (V2) – Unit 3
Introduction
A thread is an independent path of execution in a running program. When an Android program is
launched, the system creates a main thread, which is also called the UI thread. This UI thread is how
your app interacts with components from the Android UI toolkit.
Sometimes an app needs to perform resource-intensive tasks such as downloading files, making
database queries, playing media, or computing complex analytics. This type of intensive work can
block the UI thread so that the app doesn't respond to user input or draw on the screen. Users may
get frustrated and uninstall your app.
To keep the user experience (UX) running smoothly, the Android framework provides a helper class
called AsyncTask, which processes work off of the UI thread. Using AsyncTask to move intensive
processing onto a separate thread means that the UI thread can stay responsive.
Because the separate thread is not synchronized with the calling thread, it's called an asynchronous
thread. An AsyncTask also contains a callback that allows you to display the results of the
computation back in the UI thread.
In this practical, you learn how to add a background task to your Android app using an AsyncTask.
● Create an Activity.
● Programmatically get the id for the TextView and set its content.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 3
Android Developer Fundamentals Course (V2) – Unit 3
What you'll do
● Create a simple app that executes a background task using an AsyncTask.
● Run the app and see what happens when you rotate the device.
App overview
You will build an app that has one TextView and one Button. When the user clicks the Button, the
app sleeps for a random amount of time, and then displays a message in the TextView when it
wakes up. Here's what the finished app looks like:
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 4
Android Developer Fundamentals Course (V2) – Unit 3
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 5
Android Developer Fundamentals Course (V2) – Unit 3
4. Add or modify the following attributes of the "Hello World!" TextView to have these values.
Extract the string into a resource.
Attribute Value
android:id "@+id/textView1
android:textSize "24sp"
6. Add a Button element just under the TextView, and give it these attributes. Extract the
button text into a string resource.
Attribute Value
android:id "@+id/button"
android:layout_width "wrap_content"
android:layout_height "wrap_content"
android:layout_marginTop "24dp"
android:onClick "startTask"
app:layout_constraintStart_toStartOf "parent"
app:layout_constraintTop_toBottomOf "@+id/textView1"
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 6
Android Developer Fundamentals Course (V2) – Unit 3
7. The onClick attribute for the button will be highlighted in yellow, because the startTask()
method is not yet implemented in MainActivity. Place your cursor in the highlighted text,
press Alt + Enter (Option + Enter on a Mac) and choose Create 'startTask(View) in
'MainActivity' to create the method stub in MainActivity.
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ready_to_start"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:onClick="startTask"
android:text="@string/start_task"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView1"/>
</android.support.constraint.ConstraintLayout>
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 7
Android Developer Fundamentals Course (V2) – Unit 3
An AsyncTask subclass has the following methods for performing work off of the main thread:
● onPreExecute(): This method runs on the UI thread, and is used for setting up your task
(like showing a progress bar).
● doInBackground(): This is where you implement the code to execute the work that is to be
performed on the separate thread.
● onProgressUpdate(): This is invoked on the UI thread and used for updating progress in the
UI (such as filling up a progress bar)
● onPostExecute(): Again on the UI thread, this is used for updating the results to the UI once
the AsyncTask has finished loading.
Note: A background or worker thread is any thread which is not the main or UI thread.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 8
Android Developer Fundamentals Course (V2) – Unit 3
When you create an AsyncTask subclass, you may need to give it information about the work which
it is to perform, whether and how to report its progress, and in what form to return the result.
When you create an AsyncTask subclass, you can configure it using these parameters:
● Params: The data type of the parameters sent to the task upon executing the
doInBackground() override method.
● Progress: The data type of the progress units published using the onProgressUpdated()
override method.
● Result: The data type of the result delivered by the onPostExecute() override method.
For example, an AsyncTask subclass called MyAsyncTask with the following class declaration might
take the following parameters:
In this task you will use an AsyncTask subclass to define work that will run in a different thread than
the UI thread.
1. Create a new Java class called SimpleAsyncTask that extends AsyncTask and takes three
generic type parameters.
Use Void for the params, because this AsyncTask does not require any inputs. Use Void for
the progress type, because the progress is not published. Use a String as the result type,
because you will update the TextView with a string when the AsyncTask has completed
execution.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 9
Android Developer Fundamentals Course (V2) – Unit 3
Note: The class declaration will be underlined in red, because the required method
doInBackground() has not yet been implemented.
2. At the top of the class, define a member variable mTextView of the type
WeakReference<TextView>:
3. Implement a constructor for AsyncTask that takes a TextView as a parameter and creates a
new weak reference for that TextView:
SimpleAsyncTask(TextView tv) {
mTextView = new WeakReference<>(tv);
}
The AsyncTask needs to update the TextView in the Activity once it has completed sleeping (in
the onPostExecute() method). The constructor for the class will therefore need a reference to the
TextView to be updated.
What is the weak reference (the WeakReference class) for? If you pass a TextView into the
AsyncTask constructor and then store it in a member variable, that reference to the TextView
means the Activity cannot ever be garbage collected and thus leaks memory, even if the Activity
is destroyed and recreated as in a device configuration change. This is called creating a leaky context,
and Android Studio will warn you if you try it.
The weak reference prevents the memory leak by allowing the object held by that reference to be
garbage collected if necessary.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 10
Android Developer Fundamentals Course (V2) – Unit 3
1. Place your cursor on the highlighted class declaration, press Alt + Enter (Option + Enter on
a Mac) and select Implement methods. Choose doInBackground() and click OK. The
following method template is added to your class:
@Override
protected String doInBackground(Void... voids) {
return null;
}
2. Add code to generate a random integer between 0 and 10. This is the number of
milliseconds the task will pause. This is not a lot of time to pause, so multiply that number by
200 to extend that time.
int s = n * 200;
try {
Thread.sleep(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 11
Android Developer Fundamentals Course (V2) – Unit 3
4. Replace the existing return statement to return the String "Awake at last after sleeping for
xx milliseconds", where xx is the number of milliseconds the app slept.
@Override
protected String doInBackground(Void... voids) {
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 12
Android Developer Fundamentals Course (V2) – Unit 3
1. Implement onPostExecute() to take a String argument and display that string in the
TextView:
The String parameter to this method is what you defined in the third parameter of your AsyncTask
class definition, and what your doInBackground() method returns.
Because mTextView is a weak reference, you have to deference it with the get() method to get the
underlying TextView object, and to call setText() on it.
Note: You can update the UI in onPostExecute() because that method is run on the main thread.
You cannot update the TextView with the new string in the doInBackground() method, because
that method is executed on a separate thread.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 13
Android Developer Fundamentals Course (V2) – Unit 3
3. In the startTask() method, Update the TextView to show the text "Napping…". Extract that
message into a string resource.
mTextView.setText(R.string.napping);
Note: The execute() method is where you pass comma-separated parameters that are then
passed into doInBackground() by the system. Because this AsyncTask has no parameters, you
leave it blank.
package com.example.android.simpleasynctask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
/**
* The SimpleAsyncTask app contains a button that launches an AsyncTask
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 14
Android Developer Fundamentals Course (V2) – Unit 3
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.textView1);
}
Note: You'll notice that when the device is rotated, the TextView resets to its initial content, and
the AsyncTask does not seem able to update the TextView.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 15
Android Developer Fundamentals Course (V2) – Unit 3
● When you rotate the device, the system restarts the app, calling onDestroy() and then
onCreate(). The AsyncTask will continue running even if the activity is destroyed, but it will
lose the ability to report back to the activity's UI. It will never be able to update the TextView
that was passed to it, because that particular TextView has also been destroyed.
● Once the activity is destroyed the AsyncTask will continue running to completion in the
background, consuming system resources. Eventually, the system will run out of resources,
and the AsyncTask will fail.
● Even without the AsyncTask, the rotation of the device resets all of the UI elements to their
default state, which for the TextView is the default string that you set in the layout file.
For these reasons, an AsyncTask is not well suited to tasks which may be interrupted by the
destruction of the Activity. In use cases where this is critical you can use a different type of
background class called an AsyncTaskLoader which you will learn about in a later practical.
In order to prevent the TextView from resetting to the initial string, you need to save its state. You
will now implement onSaveInstanceState() to preserve the content of your TextView when the
activity is destroyed in response to a configuration change such as device rotation.
Note: Not all uses of AsyncTask require you to handle the state of the views on rotation. This app
uses a TextView to display the results of the app, so preserving the state is useful. In other cases,
such as uploading a file, you may not need any persistent information in the UI, so retaining the
state is not critical.
3. At the top of the class, add a constant for the key for the current text in the state bundle:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save the state of the TextView
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 16
Android Developer Fundamentals Course (V2) – Unit 3
outState.putString(TEXT_STATE,
mTextView.getText().toString());
}
}
5. In onCreate(), retrieve the value of the TextView from the state bundle when the activity is
restored.
package android.example.com.simpleasynctask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
/**
* The SimpleAsyncTask app contains a button that launches an AsyncTask
* which sleeps in the asynchronous thread for a random amount of time.
*/
public class MainActivity extends AppCompatActivity {
/**
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 17
Android Developer Fundamentals Course (V2) – Unit 3
/**`
* Handles the onCLick for the "Start Task" button. Launches the
AsyncTask
* which performs work off of the UI thread.
*
* @param view The view (Button) that was clicked.
*/
public void startTask (View view) {
// Put a message in the text view
mTextView.setText(R.string.napping);
/**
* Saves the contents of the TextView to restore on configuration
change.
* @param outState The bundle in which the state of the activity is
saved when it is spontaneously destroyed.
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save the state of the TextView
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 18
Android Developer Fundamentals Course (V2) – Unit 3
outState.putString(TEXT_STATE, mTextView.getText().toString());
}
}
Coding challenge
Note: All coding challenges are optional and are not prerequisites for later lessons.
Challenge: The AsyncTask class provides another useful override method: onProgressUpdate(),
which allows you to update the UI while the AsyncTask is running. Use this method to update the UI
with the current sleep time. Look to the AsyncTask documentation to see how onProgressUpdate()
is properly implemented. Remember that in the class definition of your AsyncTask, you will need to
specify the data type to be used in the onProgressUpdate() method.
Summary
● An AsyncTask is an abstract Java class that moves intensive processing onto a separate
thread.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 19
Android Developer Fundamentals Course (V2) – Unit 3
● Any code that does not involve drawing the UI or responding to user input should be moved
from the UI thread to another, separate thread.
● The doInBackground() method is the only method that is actually run on a worker thread.
Do not call UI methods in the doInBackground() method.
● The other methods of AsyncTask run in the UI thread and allow you to call methods of UI
components.
● Rotating an Android device destroys and recreates an Activity. This can disconnect the UI
from the background thread in AsyncTask, which will continue to run.
Related concept
The related concept documentation is in 7.1: AsyncTask and AsyncTaskLoader.
Learn more
Android developer documentation:
Other resources:
Videos:
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 20
Android Developer Fundamentals Course (V2) – Unit 3
Homework
Build and run an app
Open the SimpleAsyncTask app. Add a ProgressBar that displays the percentage of sleep time
completed. The progress bar fills up as the AsyncTask thread sleeps from a value of 0 to 100
(percent).
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 21
Android Developer Fundamentals Course (V2) – Unit 3
Question 1
For a ProgressBar:
1. How do you determine the range of values that a ProgressBar can show?
2. How do you change how much of the progress bar is filled in?
Question 2
1. What is the type of the value that is passed to doInBackground() in the AsyncTask?
2. What is the type of the value that is passed to the callback that reports the progress of the
task?
3. What is the type of the value that is passed to the callback that is executed when the task
completes?
Question 3
To report progress of the work executed by an AsyncTask, what callback method do you implement,
and what method do you call in your AsyncTask subclass?
● Implement publishProgress().
Call publishProgress().
● Implement publishProgress().
Call onProgressUpdate().
● Implement onProgressUpdate().
Call publishProgress().
● Implement onProgressUpdate().
Call onProgressUpdate().
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 22
Android Developer Fundamentals Course (V2) – Unit 3
● The layout includes a ProgressBar that sets the appropriate attributes to determine the
range of values.
● The AsyncTask breaks the total sleep time into chunks and updates the progress bar after
each chunk.
● The AsyncTask calls the appropriate method and implements the appropriate callback to
update the progress bar.
● The AsyncTask needs to know which views to update. Depending on whether the AsyncTask
is implemented as an inner class or not, the views can either be passed to the constructor of
the AsyncTask or defined as member variables on the Activity.
Introduction
In this practical you use an AsyncTask to start a background task that gets data from the internet
using a simple REST API. You use the Google APIs Explorer to query the Books API, implement this
query in a worker thread using an AsyncTask, and display the result in your UI.
Then you reimplement the same background task using AsyncTaskLoader, which is a more
efficient way to update your UI.
● Create an activity.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 23
Android Developer Fundamentals Course (V2) – Unit 3
What you'll do
● Use the Google APIs Explorer to learn about the Books API.
● Create the "Who Wrote It?" app, which queries the Books API using a worker thread and
displays the result in the UI.
● Modify the "Who Wrote it?" app to use an AsyncTaskLoader instead of an AsyncTask.
App overview
You will build an app that contains an EditText and a Button.
● The user enters the name of the book in the EditText and taps the button.
● The button executes an AsyncTask that queries the Google Books API to find the author
and title of the book the user is looking for.
● The results are retrieved and displayed in a TextView below the button.
Once the app is working, you modify the app to use AsyncTaskLoader instead of the AsyncTask
class.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 24
Android Developer Fundamentals Course (V2) – Unit 3
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 25
Android Developer Fundamentals Course (V2) – Unit 3
3. Find books.volumes.list and click that function name. To find within a page, you can press
Control+F (Command+F on Mac).
You should see a webpage that lists the parameters of the Books API function that performs
book searches.
4. In the q field, enter a book name or a partial book name, for example "Romeo". The q
parameter is the only required field.
5. In the maxResults field, enter 10 to limit the results to the top 10 matching books.
6. In the printType field, enter books to limit the results to books that are in print.
7. Make sure that the Authorize requests using OAuth 2.0 switch at the top of the form is off.
The HTPP request is a uniform resource identifier (URI). A URI is a string that identifies a resource,
and a URL is a certain type of URI that identifies a web resource. For the Books API, the request is a
URL. The search parameters that you entered into the form follow the ? in the URL.
Notice the API key field at the end of the URL. For security reasons, when you access a public API,
you must obtain an API key and include it in your request. The Books API doesn't require an API key,
so you can leave out that portion of the request URI in your app.
The response is made up of name/value pairs that are separated by commas. For example, "kind":
"books#volumes" is a name/value pair, where "kind" is the name and "books#volumes" is the
value. This is the JSON format.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 26
Android Developer Fundamentals Course (V2) – Unit 3
1. Find the value for the "title" name for one book. Notice that this result contains a single
value.
2. Find the value for the "authors" name for one book. Notice that this result is an array that
can contain more than one value.
The book search includes all the books that contain the search string, with multiple objects to
represent each book. In this practical, you only return the title and authors of the first item in the
response.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 27
Android Developer Fundamentals Course (V2) – Unit 3
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 28
Android Developer Fundamentals Course (V2) – Unit 3
1. The onClick attribute for the button will be highlighted in yellow, because the
searchBooks() method is not yet implemented in MainActivity. To create the method
stub in MainActivity, place your cursor in the highlighted text, press Alt+Enter
(Option+Enter on a Mac) and choose Create 'searchBooks(View) in 'MainActivity'.
<TextView
android:id="@+id/instructions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/instructions"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<EditText
android:id="@+id/bookInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/input_hint"
android:inputType="text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/instructions"/>
<Button
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 29
Android Developer Fundamentals Course (V2) – Unit 3
android:id="@+id/searchButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:onClick="searchBooks"
android:text="@string/button_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bookInput"/>
<TextView
android:id="@+id/titleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance=
"@style/TextAppearance.AppCompat.Headline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/searchButton" />
<TextView
android:id="@+id/authorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance=
"@style/TextAppearance.AppCompat.Headline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/titleText"/>
</android.support.constraint.ConstraintLayout>
1. In MainActivity.java, create member variables for the EditText, the author TextView,
and the title TextView.
private EditText mBookInput;
private TextView mTitleText;
private TextView mAuthorText;
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 30
Android Developer Fundamentals Course (V2) – Unit 3
mBookInput = (EditText)findViewById(R.id.bookInput);
mTitleText = (TextView)findViewById(R.id.titleText);
mAuthorText = (TextView)findViewById(R.id.authorText);
}
3. In the searchBooks() method, get the text from the EditText view. Convert the text to a
String, and assign it to a variable.
public void searchBooks(View view) {
// Get the search string from the input field.
String queryString = mBookInput.getText().toString();
}
Network connectivity can be be sluggish, which can make your app erratic or slow. For this reason,
don't make network connections on the UI thread. If you attempt a network connection on the UI
thread, the Android runtime might raise a NetworkOnMainThreadException to warn you that it’s a
bad idea.
Instead, use a subclass of AsyncTask to make network connections. An AsyncTask requires three
type parameters: an input-parameter type, a progress-indicator type, and a result type.
1. Create a Java class in your app called FetchBook, that extends AsyncTask. The generic type
parameters for the class will be <String, Void, String>. (String because the query is a
string, Void because there is no progress indicator, and String because the JSON response
is a string.)
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 31
Android Developer Fundamentals Course (V2) – Unit 3
2. Implement the required method, doInBackground(). To do this, place your cursor on the
red underlined text, press Alt+Enter (Option+Enter on a Mac) and select Implement
methods. Choose doInBackground() and click OK.
Make sure the parameters and return types are correct. (The method takes a variable list of String
objects and returns a String.)
@Override
protected String doInBackground(String... strings) {
return null;
}
3. Select Code > Override methods, or press Ctrl+O. Select the onPostExecute() method to
insert the method definition into the class. The onPostExecute() method takes a String as
a parameter and returns void.
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
4. To display the results in the TextView objects in MainActivity, you must have access to
those text views inside the AsyncTask. Create WeakReference member variables for
references to the two text views that show the results.
private WeakReference<TextView> mTitleText;
private WeakReference<TextView> mAuthorText;
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 32
Android Developer Fundamentals Course (V2) – Unit 3
Note: As you learned in the previous practical, you use WeakReference objects for these text
views (rather than actual TextView objects) to avoid leaking context from the Activity. The
weak references prevent memory leaks by allowing the object held by that reference to be
garbage-collected if necessary.
5. Create a constructor for the FetchBook class that includes the TextView views from
MainActivity, and initialize the member variables in that constructor.
@Override
protected String doInBackground(String... strings) {
return null;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
}
}
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 33
Android Developer Fundamentals Course (V2) – Unit 3
In this task, you write the code for connecting to the internet in a helper class called NetworkUtils.
1. Create a new Java class in your app called NetworkUtils. The NetworkUtils class does not
extend from any other class.
2. For logging, create a LOG_TAG variable with the name of the class:
3. Create a static method named getBookInfo(). The getBookInfo() method takes the
search term as a String parameter and returns the JSON String response from the API you
examined earlier.
static String getBookInfo(String queryString){
4. Create the following local variables in the getBookInfo() method. You will need these
variables for connecting to the internet, reading the incoming data, and holding the
response string.
HttpURLConnection urlConnection = null;
BufferedReader reader = null;
String bookJSONString = null;
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 34
Android Developer Fundamentals Course (V2) – Unit 3
6. Add a skeleton try/catch/finally block in getBookInfo(), after the local variables and
before the return statement.
In the try block, you'll build the URI and issue the query. In the catch block, you'll handle problems
with the request. In the finally block, you'll close the network connection after you finish receiving
the JSON data.
try {
//...
} catch (IOException e) {
e.printStackTrace();
} finally {
//...
}
7. Create the following member constants at the top of the the NetworkUtils class, below the
LOG_TAG constant:
// Base URL for Books API.
private static final String BOOK_BASE_URL =
"https://www.googleapis.com/books/v1/volumes?";
// Parameter for the search string.
private static final String QUERY_PARAM = "q";
// Parameter that limits search results.
private static final String MAX_RESULTS = "maxResults";
// Parameter to filter by print type.
private static final String PRINT_TYPE = "printType";
As you saw in the request on the Books API web page, all of the requests begin with the same URI.
To specify the type of resource, append query parameters to that base URI. It is common practice to
separate all of these query parameters into constants, and combine them using an Uri.Builder so
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 35
Android Developer Fundamentals Course (V2) – Unit 3
they can be reused for different URIs. The Uri class has a convenient method, Uri.buildUpon(),
that returns a URI.Builder that you can use.
For this app, you limit the number and type of results returned to increase the query speed. To
restrict the query, you will only look for books that are printed.
8. In the getBookInfo() method, build your request URI in the try block:
Uri builtURI = Uri.parse(BOOK_BASE_URL).buildUpon()
.appendQueryParameter(QUERY_PARAM, queryString)
.appendQueryParameter(MAX_RESULTS, "10")
.appendQueryParameter(PRINT_TYPE, "books")
.build();
9. Also inside the try block, convert your URI to a URL object:
1. In the try block of the getBookInfo() method, open the URL connection and make the
request:
urlConnection = (HttpURLConnection) requestURL.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 36
Android Developer Fundamentals Course (V2) – Unit 3
1. Also inside the try block, set up the response from the connection using an InputStream, a
BufferedReader and a StringBuilder.
// Get the InputStream.
InputStream inputStream = urlConnection.getInputStream();
1. Read the input line-by-line into the string while there is still input:
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
Note: The while loop adds the incoming line to the builder string in two steps: one step for the
line of response data, and one step to add the new line character ("\n").
The new line does not affect JSON parsing of the response, but it makes it a lot easier to debug the
response when you view it in the log.
2. At the end of the input, check the string to see if there is existing response content. Return
null if the response is empty.
if (builder.length() == 0) {
// Stream was empty. No point in parsing.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 37
Android Developer Fundamentals Course (V2) – Unit 3
return null;
}
bookJSONString = builder.toString();
4. In the finally block, close both the connection and the BufferedReader:
finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Note: Each time the connection fails for any reason, this code returns null. This means that the
onPostExecute() in the FetchBook class has to check its input parameter for a null string and
let the user know about the failure.
This error handling strategy is simplistic, because the user has no idea why the connection failed.
A better solution for a production app is to handle each point of failure differently so that the user
gets helpful feedback.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 38
Android Developer Fundamentals Course (V2) – Unit 3
5. Just before the final return, print the value of the bookJSONString variable to the log.
Log.d(LOG_TAG, bookJSONString);
1. In MainActivity, add this line to the end of the searchBooks() method to launch the
background task with the execute() method and the query string.
1. Run your app and execute a search. Your app will crash. In Android Studio, click Logcat to
view the logs and see what is causing the error. You should see the following line:
This error indicates that you have not included the permission to access the internet in your Android
manifest. Connecting to the internet introduces security concerns, which is why apps do not have
connectivity by default. In the next task you add internet permissions to the manifest.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 39
Android Developer Fundamentals Course (V2) – Unit 3
3. Build and run your app again. In Android Studio, click Logcat to view the log. Note that this
time, the query runs correctly and the JSON string result is printed to the log.
There is a chance that the doInBackground() method won't return the expected JSON string. For
example, the try/catch might fail and throw an exception, the network might time out, or other
unhandled errors might occur. In those cases, the JSON parsing will fail and will throw an exception.
To handle this case, do the JSON parsing in a try/catch block, and handle the case where incorrect
or incomplete data is returned.
1. In the FetchBook class, in the onPostExecute() method, add a try/catch block below the
call to super.
try {
//...
} catch (JSONException e) {
e.printStackTrace();
}
2. Inside the try block, use the classes JSONObject and JSONArray to obtain the JSON array of
items from the result string.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 40
Android Developer Fundamentals Course (V2) – Unit 3
4. Iterate through the itemsArray array, checking each book for title and author information.
With each loop, test to see if both an author and a title are found, and if so, exit the loop.
This way, only entries with both a title and author will be displayed.
while (i < itemsArray.length() &&
(authors == null && title == null)) {
// Get the current item information.
JSONObject book = itemsArray.getJSONObject(i);
JSONObject volumeInfo = book.getJSONObject("volumeInfo");
// Try to get the author and title from the current item,
// catch if either field is empty and move on.
try {
title = volumeInfo.getString("title");
authors = volumeInfo.getString("authors");
} catch (Exception e) {
e.printStackTrace();
}
Note: The loop ends at the first match in the response. More responses might be available, but
this app only displays the first one.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 41
Android Developer Fundamentals Course (V2) – Unit 3
5. If a matching response is found, update the UI with that response. Because the references to
the TextView objects are WeakReference objects, you have to dereference them using the
get() method.
if (title != null && authors != null) {
mTitleText.get().setText(title);
mAuthorText.get().setText(authors);
}
6. If the loop has stopped and the result has no items with both a valid author and a valid title,
set the title TextView to a "no results" string resource and clear the author TextView.
} else {
mTitleText.get().setText(R.string.no_results);
mAuthorText.get().setText("");
}
7. In the catch block, print the error to the log. Set the title TextView to the "no results" string
resource, and clear the author TextView.
} catch (Exception e) {
// If onPostExecute does not receive a proper JSON string,
// update the UI to show failed results.
mTitleText.get().setText(R.string.no_results);
mAuthorText.get().setText("");
e.printStackTrace();
}
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 42
Android Developer Fundamentals Course (V2) – Unit 3
Solution code:
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
try {
// Convert the response into a JSON object.
JSONObject jsonObject = new JSONObject(s);
// Get the JSONArray of book items.
JSONArray itemsArray = jsonObject.getJSONArray("items");
// Try to get the author and title from the current item,
// catch if either field is empty and move on.
try {
title = volumeInfo.getString("title");
authors = volumeInfo.getString("authors");
} catch (Exception e) {
e.printStackTrace();
}
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 43
Android Developer Fundamentals Course (V2) – Unit 3
} catch (Exception e) {
// If onPostExecute does not receive a proper JSON string,
// update the UI to show failed results.
mTitleText.get().setText(R.string.no_results);
mAuthorText.get().setText("");
}
}
● When the user clicks Search Books, the keyboard does not disappear. The user has no
indication that the query is being executed.
● If there is no network connection, or if the search field is empty, the app still tries to query
the API and fails without properly updating the UI.
● If you rotate the screen during a query, the AsyncTask becomes disconnected from the
Activity, and it is not able to update the UI with results.
You fix the first two of these issues in this section, and the last issue in Task 4.
One solution is to programmatically hide the keyboard and update one of the result text views to
read "Loading…" while the query is performed.
1. In MainActivity, add the following code to the searchBooks() method, after the
queryString definition. The code hides the keyboard when the user taps the button.
InputMethodManager inputManager = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 44
Android Developer Fundamentals Course (V2) – Unit 3
if (inputManager != null ) {
inputManager.hideSoftInputFromWindow(view.getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
}
1. Just beneath the call to execute the FetchBook task, add code to change the title TextView
to a loading message and clear the author TextView.
new FetchBook(mTitleText, mAuthorText).execute(queryString);
mAuthorText.setText("");
mTitleText.setText(R.string.loading);
3.2 Manage the network state and the empty search field case
Whenever your app uses the network, it needs to handle the possibility that a network connection is
unavailable. Before attempting to connect to the network, your app should check the state of the
network connection. In addition, it should not try to query the Books API if the user has not entered
a query string.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 45
Android Developer Fundamentals Course (V2) – Unit 3
2. Add a test around the call to the FetchBook task and TextView updates to ensure that the
network connection exists, that the network is connected, and that a query string is
available.
if (networkInfo != null && networkInfo.isConnected()
&& queryString.length() != 0) {
new FetchBook(mTitleText, mAuthorText).execute(queryString);
mAuthorText.setText("");
mTitleText.setText(R.string.loading);
}
3. Add an else block to that test. In the else block, update the UI with a no_search_term
error message if there is no term to search for, and a no_network error message otherwise.
} else {
if (queryString.length() == 0) {
mAuthorText.setText("");
mTitleText.setText(R.string.no_search_term);
} else {
mAuthorText.setText("");
mTitleText.setText(R.string.no_network);
}
}
Solution code:
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 46
Android Developer Fundamentals Course (V2) – Unit 3
inputManager.hideSoftInputFromWindow(view.getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
}
Solution code
The solution code for this practical up to this point is in the Android Studio project WhoWroteIt.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 47
Android Developer Fundamentals Course (V2) – Unit 3
AsyncTaskLoader loads data in the background and reassociates background tasks with the
Activity, even after a configuration change. With an AsyncTaskLoader, if you rotate the device
while the task is running, the results are still displayed correctly in the Activity.
Why use an AsyncTask if an AsyncTaskLoader is much more useful? The answer is that it depends
on the situation. If the background task is likely to finish before any configuration changes occur,
and it's not crucial for the task to update the UI, an AsyncTask may be sufficient. The
AsyncTaskLoader class actually uses an AsyncTask behind the scenes to work its magic.
Note: The AsyncTaskLoader class is part of the Android platform's Loader API, which is a
framework to manage loading data into your app in the background. Loaders were deprecated in
Android P (API 28) in favor of ViewModels and LiveData.
The AsyncTaskLoader class is still available, but for full backward-compatibility, make sure to use
the AsyncTaskLoader and other related classes from the Android Support Library.
In this exercise you learn how to use AsyncTaskLoader instead of AsyncTask to run your Books
API query.
Make sure to import the AsyncTaskLoader class from the v4 Support Library.
3. Implement the required loadInBackground() method. Notice the similarity between this
method and the initial doInBackground() method from AsyncTask.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 48
Android Developer Fundamentals Course (V2) – Unit 3
@Nullable
@Override
public String loadInBackground() {
return null;
}
4. Create the constructor for the BookLoader class. With your text cursor on the class
declaration line, press Alt+Enter (Option+Enter on a Mac) and select Create constructor
matching super. This creates a constructor with the Context as a parameter.
public BookLoader(@NonNull Context context) {
super(context);
}
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 49
Android Developer Fundamentals Course (V2) – Unit 3
3. Create a member variable called mQueryString to hold the string for the Books API query.
Modify the constructor to take a String as an argument and assign it to the mQueryString
variable.
private String mQueryString;
4. In the loadInBackground() method, replace the return statement with the following code,
which calls the NetworkUtils.getBookInfo() method with the query string and returns the
result:
return NetworkUtils.getBookInfo(mQueryString);
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 50
Android Developer Fundamentals Course (V2) – Unit 3
2. Implement all the required callback methods from the interface. Thi includes
onCreateLoader(), onLoadFinished(), and onLoaderReset(). Place your cursor on the
class signature line and press Alt+Enter (Option+Enter on a Mac). Make sure that all the
methods are selected and click OK.
@NonNull
@Override
public Loader<String> onCreateLoader(int id, @Nullable Bundle args) {
return null;
}
@Override
public void onLoadFinished(@NonNull Loader<String> loader, String data) {
@Override
public void onLoaderReset(@NonNull Loader<String> loader) {
For this app, you only implement the first two methods. Leave onLoaderReset() empty.
3. The searchBooks() method is the onClick method for the button. In searchBooks(),
replace the call to execute the FetchBook task with a call to restartLoader(). Pass in the
query string that you got from the EditText in the loader's Bundle object:
Bundle queryBundle = new Bundle();
queryBundle.putString("queryString", queryString);
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 51
Android Developer Fundamentals Course (V2) – Unit 3
The restartLoader() method is defined by the LoaderManager, which manages all the loaders
used in an activity or fragment. Each activity has exactly one LoaderManager instance that is
responsible for the lifecycle of the Loaders that the activity manages.
● A loader id, which is useful if you implement more than one loader in your activity.
● An arguments Bundle for any data that the loader needs.
● The instance of LoaderCallbacks that you implemented in your activity. If you want the
loader to deliver the results to the MainActivity, specify this as the third argument.
if (args != null) {
queryString = args.getString("queryString");
}
1. Copy the code from onPostExecute() in your FetchBook class to onLoadFinished() in your
MainActivity. Remove the call to super.onPostExecute(). This is the code that parses the
JSON result for a match with the query string.
2. Remove all the calls to get() for each of the TextView objects. Because updating the UI
happens in the Activity itself, you no longer need weak references to the original views.
3. Replace the argument to the JSONObject constructor (the variable s) with the parameter
data.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 52
Android Developer Fundamentals Course (V2) – Unit 3
4. Run your app. You should have the same functionality as before, but now in a loader!
However, when you rotate the device, the view data is lost. That's because when the activity
is created (or recreated), the activity doesn't know that a loader is running. To reconnect to
the loader, you need an initLoader() method in the onCreate() of MainActivity.
5. Add the following code in onCreate() to reconnect to the loader, if the loader already exists:
if(getSupportLoaderManager().getLoader(0)!=null){
getSupportLoaderManager().initLoader(0,null,this);
}
If the loader exists, initialize it. You only want to reassociate the loader to the activity if a query has
already been executed. In the initial state of the app, no data is loaded, so there is no data to
preserve.
1. Run your app again and rotate the device. The loader manager now holds onto your data
across device-configuration changes!
2. Remove the FetchBook class, because it is no longer used.
Solution code
The solution code for this task is in the Android Studio project WhoWroteItLoader.
Coding challenge
Note: All coding challenges are optional and are not a prerequisite for later lessons.
Challenge 1: Explore the the Books API in greater detail and find a search parameter that restricts
the results to books that are downloadable in the EPUB format. Add the parameter to your request
and view the results.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 53
Android Developer Fundamentals Course (V2) – Unit 3
Summary
● Tasks that connect to the network should not be executed on the UI thread. The Android
runtime usually raises an exception if you attempt network connectivity or file access on the
UI thread.
● Use the Books Search API to access Google Books programmatically. An API request to
Google Books is in the form of a URL, and the response is a JSON string.
● Use the Google APIs Explorer to explore Google APIs interactively.
● Use getText() to retrieve text from an EditText view. To convert the text into a simple
string, use toString().
● The Uri.buildUpon() method returns a URI.Builder that you can use to construct URI
strings.
● To connect to the internet, you must configure network permission in the Android manifest
file:
<usespermission android:name="android.permission.INTERNET" />
The AsyncTask class lets you run tasks in the background instead of on the UI thread:
● To use an AsyncTask, you have to subclass it. The subclass overrides the
doInBackground(Params...) method. Usually the subclass also overrides the
onPostExecute(Result) method.
● To start an AsyncTask, use execute().
● An AsyncTask can't update the UI if the activity that the AsyncTask is controlling stops, for
example because of a device-configuration change.
1. onPreExecute() runs on the UI thread before the task is executed. This step is normally
used to set up the task, for instance by showing a progress bar in the UI.
2. doInBackground(Params...) runs on the background thread immediately after
onPreExecute() finishes. This step performs background computations that can take a
long time.
3. onProgressUpdate(Progress...) runs on the UI thread after you a call
publishProgress(Progress...).
4. onPostExecute(Result) runs on the UI thread after the background computation is
finished. The result of the computation is passed to onPostExecute().
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 54
Android Developer Fundamentals Course (V2) – Unit 3
Related concepts
The related concept documentation is in 7.2: Internet connection.
Learn more
Android developer documentation:
Homework
Build and run an app
Create an app that retrieves and displays the contents of a web page that's located at a URL. The
app displays the following:
● A field such as a menu or spinner that allows the user to choose the protocol (HTTP or
HTTPS)
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 55
Android Developer Fundamentals Course (V2) – Unit 3
● A scrolling display of the source code of the web page at the URL
Use an AsyncTaskLoader to retrieve the source code of the web page at the URL. You need to
implement a subclass of AsyncTaskLoader.
If connection to the internet is not available when the user taps the button, the app must show the
user an appropriate response. For example, the app might display a message such as "Check your
internet connection and try again."
The display must contain a TextView in a ScrollView that displays the source code, but the exact
appearance of the interface is up to you. Your screen can look different from the screenshots below.
You can use a pop-up menu, spinner, or checkboxes to allow the user to select HTTP or HTTPS.
The image on the left shows the starting screen, with a pop-up menu for the protocol. The image on
the right shows an example of the results of retrieving the page source for given URL.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 56
Android Developer Fundamentals Course (V2) – Unit 3
Question 1
● android.permission.CONNECTIVITY
● android.permission.INTERNET
● It doesn't need any special permissions, because all Android apps are allowed to
connect to the internet.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 57
Android Developer Fundamentals Course (V2) – Unit 3
Question 2
In the manifest:
In the code:
● Wrap the code to connect to the internet in a try/catch block, and catch
NO_NETWORK errors.
● Present a dialog to the user reminding them to make sure that internet connectivity
is available before they attempt to connect to the internet.
Question 3
Where do you implement the loader callback method that's triggered when the loader finishes
executing its task?
● In the Activity that displays the results of the task. The Activity must implement
LoaderManager.LoaderCallbacks.
Question 4
When the user rotates the device, how do AsyncTask and AsyncTaskLoader behave differently if
they are in the process of running a task in the background?
● A running AsyncTask becomes disconnected from the activity, but keeps running. A
running AsyncTaskLoader becomes disconnected from the activity and stops
running, preserving system resources.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 58
Android Developer Fundamentals Course (V2) – Unit 3
● A running AsyncTask becomes disconnected from the activity and stops running,
preserving system resources. A running AsyncTaskLoader automatically restarts
execution of its task from the beginning. The activity displays the results.
● A running AsyncTask becomes disconnected from the activity, but keeps running. A
running AsyncTaskLoader automatically reconnects to the activity after the device
rotation. The activity displays the results.
Question 5
How do you initialize an AsyncTaskLoader to perform steps, such as initializing variables, that must
be done before the loader starts performing its background task?
● Perform initialization tasks for the loader at the start of loadInBackgroud() in the
Loader.
Question 6
● Combines the protocol and the web page to create a valid URL that the app uses to connect
to the internet.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 59
Android Developer Fundamentals Course (V2) – Unit 3
● Displays the results of retrieving the source of the web page in a TextView in a ScrollView.
(It's OK to do it in the same activity, or to start a new activity.)
Introduction
Broadcasts are messages that the Android system and Android apps send when events occur that
might affect the functionality of other apps or app components. For example, the Android system
sends a system broadcast when the device boots up, or when headphones are connected or
disconnected. If the wired headset is unplugged, you might like your media app to pause the music.
Your Android app can also broadcast events, for example when new data is downloaded that might
interest some other app. Events that your app delivers are called custom broadcasts.
In general, you can use broadcasts as a messaging system across apps and outside of the normal
user flow.
A broadcast is received by any app or app component that has a broadcast receiver registered for
that action. BroadcastReceiver is the base class for code that receives broadcast intents. To learn
more about broadcast receivers, see the Broadcasts overview and the Intent reference.
Note: While the Intent class is used to send and receive broadcasts, the Intent broadcast
mechanism is completely separate from intents that are used to start activities.
In this practical, you create an app that responds to a change in the charging state of the device. To
do this, your app receives and responds to a system broadcast, and it also sends and receives a
custom broadcast.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 60
Android Developer Fundamentals Course (V2) – Unit 3
What you'll do
● Subclass a BroadcastReceiver to show a toast when a broadcast is received.
App overview
The PowerReceiver app will register a BroadcastReceiver that displays a toast message when the
device is connected or disconnected from power. The app will also send and receive a custom
broadcast to display a different toast message.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 61
Android Developer Fundamentals Course (V2) – Unit 3
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 62
Android Developer Fundamentals Course (V2) – Unit 3
Apps can register to receive specific broadcasts. When the system sends a broadcast, it routes the
broadcast to apps that have registered to receive that particular type of broadcast.
● To register a receiver dynamically, use the app context or activity context. The receiver
receives broadcasts as long as the registering context is valid, meaning as long as the
corresponding app or activity is running. Dynamic receivers are also called context-registered
receivers.
For this app, you're interested in two system broadcasts, ACTION_POWER_CONNECTED and
ACTION_POWER_DISCONNECTED. The Android system sends these broadcasts when the device's
power is connected or disconnected.
Starting from Android 8.0 (API level 26 and higher), you can't use static receivers to receive most
Android system broadcasts, with some exceptions. So for this task, you use dynamic receivers:
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 63
Android Developer Fundamentals Course (V2) – Unit 3
Intent filters specify the types of intents a component can receive. They are used in filtering out the
intents based on Intent values like action and category.
When the system receives an Intent as a broadcast, it searches the broadcast receivers
based on the action value specified in the IntentFilter object.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 64
Android Developer Fundamentals Course (V2) – Unit 3
@Override
public void onReceive(Context context, Intent intent) {
String intentAction = intent.getAction();
}
3. Create a switch statement with the intentAction string. (Before using intentAction, do
a null check on it.) Display a different toast message for each action your receiver is
registered for.
if (intentAction != null) {
String toastMessage = "unknown intent action";
switch (intentAction){
case Intent.ACTION_POWER_CONNECTED:
toastMessage = "Power connected!";
break;
case Intent.ACTION_POWER_DISCONNECTED:
toastMessage = "Power disconnected!";
break;
}
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 65
Android Developer Fundamentals Course (V2) – Unit 3
4. After the switch statement, add code to display a toast message for a short time:
5. Run your app. After the app is running, connect or disconnect your device's power supply. A
Toast is displayed each time you connect or disconnect the power supply, as long as your
Activity is running.
Note: If you're using an emulator, toggle the power connection state by selecting the ellipses icon
for the menu. Select Battery in the left bar, then use the Charger connection setting.
Android provides three ways for your app to send custom broadcasts:
● Normal broadcasts are asynchronous. Receivers of normal broadcasts run in an undefined
order, often at the same time. To send a normal broadcast, create a broadcast intent and
pass it to sendBroadcast(Intent).
● Local broadcasts are sent to receivers that are in the same app as the sender. To send a local
broadcast, create a broadcast intent and pass it to
LocalBroadcastManager.sendBroadcast.
● Ordered broadcasts are delivered to one receiver at a time. As each receiver executes, it can
propagate a result to the next receiver, or it can cancel the broadcast so that the broadcast is
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 66
Android Developer Fundamentals Course (V2) – Unit 3
not passed to other receivers. To send an ordered broadcast, create a broadcast intent and
pass it to sendOrderedBroadcast(Intent, String).
This practical doesn't cover ordered broadcasts, but for more information about them, see
Sending broadcasts.
The broadcast message is wrapped in an Intent object. The Intent action string must provide the
app's Java package name syntax and uniquely identify the broadcast event.
For a custom broadcast, you define your own Intent action (a unique string). You can create
Intent objects with custom actions and broadcast them yourself from your app using one of the
methods above. The broadcasts are received by apps that have a BroadcastReceiver registered
for that action.
In this task, you add a button to your activity that sends a local broadcast intent. Your receiver
registers the broadcast intent and displays the result in a toast message.
One of the simplest ways to get your app’s package name is to use
BuildConfig.APPLICATION_ID, which returns the applicationId property’s value from your
module-level build.gradle file.
1. Create a constant member variable in both your MainActivity and your CustomReceiver
class. You'll use this variable as the broadcast Intent action.
private static final String ACTION_CUSTOM_BROADCAST =
BuildConfig.APPLICATION_ID + ".ACTION_CUSTOM_BROADCAST";
Important: Although intents are used both for sending broadcasts and starting activities with
startActivity(Intent), these actions are completely unrelated. Broadcast receivers can't see
or capture an Intent that's used to start an activity. Likewise, when you broadcast an Intent,
you can't use that Intent to find or start an activity.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 67
Android Developer Fundamentals Course (V2) – Unit 3
<Button
android:id = "@+id/sendBroadcast"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send Custom Broadcast"
android:onClick="sendCustomBroadcast"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
The sendCustomBroadcast() method will be the click-event handler for the button. To create a stub
for sendCustomBroadcast() in Android Studio:
1. Click the yellow highlighted sendCustomBroadcast method name. A red light bulb appears
on the left.
2. Click the red light bulb and select Create 'sendCustomBroadcast(View)' in 'MainActivity'.
By keeping broadcasts local, you ensure that your app data isn't shared with other Android apps.
Local broadcasts keep your information more secure and maintain system efficiency.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 68
Android Developer Fundamentals Course (V2) – Unit 3
1. Create a new Intent, with your custom action string as the argument.
Intent customBroadcastIntent = new Intent(ACTION_CUSTOM_BROADCAST);
2. After the custom Intent declaration, send the broadcast using the
LocalBroadcastManager class:
LocalBroadcastManager.getInstance(this).sendBroadcast(customBroadcastIntent)
;
If you register a broadcast receiver dynamically, you must unregister the receiver when it is no
longer needed. In your app, the receiver only needs to respond to the custom broadcast when the
app is running, so you can register the action in onCreate() and unregister it in onDestroy().
2. In MainActivity.java, inside the onDestroy() method, unregister your receiver from the
LocalBroadcastManager:
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 69
Android Developer Fundamentals Course (V2) – Unit 3
LocalBroadcastManager.getInstance(this)
.unregisterReceiver(mReceiver);
That's it! Your app delivers a custom broadcast and is able to receive both system and custom
broadcasts.
Solution code
Android Studio project: PowerReceiver
Coding challenge
Note: All coding challenges are optional and are not prerequisites for later lessons.
Challenge: If you were developing a music-player app, your app might need to play or pause music
when the user connected or disconnected a wired headset. To implement this functionality, you
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 70
Android Developer Fundamentals Course (V2) – Unit 3
need a broadcast receiver that responds to wired headset events. Implement a broadcast receiver
that shows a toast message when a wired headset is connected or disconnected.
Hint: You need to register for the ACTION_HEADSET_PLUG action. Because this is a system
broadcast action, you can't register for it statically. Instead, register your receiver dynamically by
using the context with Context.registerReceiver():
IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
this.registerReceiver(mReceiver, filter);
You must also unregister the receiver when you no longer need it:
unregisterReceiver(mReceiver);
Summary
● Broadcast receivers are fundamental components of an Android app.
● Broadcast receivers can receive broadcasts sent by the system or by apps.
● The Intent used in the broadcast mechanism is completely different from intents used to
start activities.
● To process the incoming Intent associated with a broadcast, you subclass the
BroadcastReceiver class and implement onReceive().
● You can register a broadcast receiver in the Android manifest file or programmatically.
● Local broadcasts are private to your app. To register and send local broadcasts, use
LocalBroadcastManager. Local broadcasts don't involve interprocess communication, which
makes them efficient. Using local broadcasts can also protect your app against some security
issues, because data stays inside your app.
● To create unique Intent action names for broadcasts, a common practice is to prepend the
action name with your package name.
● If your app targets API level 26 or higher, you cannot use the manifest to declare a receiver
for most implicit broadcasts. (Implicit broadcasts, which include most system broadcasts, are
broadcasts that don't target your app.) A few implicit broadcasts are exceptions. However,
you can use dynamic receivers to receive all broadcasts.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 71
Android Developer Fundamentals Course (V2) – Unit 3
Related concept
The related concept documentation is in 7.3: Broadcasts.
Learn more
Android developer documentation:
Homework
Update an app
Extend the PowerReceiver app that you created in the practical.
1. Send extra data to your local custom broadcast receiver. To do this, generate a random
integer between 1 and 20. Add the number to the extra field of the Intent before sending
the local custom broadcast.
2. In your receiver, extract the integer data from the Intent. In the toast message, display the
square of the random number.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 72
Android Developer Fundamentals Course (V2) – Unit 3
Question 1
● A message that your app sends and receives when an event of interest occurs in the app.
● A message that is sent from an app to a different component of the same app.
● A message that the Android system sends when a system event occurs.
● A message that the Android system receives when an event of interest occurs in your app.
Question 2
Which pair of methods do you use to register and unregister your broadcast receiver dynamically?
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 73
Android Developer Fundamentals Course (V2) – Unit 3
Question 3
● Broadcast receivers can't see or capture the intents used to start an activity.
● Using a broadcast intent, you can't find or start an activity.
● You can use a broadcast intent to start an activity.
● You can receive the intent used to start activity in your broadcast receiver.
Question 4
Which class is used to mitigate the security risks of broadcast receivers when the broadcasts are not
cross-application (that is, when broadcasts are sent and received by the same app)?
● SecureBroadcast
● LocalBroadcastManager
● OrderedBroadcast
● SecureBroadcastManager
● The app generates a random integer and sends the integer as an Intent extra in the
LocalBroadcast.
● In the receiver's onReceive() method, the random integer data is extracted from the
Intent, and the integer's square is displayed in a toast message.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 74
Android Developer Fundamentals Course (V2) – Unit 3
Introduction
Sometimes you want your app to show information to users even when the app isn't running in the
foreground. For example, you might want to let users know that new content is available, or that
their favorite sports team just scored a goal in a game. The Android notification framework provides
a way for your app to notify users even when the app is not in the foreground.
A notification is a message that your app displays to the user outside of your app's normal UI.
Notifications appear as icons in the device's notification area, which is in the status bar. To see the
details of a notification, the user opens the notification drawer, for example by swiping down on the
status bar. The notification area and the notification drawer are system-controlled areas that the
user can view at any time.
On devices running Android 8.0 and higher, when your app has a new notification to show to the
user, your app icon automatically shows a badge. (Badges are also called notification dots). When the
user long-presses the app icon, the notification appears above the app icon, as shown in the
screenshot below.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 75
Android Developer Fundamentals Course (V2) – Unit 3
In this practical you create an app that triggers a notification when the user taps a button in your
app. The user can update the notification or cancel it.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 76
Android Developer Fundamentals Course (V2) – Unit 3
What you'll do
● Create an app that sends a notification when the user taps a button in the app.
● Update the notification from a button in your app, and from an action button that's inside
the notification.
App overview
Notify Me! is an app that lets the user trigger, update, and cancel a notification using the three
buttons shown in the screenshots below. While you create the app, you'll experiment with
notification styles, actions, and priorities.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 77
Android Developer Fundamentals Course (V2) – Unit 3
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 78
Android Developer Fundamentals Course (V2) – Unit 3
<Button
android:id="@+id/notify"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Notify Me!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
2. In the onCreate() method, initialize the Notify Me! button and create an onClickListener
for it. Call sendNotification() from the onClick method:
button_notify = findViewById(R.id.notify);
button_notify.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sendNotification();
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 79
Android Developer Fundamentals Course (V2) – Unit 3
}
});
On Android-powered devices running Android 8.0 (API level 26) or higher, notification channels that
you create in your app appear as Categories under App notifications in the device Settings app.
For example, in the screenshot below of a device running Android 8.0, the Notify Me! app has one
notification channel, Mascot Notification.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 80
Android Developer Fundamentals Course (V2) – Unit 3
When your app targets Android 8.0 (API level 26), to display notifications to your users you must
implement at least one notification channel. To display notifications on lower-end devices, you're
not required to implement notification channels. However, it's good practice to always do the
following:
If your targetSdkVersion is set to 25 or lower, when your app runs on Android 8.0 (API level 26) or
higher, it behaves the same as it would on devices running Android 7.1 (API level 25) or lower.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 81
Android Developer Fundamentals Course (V2) – Unit 3
1. In MainActivity, create a constant for the notification channel ID. Every notification channel
must be associated with an ID that is unique within your package. You use this channel ID
later, to post your notifications.
private static final String PRIMARY_CHANNEL_ID =
"primary_notification_channel";
2. The Android system uses the NotificationManager class to deliver notifications to the
user. In MainActivity.java, create a member variable to store the
NotificationManager object.
private NotificationManager mNotifyManager;
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 82
Android Developer Fundamentals Course (V2) – Unit 3
6. Set the channel name. The name is displayed under notification Categories in the device's
user-visible Settings app.
7. Set the importance to IMPORTANCE_HIGH. (For the complete list of notification importance
constants, see the NotificationManager documentation.)
// Create a NotificationChannel
NotificationChannel notificationChannel = new
NotificationChannel(PRIMARY_CHANNEL_ID,
"Mascot Notification", NotificationManager
.IMPORTANCE_HIGH);
● Icon (required), which you set in your code using the setSmallIcon() method.
● Title (optional), which you set using setContentTitle().
● Detail text (optional), which you set using setContentText().
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 83
Android Developer Fundamentals Course (V2) – Unit 3
4. Rename the resource ic_android and click Next and Finish. This creates drawable files
with different resolutions for different API levels.
1. You need to associate the notification with a notification ID so that your code can update or
cancel the notification in the future. In MainActivity.java, create a constant for the
notification ID:
private static final int NOTIFICATION_ID = 0;
5. Inside the getNotificationBuilder() method, add the title, text, and icon to the builder,
as shown below. At the end, return the Builder object.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 84
Android Developer Fundamentals Course (V2) – Unit 3
Now you can finish the sendNotification() method that sends the notification:
1. Run your app. The Notify Me! button issues a notification, and the icon appears in the status
bar. However, the notification is missing an essential feature: nothing happens when you tap
it. You add that functionality in the next task.
The major difference with an Intent that's used for a notification is that the Intent must be
wrapped in a PendingIntent. The PendingIntent allows the Android notification system to
perform the assigned action on behalf of your code.
In this step you update your app so that when the user taps the notification, your app sends a
content intent that launches the MainActivity. (If the app is open and active, tapping the
notification will not have any effect.)
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 85
Android Developer Fundamentals Course (V2) – Unit 3
By using a PendingIntent to communicate with another app, you are telling that app to
execute some predefined code at some point in the future. It’s like the other app can
perform an action on behalf of your app.
PendingIntent notificationPendingIntent =
PendingIntent.getActivity(this,
NOTIFICATION_ID, notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
Setting auto-cancel to true closes the notification when user taps on it.
1. Run the app. Tap the Notify Me! button to send the notification. Tap the home button. Now
view the notification and tap it. Notice the app opens back at the MainActivity.
2. If you are running the app on a device or emulator with API 26 or higher, press the Home
button and open the app launcher. Notice the badge (the notification dot) on the app icon.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 86
Android Developer Fundamentals Course (V2) – Unit 3
When the user touches and holds the app icon, a popup shows notifications along with the
icon.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 87
Android Developer Fundamentals Course (V2) – Unit 3
If you're running on a device or emulator with API 26 or higher, here's how to view the notification
channel that you created:
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 88
Android Developer Fundamentals Course (V2) – Unit 3
Note: This task is required for devices running Android 7.1 or lower, which is most
Android-powered devices. For devices running Android 8.0 and higher, you use notification
channels to add priority and defaults to notifications, but it's a best practice to provide backward
compatibility and support for lower-end devices.
When the user taps the Notify Me! button in your app, the notification is issued, but the only visual
that the user sees is the icon in the notification bar. To catch the user's attention, set notification
default options.
Priority is an integer value from PRIORITY_MIN (-2) to PRIORITY_MAX (2). Notifications with a higher
priority are sorted above lower priority ones in the notification drawer. HIGH or MAX priority
notifications are delivered as "heads up" notifications, which drop down on top of the user's active
screen. It’s not a good practice to set all your notifications to MAX priority, so use MAX sparingly.
1. Set the sound, vibration, and LED-color pattern for your notification (if the user's device has
an LED indicator) to the default values.
1. To see the changes, quit the app and run it again from Android Studio. If you are unable to
see your changes, uninstall the app and install it again.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 89
Android Developer Fundamentals Course (V2) – Unit 3
Note: The high-priority notification will not drop down in front of the active screen unless both the
priority and the defaults are set. Setting the priority alone is not enough.
<Button
android:id="@+id/notify"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Notify Me!"
app:layout_constraintBottom_toTopOf="@+id/update"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Update Me!"
app:layout_constraintBottom_toTopOf="@+id/cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 90
Android Developer Fundamentals Course (V2) – Unit 3
app:layout_constraintTop_toBottomOf="@+id/notify" />
<Button
android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cancel Me!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/update" />
1. At the end of onCreate() method, initialize the button variables and set their onClick
listeners. If Android Studio throws an error, rebuild your project
button_update = findViewById(R.id.update);
button_update.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Update the notification
}
});
button_cancel = findViewById(R.id.cancel);
button_cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Cancel the notification
}
});
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 91
Android Developer Fundamentals Course (V2) – Unit 3
2. Create methods for updating and canceling the notification. The methods take no
parameters and return void:
public void updateNotification() {}
public void cancelNotification() {}
Updating a notification is more complex than canceling a notification. Android notifications come
with styles that can condense information. For example, the Gmail app uses InboxStyle
notifications if the user has more than one unread message, which condenses the information into a
single notification.
In this example, you update your notification to use BigPictureStyle, which allows you to include
an image in the notification.
1. Download this image to use in your notification and rename it to mascot_1. If you use your
own image, make sure that its aspect ratio is 2:1 and its width is 450 dp or less.
2. Put the mascot_1 image in the res/drawable folder.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 92
Android Developer Fundamentals Course (V2) – Unit 3
notifyBuilder.setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(androidImage)
.setBigContentTitle("Notification Updated!"));
6. Inside updateNotification(), after setting the notification style, build the notification and
call notify() on the NotificationManager. Pass in the same notification ID as before.
mNotifyManager.notify(NOTIFICATION_ID, notifyBuilder.build());
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 93
Android Developer Fundamentals Course (V2) – Unit 3
7. Run your app. Tap the update button and check the notification again—the notification now
has the image and the updated title! To shrink back to the regular notification style, pinch
the extended notification.
You can fix this by enabling and disabling the buttons depending on the state of the notification:
● When the app is first run, the Notify Me! button should be the only button enabled, because
there is no notification yet to update or cancel.
● After a notification is sent, the cancel and update buttons should be enabled, and the
notification button should be disabled, because the notification has been delivered.
● After the notification is updated, the update and notify buttons should be disabled, leaving
only the cancel button enabled.
● If the notification is canceled, the buttons should return to their initial states, with only the
notify button enabled.
To toggle the button state for all the buttons, do the following steps in MainActivity.java:
onCreate():
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 94
Android Developer Fundamentals Course (V2) – Unit 3
sendNotification():
updateNotification():
cancelNotification():
To avoid opening your app, the notification framework lets you embed a notification action button
directly in the notification itself.
In this task, you add an action button to your notification. The action button lets the user update the
notification from within the notification, without opening the app. This Update Notification action
works whether your app is running in the foreground or the background.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 95
Android Developer Fundamentals Course (V2) – Unit 3
public NotificationReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
// Update the notification
}
}
4. In MainActivity.java, create a member variable for your receiver and initialize it using
the default constructor.
private NotificationReceiver mReceiver = new NotificationReceiver();
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 96
Android Developer Fundamentals Course (V2) – Unit 3
registerReceiver(mReceiver,new IntentFilter(ACTION_UPDATE_NOTIFICATION));
@Override
protected void onDestroy() {
unregisterReceiver(mReceiver);
super.onDestroy();
}
Note: It may seem as if the broadcast sent by the notification only concerns your app and should
be delivered with a LocalBroadcastManager. However, using a PendingIntent delegates the
responsibility of delivering the notification to the Android framework. Because the Android
runtime handles the broadcast, you cannot use LocalBroadcastManager.
2. In the Icon Type drop-down list, select Action Bar and Tab Icons.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 97
Android Developer Fundamentals Course (V2) – Unit 3
Starting from Android 7.0, icons are not displayed in notifications. Instead, more room is provided
for the labels themselves. However, notification action icons are still required, and they continue to
be used on older versions of Android and on devices such as Android Wear.
1. At the beginning of the method, create an Intent using the custom update action
ACTION_UPDATE_NOTIFICATION.
2. Use getBroadcast() to get a PendingIntent. To make sure that this pending intent is sent
and used only once, set FLAG_ONE_SHOT.
Intent updateIntent = new Intent(ACTION_UPDATE_NOTIFICATION);
PendingIntent updatePendingIntent = PendingIntent.getBroadcast
(this, NOTIFICATION_ID, updateIntent,
PendingIntent.FLAG_ONE_SHOT);
4. Run your app. Tap the Notify Me! button, then press the Home button. Open the
notification and tap on Update Notification button. The notification is updated.
The user can now update the notification without opening the app!
Solution code
Android Studio project: NotifyMe
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 98
Android Developer Fundamentals Course (V2) – Unit 3
Coding challenge
Note: All coding challenges are optional and are not prerequisites for later lessons.
Enabling and disabling buttons is a common way to ensure that the user does not perform any
actions that aren't supported in the current state of the app. For example, you might disable a Sync
button when no network is available.
In the NotifyMe app, there is one use case in which the state of your buttons does not match the
state of the app: when a user dismisses a notification by swiping it away or clearing the whole
notification drawer. In this case, your app has no way of knowing that the notification was canceled
and that the button state must be changed.
Create another pending intent to let the app know that the user has dismissed the notification, and
toggle the button states accordingly.
Hint: Check out the NotificationCompat.Builder class for a method that delivers an Intent if
the user dismisses the notification.
Summary
A notification is a message that you can display to the user outside of your app's normal UI:
● Notifications provide a way for your app to interact with the user even when the app is not
running.
● When Android issues a notification, the notification appears first as an icon in the
notification area of the device.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 99
Android Developer Fundamentals Course (V2) – Unit 3
● Notifications can also include pending intents, expanded styles, priorities, etc. For more
details, see NotificationCompat.Builder.
Related concept
The related concept documentation is in 8.1: Notifications.
Learn more
Guides:
● Notifications Overview
● Material Design spec for notifications
● Create and Manage Notification Channels
Reference:
● NotificationCompat.Builder
● NotificationCompat.Style
Homework
Build and run an app
Open the solution code for the NotifyMe app. Change the updated notification in the app to use the
InboxStyle expanded layout instead of BigPictureStyle. Use fake string data for each line, and
for the summary text.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 100
Android Developer Fundamentals Course (V2) – Unit 3
Note: The notification might look a little different, depending on the API level of the device.
Question 1
● You use notification channels to display notifications to the user in the device status bar.
● You use notification channels to group multiple notifications so that the user can control the
notifications' behavior.
● Notification channels are available in older devices, those running Android 7.0 Nougat (API
24) and lower.
● Notification channels are not yet available in the Android Support Library package.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 101
Android Developer Fundamentals Course (V2) – Unit 3
Question 2
Which API do you use to show a notification in the notification drawer and in the device's status bar?
● Notification.notify()
● NotificationManager.notify()
● NotificationCompact.notify()
● NotificationCompat.Builder.notify()
Question 3
Question 4
● NotificationCompat.addActionButton()
● NotificationCompat.Builder.addAction()
● Notification.Builder.addActionButton()
● NotificationManager.addAction()
Question 5
Suppose that you create an app that downloads a work of art on the user's device every day. Once
the day's image is available, the app shows a notification to the user, and the user can download the
image or skip the download. What PendingIntent method would you use to start a service to
download the image?
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 102
Android Developer Fundamentals Course (V2) – Unit 3
● Activity.startService()
● PendingIntent.getBroadcast()
● PendingIntent.getActivity()
● PendingIntent.getService()
● When the user taps the Update Notification button, the notification becomes an
InboxStyle notification with several rows of text representing line items.
● The screen has a summary and title-text line, which changes its position depending on the
API level.
Introduction
In previous lessons, you learned how to make your app respond when a user taps a button or a
notification. You also learned how to make your app respond to system events using broadcast
receivers. But what if your app needs to take action at a specific time, for example for a calendar
notification? In this case, you would use AlarmManager. The AlarmManager class lets you launch and
repeat a PendingIntent at a specified time, or after a specified interval.
In this practical, you create a timer that reminds the user to stand up every 15 minutes.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 103
Android Developer Fundamentals Course (V2) – Unit 3
● Send notifications.
What you'll do
● Set a repeating alarm to notify you every 15 minutes.
● Use Toast messages to notify the user when the alarm is turned on or off.
App overview
Stand Up! is an app that helps you stay healthy by reminding you to stand up and walk around every
15 minutes. It uses a notification to let you know when 15 minutes have passed. The app includes a
toggle button that can turn the alarm on and off.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 104
Android Developer Fundamentals Course (V2) – Unit 3
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 105
Android Developer Fundamentals Course (V2) – Unit 3
Attribute Value
android:id "@+id/alarmToggle"
android:layout_width "wrap_content"
android:layout_height "wrap_content"
app:layout_constraintStart_toStartOf "parent"
app:layout_constraintBottom_toBottomOf "parent"
app:layout_constraintEnd_toEndOf "parent"
app:layout_constraintTop_toTopOf "parent"
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 106
Android Developer Fundamentals Course (V2) – Unit 3
The first parameter in onCheckedChanged() is the CompoundButton that the user tapped, which in
this case is the alarm ToggleButton. The second parameter is a boolean that represents the state
of the ToggleButton, that is, whether the toggle is on or off.
alarmToggle.setOnCheckedChangeListener(
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton,
boolean isChecked) {
}
});
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 107
Android Developer Fundamentals Course (V2) – Unit 3
mNotificationManager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
3. Create member constants for the notification ID and the notification channel ID. You will use
these to display the notification. To learn more about notifications, see the Notifications
overview.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 108
Android Developer Fundamentals Course (V2) – Unit 3
For Android 8.0 (API level 27) and higher, to display notifications to the user, you need a notification
channel.
/**
* Creates a Notification channel, for OREO and higher.
*/
public void createNotificationChannel() {
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.enableVibration(true);
notificationChannel.setDescription
("Notifies every 15 minutes to stand up and walk");
mNotificationManager.createNotificationChannel(notificationChannel);
}
}
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 109
Android Developer Fundamentals Course (V2) – Unit 3
2. In the deliverNotification() method, create an Intent that you will use for the
notification content intent.
Intent contentIntent = new Intent(context, MainActivity.class);
Note: PendingIntent flags tell the system how to handle the situation when multiple instances
of the same PendingIntent are created (meaning that the instances contain the same Intent).
The FLAG_UPDATE_CURRENT flag tells the system to use the old Intent but replace the extras
data. Because you don't have any extras in this Intent, you can use the same PendingIntent
over and over.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 110
Android Developer Fundamentals Course (V2) – Unit 3
1. Use the Image Asset Studio to add an image asset to use as the notification icon. Choose any
icon you find appropriate for this alarm and name it ic_stand_up. For example, you could
2. In onCreate(), call deliverNotification() when the alarm toggle button is turned on,
passing in the activity context.
3. In onCreate(), call cancelAll() on the NotificationManager if the toggle is turned off
to remove the notification.
if(isChecked){
deliverNotification(MainActivity.this);
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 111
Android Developer Fundamentals Course (V2) – Unit 3
At this point there is no alarm at all: the notification is immediately delivered when the alarm toggle
button is turned on. In the next task you implement the AlarmManager to schedule and deliver the
notification every 15 minutes.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 112
Android Developer Fundamentals Course (V2) – Unit 3
AlarmManager, like notifications, uses a PendingIntent that it delivers with the specified options.
Because of this, AlarmManager can deliver the Intent even when the app is no longer running.
A broadcast receiver receives the broadcast intent and delivers the notification.
Alarms do not fire when the device is in Doze mode (idle). Instead, alarms are deferred until the
device exits Doze. To guarantee that alarms execute, you can use setAndAllowWhileIdle() or
setExactAndAllowWhileIdle(). You can also use the new WorkManager API, which is built to
perform background work either once or periodically. For details, see Schedule tasks with
WorkManager.
The AlarmManager can trigger one-time or recurring events that occur even when your app is not
running. For real-time clock (RTC) alarms, schedule events using System.currentTimeMillis().
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 113
Android Developer Fundamentals Course (V2) – Unit 3
For more about the available clocks and how to control the timing of events, see SystemClock.
1. In Android Studio, select File > New > Other > Broadcast Receiver.
2. Enter AlarmReceiver for the Class Name. Make sure that the Exported checkbox is
cleared so that other apps can't invoke this broadcast receiver.
Android Studio creates a subclass of BroadcastReceiver with the required method, onReceive().
Android Studio also adds the receiver to your AndroidManifest file.
1. Remove the entire default implementation from the onReceive() method, including the line
that raises the UnsupportedOperationException.
2. Cut and paste the deliverNotification() method from the MainActivity class to the
AlarmReceiver class and call it from onReceive(). You may notice some variables
highlighted in red. You define them in the next step.
3. Copy the NOTIFICATION_ID, PRIMARY_CHANNEL_ID, and mNotificationManager
member variables from the MainActivity class into the AlarmReceiver class.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 114
Android Developer Fundamentals Course (V2) – Unit 3
context.getSystemService(Context.NOTIFICATION_SERVICE);
deliverNotification(context);
}
1. Create an Intent called notifyIntent. Pass in the context and AlarmReceiver class.
Intent notifyIntent = new Intent(this, AlarmReceiver.class);
2. Create the notify PendingIntent. Use the context, the NOTIFICATION_ID variable, the new
notify intent, and the FLAG_UPDATE_CURRENT flag.
PendingIntent notifyPendingIntent = PendingIntent.getBroadcast
(this, NOTIFICATION_ID, notifyIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 115
Android Developer Fundamentals Course (V2) – Unit 3
You use the setInexactRepeating() alarm because it is more resource-efficient to use inexact
timing, which lets the system bundle alarms from different apps together. Also, it's acceptable for
your app to deviate a little bit from the exact 15-minute interval.
● The alarm type. In this case only the relative time is important, and you want to wake the
device if it's asleep, so use ELAPSED_REALTIME_WAKEUP.
● The trigger time in milliseconds. Use the current elapsed time, plus 15 minutes. To get the
current elapsed time, call SystemClock.elapsedRealtime(). Then use a built-in
AlarmManager constant to add 15 minutes to the elapsed time.
● The time interval in milliseconds. You can use the
AlarmManager.INTERVAL_FIFTEEN_MINUTES constant.
● The PendingIntent to be delivered.
//If the Toggle is turned on, set the repeating alarm with a 15 minute
interval
if (alarmManager != null) {
alarmManager.setInexactRepeating
(AlarmManager.ELAPSED_REALTIME_WAKEUP,
triggerTime, repeatInterval, notifyPendingIntent);
}
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 116
Android Developer Fundamentals Course (V2) – Unit 3
Note: Because you are accessing the AlarmManager and notifyPendingIntent instances from
an anonymous inner class, Android Studio may make these instances final. If it doesn't, you
have to make them final yourself.
1. Inside the else case (when the alarm is toggled off), cancel the alarm by calling cancel() on
the AlarmManager. Pass in the pending intent used to create the alarm.
if (alarmManager != null) {
alarmManager.cancel(notifyPendingIntent);
}
Keep the call to cancelAll() on the NotificationManager, because turning the alarm toggle off
should still remove any existing notification.
The AlarmManager now delivers your broadcast 15 minutes after the alarm is set, and every 15
minutes after that.
1. Run your app. If you don't want to wait 15 minutes to see the notification, change the trigger
time to SystemClock.elapsedRealtime() to see the notification immediately. You can also
change the interval to a shorter time to make sure that the repeated alarm is working.
You now have an app that can schedule and perform a repeated operation, even if the app is no
longer running. Go ahead, exit the app completely—the notification is still delivered.
You still need to fix one thing to ensure a proper user experience: if the app is closed, the toggle
button resets to the off state, even if the alarm has already been set. To fix this, you need to check
the state of the alarm every time the app is launched.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 117
Android Developer Fundamentals Course (V2) – Unit 3
1. Create a boolean that is true if PendingIntent is not null, and false otherwise. Use
this boolean to set the state of the ToggleButton when your app starts. This code has to
come before the PendingIntent is created. (Otherwise it always returns true.)
Note: The flag determines what happens if a PendingIntent whose intent matches the intent
you are trying to create already exists. The NO_CREATE flag returns null unless a
PendingIntent with a matching Intent exists.
1. Set the state of the toggle right after you define alarmUp:
alarmToggle.setChecked(alarmUp);
This ensures that the toggle is always on if the alarm is set, and off otherwise. You now have a
repeated scheduled alarm to remind the user to stand up every 15 minutes.
1. Run your app. Switch on the alarm. Exit the app. Open the app again. The alarm button
shows that the alarm is on.
Solution code
Android Studio project: StandUp
Coding challenge
Note: All coding challenges are optional and are not prerequisites for later lessons.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 118
Android Developer Fundamentals Course (V2) – Unit 3
The AlarmManager class also handles the usual kind of alarm clocks, the kind that wake you up in
the morning. On devices running API 21 and higher, you can get information about the next alarm
clock of this kind by calling getNextAlarmClock() on the alarm manager.
Add a button to your app that displays a Toast message. The toast shows the time of the next alarm
clock that the user has set.
Summary
● AlarmManager allows you to schedule tasks based on the real-time clock or on the elapsed
time since boot.
● AlarmManager provides a variety of alarm types, both periodic and one-time.
● Alarms do not fire when the device is in Doze mode (idle). Scheduled alarms are deferred
until the device exits Doze.
● If you need tasks to be completed even when the device is idle, you can use
setAndAllowWhileIdle() or setExactAndAllowWhileIdle(). You can also use the
WorkManager API, which is built to perform background work either once or periodically. For
more information, see Schedule tasks with WorkManager.
● Whenever possible, use the inexact-timing version of the AlarmManager. Inexact timing
minimizes the load caused by multiple users' devices or multiple apps performing a task at
the exact same time.
● AlarmManager uses pending intents to perform its operations. You schedule broadcasts,
services, and activities using the appropriate PendingIntent.
Related concept
The related concept documentation is in 8.2: Alarms.
Learn more
Android developer documentation:
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 119
Android Developer Fundamentals Course (V2) – Unit 3
● AlarmManager
● SystemClock
Other resources:
Homework
Build and run an app
Make an app that delivers a notification when the time is 11:11 AM. The screen displays a toggle
switch that turns the alarm on and off.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 120
Android Developer Fundamentals Course (V2) – Unit 3
Note: The notification might look a little different, depending on the API level of the device.
Question 1
In which API level did inexact timing become the default for AlarmManager? (All set() methods use
inexact timing, unless explicitly stated.)
● API level 16
● API level 18
● API level 19
● API level 17
Introduction
You've seen that you can use the AlarmManager class to trigger events based on the real-time clock,
or based on elapsed time since boot. Most tasks, however, do not require an exact time, but should
be scheduled based on a combination of system and user requirements. For example, to preserve
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 121
Android Developer Fundamentals Course (V2) – Unit 3
the user's data and system resources, a news app could wait until the device is charging and
connected to Wi-Fi to update the news.
The JobScheduler class allows you to set the conditions, or parameters, for when to run your task.
Given these conditions, JobScheduler calculates the best time to schedule the execution of the job.
For example, job parameters can include the persistence of the job across reboots, whether the
device is plugged in, or whether the device is idle.
The task to be run is implemented as a JobService subclass and executed according to the
specified parameters.
JobScheduler is only available on devices running API 21 and higher, and is currently not available
in the support library. For backward compatibility, use WorkManager. The WorkManager API lets you
schedule background tasks that need guaranteed completion, whether or not the app process is
around. For devices running API 14 and higher, including devices without Google Play services,
WorkManager provides capabilities that are like those provided by JobScheduler.
In this practical, you create an app that schedules a notification. The notification is posted when the
parameters set by the user are fulfilled and the system requirements are met.
● Create PendingIntents.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 122
Android Developer Fundamentals Course (V2) – Unit 3
What you'll do
● Implement a JobService that delivers a simple notification to let the user know the job is
running.
● Get user input to configure constraints (such as waiting until the device is charging) on the
JobService.
App overview
For this practical you create an app called Notification Scheduler. Your app will demonstrate the
JobScheduler framework by allowing the user to select constraints and schedule a job. When that
job is executed, the app posts a notification. (In this app, the notification is effectively the “job.”)
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 123
Android Developer Fundamentals Course (V2) – Unit 3
● A JobInfo object contains the set of conditions that trigger a job to run.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 124
Android Developer Fundamentals Course (V2) – Unit 3
● A JobService is the implementation of the job that runs under the conditions set in the
JobInfo object.
To begin with, create a service that will run at a time determined by the conditions. The system
automatically executes the JobService. The only parts you need to implement are the
onStartJob() callback and the onStopJob() callback.
● Called when the system determines that your task should be run. In this method, you
implement the job to be done.
● Returns a boolean indicating whether the job needs to continue on a separate thread. If
true, the work is offloaded to a different thread, and your app must call jobFinished()
explicitly in that thread to indicate that the job is complete. If false, the system knows that
the job is completed by the end of onStartJob(), and the system calls jobFinished() on
your behalf.
Note: The onStartJob() method is executed on the main thread, and therefore any
long-running tasks must be offloaded to a different thread. In this app, you are simply posting a
notification, which can be done safely on the main thread.
● If the conditions described in the JobInfo are no longer met, the job must be stopped, and
the system calls onStopJob().
● The onStopJob() callback returns a boolean that determines what to do if the job is not
finished. If the return value is true, the job is rescheduled; otherwise, the job is dropped.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 125
Android Developer Fundamentals Course (V2) – Unit 3
1. Create a new Java project called "Notification Scheduler". Use API 21 as the target SDK, and
use the Empty Activity template.
2. Inside the com.android.example.notificationscheduler package, create a new Java
class that extends JobService. Call the new class NotificationJobService.
3. Add the required methods, which are onStartJob() and onStopJob(). Click the red light
bulb next to the class declaration and select Implement methods, then select OK.
4. In your AndroidManfest.xml file, inside the <application> tag, register your JobService
with the following permission:
<service
android:name=".NotificationJobService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
1. Add an image asset to use as a notification icon for the "Job" notification. Name the
image ic_job_running.
2. Declare a member variable for the notification manager and a constant for the notification
channel ID.
NotificationManager mNotifyManager;
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 126
Android Developer Fundamentals Course (V2) – Unit 3
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.enableVibration(true);
notificationChannel.setDescription
("Notifications from Job Service");
mNotifyManager.createNotificationChannel(notificationChannel);
}
}
4. Inside onStartJob(), call the method to create the notification channel. Create a
PendingIntent that launches your app's MainActivity. This intent is the content intent
for your notification.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 127
Android Developer Fundamentals Course (V2) – Unit 3
Attribute Title
Priority NotificationCompat.PRIORITY_HIGH
Defaults NotificationCompat.DEFAULT_ALL
AutoCancel true
@Override
public boolean onStartJob(JobParameters jobParameters) {
//Set up the notification content intent to launch the app when clicked
PendingIntent contentPendingIntent = PendingIntent.getActivity
(this, 0, new Intent(this, MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT);
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 128
Android Developer Fundamentals Course (V2) – Unit 3
mNotifyManager.notify(0, builder.build());
return false;
}
8. Make sure that onStopJob() returns true, because if the job fails, you want the job to be
rescheduled instead of dropped.
To begin, you create a group of radio buttons to determine the network type that this job requires.
● NETWORK_TYPE_NONE means that the job will run with or without a network connection. This
is the default value.
● NETWORK_TYPE_ANY means that the job will run as long as a network (cellular, Wi-Fi) is
available.
● NETWORK_TYPE_UNMETERED means that the job will run as long as the device is connected to
Wi-Fi that does not use a HotSpot.
Your app layout includes radio buttons with which the user chooses network criteria.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 129
Android Developer Fundamentals Course (V2) – Unit 3
Implement the following steps in the activity_main.xml file. Make sure to extract all the
dimensions and string resources.
1. Change the root view element to a vertical LinearLayout and give the layout a padding of
16dp. You might get a few errors, which you fix later.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
....
</LinearLayout>
Attribute Value
android:layout_width "wrap_content"
android:layout_height "wrap_content"
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 130
Android Developer Fundamentals Course (V2) – Unit 3
android:textAppearance "@style/TextAppearance.AppCompat.Subhe
ad"
android:layout_margin "4dp"
1. Below the TextView, add a RadioGroup container element with the following attributes:
Attribute Value
android:layout_width "wrap_content"
android:layout_height "wrap_content"
android:orientation "horizontal"
android:id "@+id/networkOptions"
android:layout_margin "4dp"
Note: Using a RadioGroup element ensures that the user can select only one of the element's
children, which you define in the next step. For more details, see Radio Buttons.
1. Add three RadioButton views as child elements inside the RadioGroup. For each of the
radio buttons, set the layout height and width to "wrap_content", and set the following
attributes:
RadioButton 1
android:text "None"
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 131
Android Developer Fundamentals Course (V2) – Unit 3
android:id "@+id/noNetwork"
android:checked "true"
RadioButton 2
android:text "Any"
android:id "@+id/anyNetwork"
RadioButton 3
android:text "Wifi"
android:id "@+id/wifiNetwork"
1. Add two Button views below the RadioGroup. For each of the buttons, set the height and
width to "wrap content", and set the following attributes:
Button 1
android:onClick "scheduleJob"
android:layout_gravity "center_horizontal"
android:layout_margin "4dp"
Button 2
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 132
Android Developer Fundamentals Course (V2) – Unit 3
android:onClick "cancelJobs"
android:layout_gravity "center_horizontal"
android:layout_margin "4dp"
1. In MainActivity, add a method stub for an onClick() method for each of the two buttons.
Implement the following steps in MainActivity.java. Extract your string resources when
required.
1. In the scheduleJob() method, find the RadioGroup by ID and save it in an instance variable
called networkOptions.
RadioGroup networkOptions = findViewById(R.id.networkOptions);
2. In the scheduleJob() method, get the selected network ID and save it in an integer variable.
int selectedNetworkID = networkOptions.getCheckedRadioButtonId();
1. In the scheduleJob() method, create an integer variable for the selected network option.
Set the variable to the default network option, which is NETWORK_TYPE_NONE.
int selectedNetworkOption = JobInfo.NETWORK_TYPE_NONE;
2. In the scheduleJob() method, create a switch statement with the selected network ID. Add a
case for each of the possible IDs.
3. In the scheduleJob() method, assign the selected network option the appropriate JobInfo
network constant, depending on the case.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 133
Android Developer Fundamentals Course (V2) – Unit 3
switch(selectedNetworkID){
case R.id.noNetwork:
selectedNetworkOption = JobInfo.NETWORK_TYPE_NONE;
break;
case R.id.anyNetwork:
selectedNetworkOption = JobInfo.NETWORK_TYPE_ANY;
break;
case R.id.wifiNetwork:
selectedNetworkOption = JobInfo.NETWORK_TYPE_UNMETERED;
break;
}
4. Inside the scheduleJob() method, after the Switch block, create a JobInfo.Builder
object. The first parameter is the JOB_ID. The second parameter is the ComponentName for
the JobService you created. The ComponentName is used to associate the JobService
with the JobInfo object.
ComponentName serviceName = new ComponentName(getPackageName(),
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 134
Android Developer Fundamentals Course (V2) – Unit 3
NotificationJobService.class.getName());
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, serviceName);
6. Call schedule() on the JobScheduler object. Use the build() method to pass in the
JobInfo object.
JobInfo myJobInfo = builder.build();
mScheduler.schedule(myJobInfo);
7. Show a Toast message, letting the user know the job was scheduled.
Toast.makeText(this, "Job Scheduled, job will run when " +
"the constraints are met.", Toast.LENGTH_SHORT).show();
8. In the cancelJobs() method, check whether the JobScheduler object is null. If not, call
cancelAll() on the object to remove all pending jobs. Also reset the JobScheduler to
null and show a toast message to tell the user that the job was canceled.
if (mScheduler!=null){
mScheduler.cancelAll();
mScheduler = null;
Toast.makeText(this, "Jobs cancelled", Toast.LENGTH_SHORT).show();
}
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 135
Android Developer Fundamentals Course (V2) – Unit 3
9. Run the app. You can now set tasks that have network restrictions and see how long it takes
for the tasks to be executed. In this case, the task is to deliver a notification. To dismiss the
notification, the user either swipes the notification away or taps it to open the app.
You may notice that if you do not change the network constraint to "Any" or "Wifi", the app
crashes with the following exception:
java.lang.IllegalArgumentException:
You're trying to build a job with no constraints, this is not allowed.
The crash happens because the "No Network Required" condition is the default, and this
condition does not count as a constraint. To properly schedule the JobService, the JobScheduler
needs at least one constraint.
In the following section you create a conditional variable that's true when at least one constraint is
set, and false otherwise. If the conditional is true, your app schedules the task. If the conditional is
false, your app shows a toast message that tells the user to set a constraint.
1. After the JobInfo.Builder definition, above the myJobInfo definition, create a boolean
variable called constraintSet. The variable is true if the selected network option is not
the default. (The default is JobInfo.NETWORK_TYPE_NONE.)
boolean constraintSet = selectedNetworkOption != JobInfo.NETWORK_TYPE_NONE;
2. After the constraintSet definition, create an if/else block using the constraintSet
variable.
3. Move the code that schedules the job and shows the toast message into the if block.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 136
Android Developer Fundamentals Course (V2) – Unit 3
4. If constraintSet is false, show a toast message to the user telling them to set at least one
constraint. Don't forget to extract your string resources.
if(constraintSet) {
//Schedule the job and notify the user
JobInfo myJobInfo = builder.build();
mScheduler.schedule(myJobInfo);
Toast.makeText(this, "Job Scheduled, job will run when " +
"the constraints are met.", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(this, "Please set at least one constraint",
Toast.LENGTH_SHORT).show();
}
In this section, you add switches to your app to toggle these constraints on your JobService.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 137
Android Developer Fundamentals Course (V2) – Unit 3
1. Copy the TextView that you used for the network-type label and paste it below the
RadioGroup.
2. Change the android:text attribute to "Requires:".
Attribute Value
android:layout_width "match_parent"
android:layout_height "wrap_content"
android:orientation "horizontal"
android:layout_margin "4dp"
4. Create two Switch views as children to the horizontal LinearLayout. Set the height and
width to "wrap_content", and use the following attributes:
Switch 1
android:id "@+id/idleSwitch"
Switch 2
android:id "@+id/chargingSwitch"
1. Create member variables called mDeviceIdle and mDeviceCharging, for the switches.
Initialize the variables in onCreate().
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 138
Android Developer Fundamentals Course (V2) – Unit 3
onCreate():
mDeviceIdleSwitch = findViewById(R.id.idleSwitch);
mDeviceChargingSwitch = findViewById(R.id.chargingSwitch);
2. In the scheduleJob() method, add the following calls. The calls set constraints on the
JobInfo.Builder based on the user selection in the Switch views, during the creation of
the builder object.
.setRequiresDeviceIdle(mDeviceIdleSwitch.isChecked())
.setRequiresCharging(mDeviceChargingSwitch.isChecked());
1. Update the code that sets constraintSet to consider the new constraints:
boolean constraintSet = (selectedNetworkOption != JobInfo.NETWORK_TYPE_NONE)
|| mDeviceChargingSwitch.isChecked() ||
mDeviceIdleSwitch.isChecked();
2. Run your app, now with the additional constraints. Try different combinations of switches to
see when the notification that indicates that the job ran is sent.
1. Open the More menu (the ellipses icon next to the emulated device).
2. Go to the Battery pane.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 139
Android Developer Fundamentals Course (V2) – Unit 3
3. Toggle the Battery Status drop-down menu. There is currently no way to manually put the
emulator in idle mode.
For battery-intensive tasks such as downloading or uploading large files, it's a common pattern to
wait until the device is idle and connected to a power supply.
The JobScheduler API includes the ability to set a hard deadline that overrides all previous
constraints.
Add the new UI for setting the deadline to run the task
In this step you use a SeekBar to allow the user to set a deadline between 0 and 100 seconds to
execute your task. The user sets the value by dragging the seek bar left or right.
1. Below the LinearLayout that has the Switch views, create a horizontal LinearLayout.
The new LinearLayout is for the SeekBar labels.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 140
Android Developer Fundamentals Course (V2) – Unit 3
Attribute Value
android:layout_width "match_parent"
android:layout_height "wrap_content"
android:orientation "horizontal"
android:layout_margin "4dp"
2. Give the seek bar two labels: a static label like the label for the group of radio buttons, and a
dynamic label that's updated with the value from the seek bar. Add two TextView views to
the LinearLayout with the following attributes:
TextView 1
android:layout_width "wrap_content"
android:layout_height "wrap_content"
android:id "@+id/seekBarLabel"
android:textAppearance "@style/TextAppearance.AppCompat.Subhead
"
TextView 2
android:layout_width "wrap_content"
android:layout_height "wrap_content"
android:id "@+id/seekBarProgress"
android:textAppearance "@style/TextAppearance.AppCompat.Subhead
"
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 141
Android Developer Fundamentals Course (V2) – Unit 3
3. Add a SeekBar view below the LinearLayout. Use the following attributes:
Attribute Value
android:layout_width "match_parent"
android:layout_height "wrap_content"
android:id "@+id/seekBar"
android:layout_margin "4dp"
Implement the following steps in MainActivity.java. Don’t forget to extract your string
resources.
onCreate():
mSeekBar = findViewById(R.id.seekBar);
2. In onCreate(), create and initialize a final variable for the seek bar's progress TextView.
(The variable will be accessed from an inner class.)
final TextView seekBarProgress = findViewById(R.id.seekBarProgress);
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 142
Android Developer Fundamentals Course (V2) – Unit 3
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
4. The second argument of onProgressChanged() is the current value of the seek bar. In the
onProgressChanged() callback, check whether the integer value is greater than 0 (meaning a
value has been set by the user). If the value is greater than 0, set the seek bar's progress
label to the integer value, followed by s to indicate seconds. Otherwise, set the TextView to
read "Not Set".
if (i > 0){
seekBarProgress.setText(i + " s");
}else {
seekBarProgress.setText("Not Set");
}
5. The override deadline should only be set if the integer value of the SeekBar is greater than
0. In the scheduleJob() method, create an int to store the seek bar's progress. Also create
a boolean variable that's true if the seek bar has an integer value greater than 0.
int seekBarInteger = mSeekBar.getProgress();
boolean seekBarSet = seekBarInteger > 0;
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 143
Android Developer Fundamentals Course (V2) – Unit 3
6. In the scheduleJob() method after the builder definition, if seekBarSet is true, call
setOverrideDeadline() on the JobInfo.Builder. Pass in the seek bar's integer value
multiplied by 1000. (The parameter is in milliseconds, and you want the user to set the
deadline in seconds.)
if (seekBarSet) {
builder.setOverrideDeadline(seekBarInteger * 1000);
}
2. Run the app. The user can now set a hard deadline, in seconds, by which time the
JobService must run!
Solution code
Android Studio project: NotificationScheduler
Coding challenge
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 144
Android Developer Fundamentals Course (V2) – Unit 3
Note: All coding challenges are optional and are not prerequisites for later lessons.
Challenge: Up until now, your JobService tasks have simply delivered a notification, but
JobScheduler is usually used for more robust background tasks, such as updating the weather or
syncing with a database. Because background tasks can be more complex, programmatically and
functionally, the job of notifying the framework when the task is complete falls on the developer.
Fortunately, the developer can do this by calling jobFinished().
This challenge requires you to call jobFinished() after the task is complete:
● Implement a JobService that starts an AsyncTask when the given constraints are met.
● The AsyncTask should sleep for 5 seconds.
● If the constraints stop being met while the thread is sleeping, reschedule the job and show a
Toast message saying that the job failed.
Summary
● JobScheduler provides a flexible framework to intelligently accomplish background
services.
● To use the JobScheduler, you need two parts: JobService and JobInfo.
● JobService implements the job to run under the conditions specified by JobInfo.
● You only have to implement the onStartJob() and onStopJob() callback methods, which
you do in your JobService.
● The implementation of your job occurs, or is started, in onStartJob().
● The onStartJob() method returns a boolean value that indicates whether the service
needs to process the work in a separate thread.
● If onStartJob() returns true, you must explicitly call jobFinished(). If onStartJob()
returns false, the runtime calls jobFinished() on your behalf.
● JobService is processed on the main thread, so you should avoid lengthy calculations or
I/O.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 145
Android Developer Fundamentals Course (V2) – Unit 3
● JobScheduler is the manager class responsible for scheduling the task. JobScheduler
batches tasks to maximize the efficiency of system resources, which means that you do not
have exact control of when tasks are executed.
Related concept
The related concept documentation is in 8.3: Efficient data transfer.
Learn more
Android developer documentation:
● JobScheduler
● JobInfo
● JobInfo.Builder
● JobService
● JobParameters
Homework
Build and run an app
Create an app that simulates a large download scheduled with battery and data consumption in
mind. The app contains a Download Now button and has the following features:
● When the user taps the Download Now button, it triggers a "downloading" notification.
● The "download" is performed once a day, when the phone is idle but connected to power
and to Wi-Fi, or when the user taps the button.
Hint :Define the JobService class as an inner class. That way, the Download Now button and the
JobService can call the same method to deliver the notification.
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 146
Android Developer Fundamentals Course (V2) – Unit 3
Note: The notification might look a little different, depending on the API level of the device.
Question 1
What class do you use if you want features like the ones provided by JobScheduler, but you want
the features to work for devices running API level 20 and lower?
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 147
Android Developer Fundamentals Course (V2) – Unit 3
● JobSchedulerCompat
● workManager
● AlarmManager
This work is licensed under a Creative Commons Attribution 4.0 International License.
This PDF is a one-time snapshot. See developer.android.com/courses/fundamentals-training/toc-v2
for the latest updates.
Page 148