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)
})
}
}
}