Android Architecture Components

Additional

Language
Kotlin
Version
N/A
Created
May 18, 2017
Updated
Jul 24, 2017 (Retired)
Owner
Ihor Kucherenko (KucherenkoIhor)
Contributors
Ihor Kucherenko (KucherenkoIhor)
Minsuk Eom (BoxResin)
2
Activity
Badge
Generate
Download
Source code

Android Architecture Components

Read article here

Android Architecture Components (AAC) is a new collection of libraries that contains the lifecycle-aware components. It can solve problems with configuration changes, supports data persistence, reduces boilerplate code, helps to prevent memory leaks and simplifies async data loading into your UI. I can’t say that it brings absolutely new approaches for solving these issues, but, finally, we have a formal, single and official direction.

AAC provides some abstractions to deal with Android lifecycle:

  • LifecycleOwner
  • LiveData
  • ViewModel

The main benefit is the fact that our UI components, like TextView or RecycleView, observe LiveData, which, in turn, observes the lifecycle of an Activity or Fragment, using a LifecycleObserver.

Combination of these components solves main challenges faced by Android developers, such as boilerplate code or modular. To explore and check an example of this concept, I decided to create the sample project. It just gets a list of repositories from Github and shows one using RecyclerView.

As you can see, it handles configuration changes without any problems, and an Activity looks very simple:

class ReposActivity : BaseLifecycleActivity<ReposViewModel>(), SwipeRefreshLayout.OnRefreshListener {

    override val viewModelClass = ReposViewModel::class.java

    private val rv by unsafeLazy { findViewById<RecyclerView>(R.id.rv) }

    private val vRefresh by unsafeLazy { findViewById<SwipeRefreshLayout>(R.id.lRefresh) }

    private val adapter = ReposAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_repos)
        rv.setHasFixedSize(true)
        rv.adapter = adapter
        vRefresh.setOnRefreshListener(this)

        if (savedInstanceState == null) {
            viewModel.setOrganization("yalantis")
        }
        observeLiveData()
    }

    private fun observeLiveData() {
        viewModel.isLoadingLiveData.observe(this, Observer<Boolean> {
            it?.let { vRefresh.isRefreshing = it }
        })
        viewModel.reposLiveData.observe(this, Observer<List<Repo>> {
            it?.let { adapter.dataSource = it }
        })
        viewModel.throwableLiveData.observe(this, Observer<Throwable> {
            it?.let { Snackbar.make(rv, it.localizedMessage, Snackbar.LENGTH_LONG).show() }
        })
    }

    override fun onRefresh() {
        viewModel.setOrganization("yalantis")
    }
}

How you have probably noticed, our activity assumes minimum responsibilities. ReposViewModel holds state and view data in the following way:

open class ReposViewModel(application: Application?) : AndroidViewModel(application) {

    private val organizationLiveData = MutableLiveData<String>()

    val resultLiveData = ReposLiveData().apply {
        this.addSource(organizationLiveData) { it?.let { this.organization = it } }
    }

    val isLoadingLiveData = MediatorLiveData<Boolean>().apply {
        this.addSource(resultLiveData) { this.value = false }
    }

    val throwableLiveData = MediatorLiveData<Throwable>().apply {
        this.addSource(resultLiveData) { it?.second?.let { this.value = it } }
    }

    val reposLiveData = MediatorLiveData<List<Repo>>().apply {
        this.addSource(resultLiveData) { it?.first?.let { this.value = it } }
    }

    fun setOrganization(organization: String) {
        organizationLiveData.value = organization
        isLoadingLiveData.value = true
    }

}

Testability

@RunWith(AndroidJUnit4::class)
class SampleInstrumentedTest {

    @get:Rule
    val activityRule = ActivityTestRule<ReposActivity>(ReposActivity::class.java, true, true)

    private var viewModel: ReposViewModel? = null

    @Before
    fun init() {
        viewModel = ViewModelProviders.of(activityRule.activity).get(ReposViewModel::class.java)
    }

    @Test
    fun testNotNull() {
        activityRule.activity.runOnUiThread {
            viewModel?.setOrganization("yalantis")
            viewModel?.reposLiveData?.observe(activityRule.activity, Observer<List<Repo>> {
               assertNotNull(it)
            })
        }
    }
}