Android Example App: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
Line 103: Line 103:
             removeFavoriteCountryChoiceDelegate,
             removeFavoriteCountryChoiceDelegate,
         )
         )
</syntaxhighlight>
====Full Attach Example====
Here is the full code for the refresh example.
<syntaxhighlight lang="kotlin">
    override fun attach(view: CovidResultListView) {
...
        val refreshDataDelegate =
            view.intentRefreshData().flatMap {
                refreshDataPresenter()
            }
        subscribeViewModel(
            view,
            refreshDataDelegate,
            refreshCovidResultListDelegate,
            loadCountryPreferencesDelegate,
            removeFavoriteCountryChoiceDelegate,
        )
    }
</syntaxhighlight>
</syntaxhighlight>



Revision as of 06:35, 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 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 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

In the attach function we need to create our delegate functions and add the subscriptions to the model.

Create Delegate in the Presenter

This is the function to call each time an observable is omitted

    val refreshDataDelegate =
        view.intentRefreshData().flatMap {
            refreshCountryResultPresenter()
        }

Add Subscription to the Model for the Delegate

We need to add the delegate to the View Model.

        subscribeViewModel(view,
            refreshCovidResultListDelegate,
            loadCountryPreferencesDelegate,
            removeFavoriteCountryChoiceDelegate,
        )

Full Attach Example

Here is the full code for the refresh example.

    override fun attach(view: CovidResultListView) {
...
        val refreshDataDelegate =
            view.intentRefreshData().flatMap {
                refreshDataPresenter()
            }

        subscribeViewModel(
            view,
            refreshDataDelegate,
            refreshCovidResultListDelegate,
            loadCountryPreferencesDelegate,
            removeFavoriteCountryChoiceDelegate,
        )
    }

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>