web-dev-qa-db-fra.com

Communication entre view et ViewModel dans MVVM avec LiveData

Quelle est la bonne façon de communiquer entre le ViewModel et le View, Google architecture components donner une utilisation LiveData dans laquelle la vue souscrit aux modifications et se met à jour en conséquence, mais cette communication ne convient pas pour des événements uniques, par exemple afficher le message, afficher la progression, masquer la progression, etc.

Il existe des hacks comme SingleLiveEvent dans l'exemple de Google, mais cela ne fonctionne que pour 1 observateur. Certains développeurs utilisent EventBus mais je pense que cela peut rapidement devenir hors de contrôle lorsque le projet se développe.

Existe-t-il un moyen pratique et correct de le mettre en œuvre, comment le mettez-vous en œuvre?

(les exemples Java sont également les bienvenus)

5
Pavel Poley

Pour afficher/masquer les boîtes de dialogue de progression et afficher les messages d'erreur d'un appel réseau ayant échoué lors du chargement de l'écran, vous pouvez utiliser un wrapper qui encapsule les LiveData observées par la vue.

Les détails de cette méthode se trouvent dans l'addendum à l'architecture de l'application: https://developer.Android.com/jetpack/docs/guide#addendum

Définissez une ressource:

data class Resource<out T> constructor(
        val state: ResourceState,
        val data: T? = null,
        val message: String? = null
)

Et un ResourceState:

sealed class ResourceState {
    object LOADING : ResourceState()
    object SUCCESS : ResourceState()
    object ERROR : ResourceState()
}

Dans le ViewModel, définissez vos LiveData avec le modèle enveloppé dans une ressource:

val exampleLiveData = MutableLiveData<Resource<ExampleModel>>()

Toujours dans le ViewModel, définissez la méthode qui fait l'appel API pour charger les données pour l'écran actuel:

fun loadDataForView() = compositeDisposable.add(
        exampleUseCase.exampleApiCall()
                .doOnSubscribe {
                    exampleLiveData.setLoading()
                }
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        {
                            exampleLiveData.setSuccess(it)
                        },
                        {
                            exampleLiveData.setError(it.message)
                        }
                )
)

Dans la vue, configurez l'observateur lors de la création:

    viewModel.exampleLiveData.observe(this, Observer {
        updateResponse(it)
    })

Voici l'exemple de la méthode updateResponse(), montrant/masquant la progression et affichant une erreur le cas échéant:

private fun updateResponse(resource: Resource<ExampleModel>?) {
    resource?.let {
        when (it.state) {
            ResourceState.LOADING -> {
                showProgress()
            }
            ResourceState.SUCCESS -> {
                hideProgress()
                // Use data to populate data on screen
                // it.data will have the data of type ExampleModel

            }
            ResourceState.ERROR -> {
                hideProgress()
                // Show error message
                // it.message will have the error message
            }
        }
    }
}
0
Daniel Nugent

essaye ça:

/**
 * Used as a wrapper for data that is exposed via a LiveData that represents an event.
 */
open class Event<out T>(private val content: T) {

    var hasBeenHandled = false
        private set // Allow external read but not write

    /**
     * Returns the content and prevents its use again.
     */
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    /**
     * Returns the content, even if it's already been handled.
     */
    fun peekContent(): T = content
}

Et enveloppez-le dans LiveData

class ListViewModel : ViewModel {
    private val _navigateToDetails = MutableLiveData<Event<String>>()

    val navigateToDetails : LiveData<Event<String>>
        get() = _navigateToDetails


    fun userClicksOnButton(itemId: String) {
        _navigateToDetails.value = Event(itemId)  // Trigger the event by setting a new Event as a new value
    }
}

Et observez

myViewModel.navigateToDetails.observe(this, Observer {
    it.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled
        startActivity(DetailsActivity...)
    }
})

référence du lien: tiliser un wrapper d'événement

0
Tuan Dao