/Android-Architecture-Components

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 persists data using the Room library and display data in RecyclerView.

Primary LanguageKotlin

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