Combining data binding with Lifecycle components

We can use data binding along with Lifecycle components (e.g. ViewModel and LiveData) to remove the controller (Activity or Fragment) as middle-man from the process of passing data from the ViewModel to the View.

Without data binding (at least, only using data binding to avoid findViewById):

import ...

class MyFragment : Fragment() {

    private lateinit var viewModel: MyViewModel // Declare a MyViewModel object

    private lateinit var binding: MyFragmentBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        // Inflate view and obtain an instance of the binding class.
        val binding: ScoreFragmentBinding = DataBindingUtil.inflate(
                inflater,
                R.layout.my_fragment,
                container,
                false
        )

        viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java) // Initialise the MyViewModel object to a MyViewModel instance
        viewModel.myVar.observe(this, Observer { myVar -> // Observe a variable in the ViewModel class
            binding.myTextview.text = myVar.toString() // Use it to change our UI
        })

        binding.myButton.setOnClickListener { viewModel.changeVar() } // In this example, we have a button with an onClickListener which calls a MyViewModel function to change the variable

        return binding.root
    }
}

With data binding:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data> // Add a data tag
        <variable // With a variable
            name="myViewModel" // Give it a name
            type="com.example.android.myapp.MyViewModel" /> // Point to the ViewModel class
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/app_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/my_textview"
            android:text="@{@string/string_format(myViewModel.myVar)}" // myVar is a variable within the ViewModel with a public getter. Don't need myVar.value thanks to data binding. Defaults to empty String. This has been wrapped in a String format defined in strings.xml ( <string name="string_format">\"%s\"</string> ) which wraps the String in quotes. Customise this for other types e.g. Ints, or use String.valueOf()
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/my_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{() -> myViewModel.changeVar()}" /> // Add an onClick to provide a listener for the button

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

And our Fragment looks like:

import ...

class MyFragment : Fragment() {

    private lateinit var viewModel: MyViewModel // Declare a MyViewModel object

    private lateinit var binding: MyFragmentBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        // Inflate view and obtain an instance of the binding class.
        val binding: ScoreFragmentBinding = DataBindingUtil.inflate(
                inflater,
                R.layout.my_fragment,
                container,
                false
        )

        viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
        // We have removed our onClickListener and Observer

        binding.myViewModel = viewModel // Link your ViewModel with the data tag in the xml file. Allows 

        binding.setLifecycleOwner(this) // Set the Lifecycleowner of the bindingto the current Fragment. Allows us to automatically update layouts

        return binding.root
    }
}

For anything more complicated than plain text we meed to move the logic and data manipulation to the ViewModel and use the likes  of Transformations.map (see this post).

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

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.