Using Android Architecture Components at Rocket

At Rocket, we've started using Android Architecture Components for new projects. We had been using Model View Presenter as Tambet previously wrote about. While any new framework or architecture comes with its own new ups and downs, Architecture Components has been an overwhelmingly positive experience. It's a framework supported and recommended by Google, even to the point of making it into the latest version of the standard support library.

We're not simply using it in test or sample apps, these are production-ready applications. Since the framework has a variety of new components, we'll do an overview of them here and look for future breakdowns in later blog posts.

LiveData

First, what are these Android Architecture Components and what do they do?

While there are a lot of changes, one could argue that the most important change is the addition of LiveData. LiveData is a wrapper around your typical models that notify observers when something changes in that model. These observers come in the form of LifecycleOwners. That means they have lifecycle tracking all their own that the LiveData object checks before notifying the observer of changes.

They're simple and easy to set up. All of the examples in this blog post will be in Kotlin as adoption is rising, it's supported by Google, and our new projects are written in it.

  // create a simple LiveData container for a nullable String
  var authToken: LiveData<String?>

  // create and instantiate a LiveData container for a nullable String
  var secondAuthToken = MutableLiveData<String?>()

Take note of the use of MutableLiveData here. While LiveData is an abstract class, MutableLiveData is an extension of that class.

Lifecycles

What does that get you?

Lifecycles are now baked into the standard AppCompatActivity as well as Fragments in the support library. This means that you can set up your Activity or Fragment as the LifecycleOwner and then do the following:

class MyActivity : AppCompatActivity() {  
  override fun onCreate(savedInstanceState: Bundle?) {
    // typical boilerplate
    super.onCreate(savedInstanceState)
    setContentView(R.layout.my_activity_layout)

    referenceToALiveDataObject.observe(this, Observer { data ->
      // do something with the data object, this will be called if the LiveData object changes while the Activity is
      // still active (ie, isn't paused)
      myTextView.text = data.textProperty
    })
  }
}

This means that when the Activity or Fragment is paused, you don't need to detach an observer or something similar. With a Model View Presenter (MVP) architecture, the Views have to update the Presenters and the Presenters have to detect if the Views are still active. Lifecycles handle all of that for you.

ViewModels

ViewModels are where you hold and update that LiveData. Typically, the idea is to have one ViewModel for one Activity. Additionally, you can share a ViewModel between Fragments in an Activity or have them create their own. This way you can have a single ViewModel or multiples depending on your particular use-case. Here's how to create a ViewModel class:

class MyViewModel() : ViewModel() {  
  val someLiveData: MutableLiveData<MyModelClass>()

  fun methodThatChangesTheLiveData() {
    val instanceOfMyModelClass = MyModelClass()
    someLiveData.postValue(instanceOfMyModelClass)
  }
}

By changing the value of someLiveData, observers used in activities or fragments will be notified and they can update the UI accordingly.

Additionally, you can create a ViewModel with a reference to the application in the constructor. These are called AndroidViewModels. This is useful for cases where you need access to an Android context object in order to use SharedPreferences, etc.

class MyAndroidViewModel(application: Application) : ViewModel(application) {  
  val someLiveData: MutableLiveData<MyModelClass>()
  private val preferences = PreferenceManager.getDefaultSharedPreferences(application)

  fun methodThatChangesTheLiveData() {
    val instanceOfMyModelClass = MyModelClass()
    instanceOfMyModelClass.property = preferences.getString("string_set_elsewhere", null)
    someLiveData.postValue(instanceOfMyModelClass)
  }
}

This is useful in many cases to the point of being used more than a base ViewModel because many functions require access to an Android context. While it is useful from the perspective of a developer getting something working, it does make it harder to test. Using a base ViewModel, you can easily create unit tests. As soon as you start using an AndroidViewModel, then the test becomes an instrumentation test. That is unless you've set up Dagger in your project, but that is outside the scope of this blog post.

To reference a ViewModel from an activity, you'll want to do something like the following:

class MyActivity : AppCompatActivity() {  
  private lateinit var viewModel: MyViewModel

  override fun onCreate(savedInstanceState: Bundle?) {
    // typical boilerplate
    super.onCreate(savedInstanceState)
    setContentView(R.layout.my_activity_layout)

    viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)

    viewModel.someLiveData.observe(this, Observer { data ->
      // do something with the data object, this will be called if the LiveData object changes while the Activity is
      // still active (ie, isn't paused)
      myTextView.text = data.textProperty
    })
  }
}

It's a similar process for a Fragment:

class MyFragment : Fragment() {  
  private lateinit var viewModel: MyViewModel

  override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    // activity here references the activity the Fragment is attached to
    // additionally, since Fragment implements LifecycleOwner, you can pass "this" instead of activity
    // using the activity means that it'll have the same ViewModel instance, which can be good or bad depending
    // on your particular use case
    viewModel = ViewModelProviders.of(activity).get(MyHealthViewModel::class.java)

    viewModel.someLiveData.observe(this, Observer { data ->
      // do something with the data object, this will be called if the LiveData object changes while the Activity is
      // still active (ie, isn't paused)
      myTextView.text = data.textProperty
    })
  }
}

Whether you are using a ViewModel or an AndroidViewModel, getting an instance of the view model is the same.

Room

The final big addition that comes with Android Architecture Components is Room. Room as a layer over database creation and access. First, you'll want to set your models to be a Room Entity.

@Entity
data class SimpleModel(  
  @PrimaryKey val id: Int,
  var stringProperty: String
)

There are ways to serialize and deserialize complicated properties or other objects into the database, but those will have to be saved for another blog post.

Now create a Data Access Object (DAO) interface.

@Dao
interface SimpleModelDao {  
  @Query("select * from SimpleModel where id = :id")
  fun get(id: Int): LiveData<SimpleModel>

  @Insert(onConflict = OnConflictStrategy.REPLACE)
  fun insert(model: SimpleModel)

  @Update(onConflict = OnConflictStrategy.REPLACE)
  fun update(model: SimpleModel)

  @Query("DELETE FROM SimpleModel where id = :id")
  fun delete(id: Int)
}

Notice how LiveData has come back into the picture? The Room layer uses LiveData to represent any data coming from the SQLite database. The final step is to create the database access layer that other classes will use.

@Database(entities = arrayOf(SimpleModel::class), version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {  
    abstract fun simpleModelDao(): SimpleModelDao

    companion object {
        @Volatile private var INSTANCE: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase =
                INSTANCE ?: synchronized(this) {
                    INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
                }

        private fun buildDatabase(context: Context) =
                Room
                        .databaseBuilder(context, AppDatabase::class.java, "myApplication")
                        .build()
    }
}

Now let's use that in our ViewModel. Normally you'd want a Repository class sitting in between the ViewModel layer and the database, but for simplicity's sake in this blog post, we'll omit that.

class MyAndroidViewModel(application: Application) : ViewModel(application) {  
  private lateinit var liveSimpleModel: LiveData<SimpleModel>
  private val simpleModelDao = AppDatabase.getInstance(application).simpleModelDao()

  init {
    liveSimpleModel = simpleModelDao.get(1)
  }
}

Since we're using LiveData, we'll be able to reference it from the activity or fragment that we're using. That activity or fragment can update the UI accordingly as the data changes.

Summary

Here's a diagram of the whole system and how it should work, from the official Android documentation.

Android Architecture Components Diagram

In this blog post, we've covered how to set up simple ViewModels, Models, and LifecycleOwner Activities and Fragments using LiveData and Room to pass the data around. The examples included here are simplistic, but hopefully they give an idea of how to set up a project using Android Architecture Components. You can forget about registering and unregistering views with presenters. Using LiveData and ViewModels, you'll be able to reuse ViewModels and easily watch for changes to the data as it's loaded from a database or web server.