Touch selector

Gives visual feedback when an item is pressed.

Start with an xml file to define a selector drawable. Right-click on res->New->Android Resource File, Resource type: Drawable, Root element: selector, then 'OK'. We've named this file 'list_item_selector.xml':

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

    <item android:drawable="@color/colorPrimaryLight" android:state_pressed="true" /> // Add all these states to cover the various selection possibilities
    <item android:drawable="@color/colorPrimaryLight" android:state_activated="true" />
    <item android:drawable="@color/colorPrimaryLight" android:state_selected="true" />

    <!-- This should have a default drawable color = "@android:color/background_light" --> // Default unselected state
    <item android:drawable="@android:color/background_light" />

</selector>

In the example of a list item in a RecyclerView, go to the xml layout file for the list item and add the following to the root layout:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/list_item_selector "> // Add the selector xml file as the background

   ...

</LinearLayout>

Responsive design

To cater for the wide variety of device specifications, we need to consider screen size and density.

These are the 5 most popular density buckets:

  • mdpi (medium) ~160dpi
  • hdpi (high) ~240dpi
  • xhdpi (extra high) ~320dpi
  • xxhdpi (extra extra high) ~ 480dpi
  • xxxhdpi (extra extra extra high) ~640dpi

Density-independent pixels (dp’s) largely overcome these variations in density by making an image of e.g. 48dp look approximately the same physical size on all screens. **Make touch targets 48dp at least** However, you should supply a range of variations of each image to cater for the difference in density e.g. 48px:72px:96px:144px:192px in their appropriate folders to avoid placing unnecessary load on the processor and distorting the image, through scaling. To read more about how to cater for these go here.

For different screen layouts/orientations we can define individual xml files to cater for the variation in available space. Like the density buckets, you can place your layout files in the appropriately named folder and Android will use the layout that corresponds to its current configuration.

At runtime, Android will check the configuration of the device and choose the appropriate resources from the relevant folders accordingly. This is why it is necessary for Android to destroy and recreate Activities on screen rotation, as all of the resources within it could be completely different. Examples of folder variations:
values-fr/ – values for the French language
values-fr-rCA/ – values for the French-Canadian dialect
layout-desk/ – device is docked
layout-stylus/ – for device screens with stylus input
drawable-xhdpi/ – drawable resources for a xhdpi density screen
layout-land/ – landscape orientation
layout-sw720dp/ – minimum smallest screen width the layout will apply to

An example resource directory structure:

res/
   layout/
      activity_main.xml
      detail_activity.xml
      list_item.xml
   layout-sw600dp/
      detail_activity.xml
      list_item.xml
   layout-sw720dp/
      list_item.xml

To create a smallest-width qualifier folder, right-click on res->New->Android Resource Directory, Resource type: layout, Available qualifiers: Smallest Screen Width, enter screen width (in dp), then ‘OK’. File names must be identical across folders to them to be overridden.

Landscape layouts

To efficiently utilise the space available in a landscape orientation, Android allows the defining of an alternative layout.

Create a new layout resource folder named 'layout-land' and copy your portrait xml layout file into it. Then simply edit the layout according to your preferences. If there are elements of your layout that will remain identical across portrait and landscape modes it makes sense to relocate them into their own layout files as outlined in this post.

Localisation or Internationalisation

The adaptation of a product or service to meet the needs of a particular language, culture or desired population’s “look-and-feel”.

1. Translation - you should always design your app in a way that can be easily translated to other languages. To do so, any text that you would expect to be translated like labels and titles and button descriptions should all be defined as a string resource in res/values/strings.xml. This allows you to create other versions of strings.xml for other languages. This is done by creating a new values folder with the pattern value-xx where xx can be the abbreviation of any language from the ISO 639 code list here, for example res/values-fr/strings.xml will contain the french version of the strings.xml file with all the strings translated from the default language to French. Label Strings you do not want to translate like so:

<string name="timeFormat" translatable="false">hh:mm a</string>

2. RTL Support - if you’re distributing to countries where right-to-left (RTL) scripts are used (like Arabic or Hebrew), you should consider implementing support for RTL layouts and text display and editing, to the extent possible. By using the Start and End instead of Left and Right you make it possible for Android to reverse elements when necessary (if supporting API 16 or less include the Left and Right attributes as well):

android:layout_marginStart
android:layout_marginEnd

When images with a directional context are used, allow Android to reflect them to maintain the relevance of the image:

<vector android:autoMirrored="true"> </vector>

For more info on localisation click here.

Accessibility

See the recommendations here.

The most important is TalkBack which is a pre-installed screen reader service provided by Google. It uses spoken feedback to describe the results of actions such as launching an app, and events such as notifications.

You should include the following in your xml layout files to describe all ImageViews, ImageButtons and all Checkboxes:

<ImageView android:contentDescription="@string/origin_label"/>

Others include:

  • Enable focus-based navigation which makes sure users can navigate your screen layouts using external hardware like Bluetooth keyboards
  • No audio-only feedback, which guarantees any audio feedback to always have a secondary feedback mechanism to support users who are deaf or hard of hearing

System Broadcast Intent

A special Intent sent by the system when events occur on the phone. Examples include:

  • android.intent.action.SCREEN_ON
  • android.intent.action.MEDIA_MOUNTED
  • android.intent.action.HEADSET_PLUG
  • android.intent.action.BATTERY_LOW
  • android.intent.action.DOWNLOAD_COMPLETE
  • android.media.AUDIO_BECOMING_NOISY

In order for your app to listen out for any of these System Broadcast Intents you will needs to set up a Broadcast Receiver.

Foreground Services

A foreground service is a service that the user is actively aware of because Android requires it to post a non-dismissable ongoing notification. They are typically used to show the user the real-time progress of a long-running operation. Allows the user to perform basic interactions with your service. Examples would be playback controls in a music app or Google Maps navigation instructions.

Android will prioritise these even if the system is memory constrained.

Services

Services are Android Framework components meant for running background tasks that don’t need a visual component, therefore they do not provide a User Interface (UI). An Activity can start a Service which can then continue to run, even after the Activity is shut down. They are ideal for loading and processing data in the background, regardless of whether any of the app’s Activities are open. A common example of this is how you can receive notifications when an app is not running.

A Service should be used when the task is decoupled from (does not directly affect) the UI, and needs to exist even when there is no UI.

There are 3 ways to start a Service:

  • Start – manually from a context e.g. Activity. Executes but will not normally communicate back to the component that started it.
  • Schedule – if you want to have a Service execute at some point in the future, or when some conditions are met, you can create a JobService. You can control when this starts via a Scheduler e.g. JobScheduler
  • Bind – offers a client-server-like interface, with the Service being the server, and the various components being bound to the Service being the clients. Components bind to the Service via the bindService() method. These Services can easily communicate with the components that are bound to them (unlike the started Services above). An example might be an audio player, where the Service plays the audio while the Activity controls the UI, with the UI being updated as to the progress of the audio file play. Likewise, pressing the Pause button will mean sending a command to the Service.

A Service can be both STARTED and BOUND.

If you want to run a Service in a completely separate thread you can use an Intent Service.

The Lifecycle of a Started Service is depicted below:

Lifecycle

Lifecycle has 2 interfaces:

  • LifecycleOwner – Objects with a lifecycle e.g. Activities and Fragments
  • LifecycleObserver – Observe LifecycleOwners and get notified on lifecycle changes

LiveData is lifecycle aware because it is a Lifecycle Observer. This is why we feed in the LifecycleOwner (in this case the Activity) argument when we call the LiveData’s observer() method. LiveData will also know when the Activity is destroyed and automatically unsubscribe the Observers to prevent memory leaks.

To implement this without LiveData:

import ...

class MainActivity : AppCompatActivity() {

    private lateinit var myClass: MyClass

    // Contains all the views
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Use Data Binding to get reference to the views
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        myClass = MyClass(this.lifecycle) // Instantiate your observer class and pass in the Activity lifecycle (this.lifecycle) as the observed object
    }
}
import ...
class MyClass(lifecycle: Lifecycle) : LifecycleObserver { // Set this class up as an observer

    init {
        lifecycle.addObserver(this) // Initialise the observer to the object passed in to the constructor
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START) // Choose a lifecycle event
    fun startFunction() { // This function will be called on the lifecycle event specified
        // Start something
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun stopFunction() {
        // Stop something
    }
}

 

Add Android Architecture Component Dependencies

To add the desired elements of Android Jetpack to your app. These will need to be updated as the version numbers increment.

Use the following in your build.gradle: Futures

dependencies {
    def futures_version = "1.0.0-alpha02"

    implementation "androidx.concurrent:concurrent-futures:$futures_version"
}

Lifecycle AndroidX

dependencies {
    def lifecycle_version = "2.0.0"

    // ViewModel and LiveData
    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
    // alternatively - just ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" // use -ktx for Kotlin
    // alternatively - just LiveData
    implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
    // alternatively - Lifecycles only (no ViewModel or LiveData). Some UI
    //     AndroidX libraries use this lightweight import for Lifecycle
    implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"

    annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // use kapt for Kotlin
    // alternately - if using Java8, use the following instead of lifecycle-compiler
    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"

    // optional - ReactiveStreams support for LiveData
    implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version" // use -ktx for Kotlin

    // optional - Test helpers for LiveData
    testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
}

Lifecycle pre-AndroidX

dependencies {
    def lifecycle_version = "1.1.1"

    // ViewModel and LiveData
    implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    // alternatively - just ViewModel
    implementation "android.arch.lifecycle:viewmodel:$lifecycle_version" // use -ktx for Kotlin
    // alternatively - just LiveData
    implementation "android.arch.lifecycle:livedata:$lifecycle_version"
    // alternatively - Lifecycles only (no ViewModel or LiveData).
    //     Support library depends on this lightweight import
    implementation "android.arch.lifecycle:runtime:$lifecycle_version"

    annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" // use kapt for Kotlin
    // alternately - if using Java8, use the following instead of compiler
    implementation "android.arch.lifecycle:common-java8:$lifecycle_version"

    // optional - ReactiveStreams support for LiveData
    implementation "android.arch.lifecycle:reactivestreams:$lifecycle_version"

    // optional - Test helpers for LiveData
    testImplementation "android.arch.core:core-testing:$lifecycle_version"
}

Room AndroidX

dependencies {
    def room_version = "2.1.0-alpha03"

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version" // use kapt for Kotlin

    // optional - RxJava support for Room
    implementation "androidx.room:room-rxjava2:$room_version"

    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation "androidx.room:room-guava:$room_version"

    // optional - Coroutines support for Room
    implementation "androidx.room:room-coroutines:$room_version"

    // Test helpers
    testImplementation "androidx.room:room-testing:$room_version"
}

Room pre-AndroidX

dependencies {
    def room_version = "1.1.1"

    implementation "android.arch.persistence.room:runtime:$room_version"
    annotationProcessor "android.arch.persistence.room:compiler:$room_version" // use kapt for Kotlin

    // optional - RxJava support for Room
    implementation "android.arch.persistence.room:rxjava2:$room_version"

    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation "android.arch.persistence.room:guava:$room_version"

    // Test helpers
    testImplementation "android.arch.persistence.room:testing:$room_version"
}

Paging AndroidX

dependencies {
    def paging_version = "2.1.0-rc01"

    implementation "androidx.paging:paging-runtime:$paging_version" // use -ktx for Kotlin

    // alternatively - without Android dependencies for testing
    testImplementation "androidx.paging:paging-common:$paging_version" // use -ktx for Kotlin

    // optional - RxJava support
    implementation "androidx.paging:paging-rxjava2:$paging_version" // use -ktx for Kotlin
}

Paging pre-AndroidX

dependencies {
    def paging_version = "1.0.0"

    implementation "android.arch.paging:runtime:$paging_version"

    // alternatively - without Android dependencies for testing
    testImplementation "android.arch.paging:common:$paging_version"

    // optional - RxJava support
    implementation "android.arch.paging:rxjava2:$paging_version"
}

Navigation

dependencies {
    def nav_version = "1.0.0-alpha09"

    implementation "android.arch.navigation:navigation-fragment:$nav_version" // use -ktx for Kotlin
    implementation "android.arch.navigation:navigation-ui:$nav_version" // use -ktx for Kotlin
}

Work Manager

dependencies {
    def work_version = "1.0.0-beta02"

    implementation "android.arch.work:work-runtime:$work_version" // use -ktx for Kotlin+Coroutines

    // optional - RxJava2 support
    implementation "android.arch.work:work-rxjava2:$work_version"

    // optional - Test helpers
    androidTestImplementation "android.arch.work:work-testing:$work_version"
}

See more here

Room

Room is one of the Android App Architecture Components. The Room persistence library (Object Relational Mapping library) provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite. It will map our database objects to Java objects.

Its features include:

  • Less boilerplate code
  • SQL Validation at compile time – incorrect SQL statements will be picked up
  • Built to work with LiveData and RxJava for data observation

Room uses Annotations and has 3 main components:

  • @Entity – to define our database tables
  • @DAO – provides an API for reading and writing data
  • @Database – represents a database holder. Includes a list of @Entity and @DAO annotations and allow us to create a new database or acquire a connection to our db at runtime

To add the dependencies for Room, see here.

Activity Lifecycle Stages

Activity lifecycle stages and their Callback names

General Definitions

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

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

Foreground: When the activity is on screen.

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

Lifecycle States

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

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

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

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

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

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

Activity Lifecycle Callbacks

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

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

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

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

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

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

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

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

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

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

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

Summary of the Fragment Lifecycle

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

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

Important Fragment Callbacks to Implement

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

Initialise anything essential for you fragment.

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

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

Create your views by inflating your XML.

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

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

Other callbacks

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

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

Execute any code that requires an activity instance

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

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

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

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

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

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

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

Lifecycle Cheat sheets

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

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