Android-Architecture-Components
Android-Architecture-Components copied to clipboard
The template project that uses Android Architecture Components with Repository pattern. The simple app that uses awesome Fuel library instead of Retrofit for perfoming HTTP request. The app also persi...
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)
})
}
}
}