Android Example App
Introduction
I decided to base my application on the CLEAN architecture and set about looking for great examples.
I found Lopez at https://github.com/lopspower/CleanRxArchitecture which had all of the features I looking for
- Retrofit2
- Room
- RxJava
Implementing Screens
The Model
The model for the screens has data associated with each type of data to be displayed. With each construction pass the state and either the content type of data.
class CovidResultListViewModel(
val loadingState: LoadingState = LoadingState.NONE,
val contentState: ContentState = ContentState.NONE,
val covidResults: List<CovidResult>? = null,
var errorMessage: String? = null,
val snackMessage: String? = null) {
...
companion object {
fun createData(data: List<CovidResult>?) =
CovidResultListViewModel(contentState = ContentState.CONTENT, covidResults = data)
fun createLoading() =
CovidResultListViewModel(loadingState = LoadingState.LOADING, contentState = ContentState.CONTENT)
fun createError(error: String) =
CovidResultListViewModel(contentState = ContentState.ERROR, errorMessage = error)
...
}
}
The Presenter
The Presenter has a render method which is responsible for rendering the data.
override fun render(viewModel: CovidResultListViewModel) {
showLoading(viewModel.loadingState == LoadingState.LOADING)
showRefreshingLoading(binding.swipeRefreshLayout, false)
showRetryLoading(viewModel.loadingState == LoadingState.RETRY)
showContent(binding.content, viewModel.contentState == ContentState.CONTENT)
showError(viewModel.contentState == ContentState.ERROR)
renderData(viewModel.covidResults)
renderError(viewModel.errorMessage)
renderSnack(viewModel.snackMessage)
}
Each of the show methods set the flag appropriately for the data to be displayed.
protected fun showLoading(visible: Boolean) {
binding.progressLayoutId.progress.visibility = if (visible) View.VISIBLE else View.GONE
}
protected fun showRefreshingLoading(swipeRefreshLayout: SwipeRefreshLayout, visible: Boolean) {
swipeRefreshLayout.isRefreshing = visible
}
protected fun showRetryLoading(visible: Boolean) {
binding.errorLayoutId.btnErrorRetry.isClickable = !visible
binding.progressLayoutId.progress.visibility = if (visible) View.VISIBLE else View.INVISIBLE
}
Use Case Example Refresh
We want to allow the user to refresh the data. In our example it doesn't make much sense as the Covid Results only change daily but hopefully you get the idea.
When the user pulls down on the list of countries we want the screen to
- start showing the SwipeRefreshLayout
- load new data
- stop showing the SwipeRefreshLayout
To achieve this we need to
- Create Hook in view
- Create Delegate in Presenter
Create Hook in view
In the View (Fragment) hook up the pulling of the recylerview to the view (fragment)
override fun intentRefreshData(): Observable<Boolean> =
binding.swipeRefreshLayout.refreshes().map { true }
Create Delegate in Presenter
This is the function to call each time a observable is omitted
val refreshDataDelegate =
view.intentRefreshData().flatMap {
refreshCountryResultPresenter()
}
Create function in Presenter
This is the function to call and is responsible for
- Calling the use use with the request
- Managing the repsonse in the UI
Refreshing
Android provides the SwipeRefreshLayout which is demonstrated below
Quite liked the approach of on by default. To implement this we
- Wrap the RecyclerView in it
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/covid_result_recycler_view"
android:layout_width="match_parent"
...
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>