Android Example App: Difference between revisions
Line 109: | Line 109: | ||
*Calling the use use with the request | *Calling the use use with the request | ||
*Managing the repsonse in the UI | *Managing the repsonse in the UI | ||
private fun refreshDataPresenter(): Observable<CovidResultListViewModel> = | |||
getSelectedCountryChoiceListUseCase.execute().toObservable() | |||
.flatMap { | |||
getListCovidResultUseCase.execute(it.map { it.alpha2Code }).toObservable() | |||
.map { CovidResultListViewModel.createData(it) } | |||
.onErrorReturn { | |||
CovidResultListViewModel.createSnack(getErrorMessage(it)) | |||
} | |||
} | |||
==Refreshing== | ==Refreshing== |
Revision as of 09:34, 19 March 2021
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 Views
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 the View (Fragment)
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 }
Attach and Create Function to Call (Presenter)
Attach
In the attach function we need to create our delegate functions and add the subscriptions to the model.
override fun attach(view: CovidResultListView) {
...
val refreshDataDelegate =
view.intentRefreshData().flatMap {
refreshDataPresenter()
}
subscribeViewModel(
view,
refreshDataDelegate,
refreshCovidResultListDelegate,
loadCountryPreferencesDelegate,
removeFavoriteCountryChoiceDelegate,
)
}
Create function
This is the function to call and is responsible for
- Calling the use use with the request
- Managing the repsonse in the UI
private fun refreshDataPresenter(): Observable<CovidResultListViewModel> = getSelectedCountryChoiceListUseCase.execute().toObservable() .flatMap { getListCovidResultUseCase.execute(it.map { it.alpha2Code }).toObservable() .map { CovidResultListViewModel.createData(it) } .onErrorReturn { CovidResultListViewModel.createSnack(getErrorMessage(it)) } }
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>