web-dev-qa-db-fra.com

Composants d'architecture Android: lier à ViewModel

Je suis un peu confus quant à la manière dont la liaison de données devrait fonctionner lors de l'utilisation des nouveaux composants d'architecture.

disons que j'ai une simple activité avec une liste, un ProgressBar et un TextView. L'activité doit être responsable du contrôle de l'état de toutes les vues, mais le ViewModel doit contenir les données et la logique. Par exemple, mon activité ressemble à ceci:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

    listViewModel = ViewModelProviders.of(this).get(ListViewModel.class);

    binding.setViewModel(listViewModel);

    list = findViewById(R.id.games_list);

    listViewModel.getList().observeForever(new Observer<List<Game>>() {
        @Override
        public void onChanged(@Nullable List<Game> items) {
            setUpList(items);
        }
    });

    listViewModel.loadGames();
}

private void setUpList(List<Game> items){
    list.setLayoutManager(new LinearLayoutManager(this));
    GameAdapter adapter = new GameAdapter();
    adapter.setList(items);
    list.setAdapter(adapter);
}

et ViewModel est le seul responsable du chargement des données et de la notification de l'activité lorsque la liste est prête afin qu'il puisse préparer l'adaptateur et afficher les données:

public int progressVisibility = View.VISIBLE;

private MutableLiveData<List<Game>> list;

public void loadGames(){

    Retrofit retrofit = GamesAPI.create();

    GameService service = retrofit.create(GameService.class);

    Call<GamesResponse> call = service.fetchGames();

    call.enqueue(this);
}


@Override
public void onResponse(Call<GamesResponse> call, Response<GamesResponse> response) {
    if(response.body().response.equals("success")){
        setList(response.body().data);

    }
}

@Override
public void onFailure(Call<GamesResponse> call, Throwable t) {

}

public MutableLiveData<List<Game>> getList() {
    if(list == null)
        list = new MutableLiveData<>();
    if(list.getValue() == null)
        list.setValue(new ArrayList<Game>());
    return list;
}

public void setList(List<Game> list) {
    this.list.postValue(list);
}

Ma question est: quelle est la bonne façon d'afficher/masquer la liste, la barre de progression et le texte d'erreur?

devrais-je ajouter un entier pour chaque vue dans ViewModel, ce qui permet de contrôler les vues et de l'utiliser de la manière suivante:

<TextView
    Android:id="@+id/main_list_error"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:text="@{viewModel.error}"
    Android:visibility="@{viewModel.errorVisibility}" />

ou ViewModel doit-il instancier un objet LiveData pour chaque propriété:

private MutableLiveData<Integer> progressVisibility = new MutableLiveData<>();
private MutableLiveData<Integer> listVisibility = new MutableLiveData<>();
    private MutableLiveData<Integer> errorVisibility = new MutableLiveData<>();

mettre à jour leur valeur si nécessaire et faire en sorte que l'activité observe leur valeur?

viewModel.getProgressVisibility().observeForever(new Observer<Integer>() {
    @Override
    public void onChanged(@Nullable Integer visibility) {
        progress.setVisibility(visibility);
    }
});

viewModel.getListVisibility().observeForever(new Observer<Integer>() {
    @Override
    public void onChanged(@Nullable Integer visibility) {
        list.setVisibility(visibility);
    }
});

viewModel.getErrorVisibility().observeForever(new Observer<Integer>() {
    @Override
    public void onChanged(@Nullable Integer visibility) {
        error.setVisibility(visibility);
    }
});

J'ai vraiment du mal à comprendre cela. Si quelqu'un peut clarifier cela, ce serait formidable.

Merci

7
jack_the_beast

Voici des étapes simples:

public class MainViewModel extends ViewModel {

    MutableLiveData<ArrayList<Game>> gamesLiveData = new MutableLiveData<>();
    // ObservableBoolean or ObservableField are classes from  
    // databinding library (Android.databinding.ObservableBoolean)

    public ObservableBoolean progressVisibile = new ObservableBoolean();
    public ObservableBoolean listVisibile = new ObservableBoolean();
    public ObservableBoolean errorVisibile = new ObservableBoolean();
    public ObservableField<String> error = new ObservableField<String>();

    // ...


    // For example we want to change list and progress visibility
    // We should just change ObservableBoolean property
    // databinding knows how to bind view to changed of field

    public void loadGames(){
        GamesAPI.create().create(GameService.class)
            .fetchGames().enqueue(this);

        listVisibile.set(false); 
        progressVisibile.set(true);
    }

    @Override
    public void onResponse(Call<GamesResponse> call, Response<GamesResponse> response) {
        if(response.body().response.equals("success")){
            gamesLiveData.setValue(response.body().data);

            listVisibile.set(true);
            progressVisibile.set(false);
        }
    }

}

Et alors

<data>
    <import type="Android.view.View"/>

    <variable
        name="viewModel"
        type="MainViewModel"/>
</data>

...

<ProgressBar
    Android:layout_width="32dp"
    Android:layout_height="32dp"
    Android:visibility="@{viewModel.progressVisibile ? View.VISIBLE : View.GONE}"/>

<ListView
    Android:layout_width="32dp"
    Android:layout_height="32dp"
    Android:visibility="@{viewModel.listVisibile ? View.VISIBLE : View.GONE}"/>

<TextView
    Android:id="@+id/main_list_error"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:text="@{viewModel.error}"
    Android:visibility="@{viewModel.errorVisibile ? View.VISIBLE : View.GONE}"/>

Notez également que c'est votre choix de faire observer observer 

ObservableBoolean : false / true 
    // or
ObservableInt : View.VISIBLE / View.INVISIBLE / View.GONE

mais ObservableBoolean est préférable pour les tests ViewModel.

Vous devez également observer LiveData en considérant le cycle de vie:

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    listViewModel.getList().observe((LifecycleOwner) this, new Observer<List<Game>>() {
        @Override
        public void onChanged(@Nullable List<Game> items) {
            setUpList(items);
        }
    });
}
5
Yuriy Kot

Voici quelques étapes simples pour atteindre votre objectif.

Tout d'abord , demandez à votre ViewModel d'exposer un objet LiveData et de pouvoir démarrer LiveData avec une valeur vide.

private MutableLiveData<List<Game>> list = new MutableLiveData<>();

public MutableLiveData<List<Game>> getList() {
    return list;
}

Deuxièmement , demandez à votre vue (activité/fragment) d’observer LiveData et de modifier l’UI en conséquence.

listViewModel = ViewModelProviders.of(this).get(ListViewModel.class);
listViewModel.data.observe(this, new Observer<List<Game>>() {
    @Override
    public void onChanged(@Nullable final List<Game> games) {
        setUpList(games);
    }
});

Ici, il est important que vous utilisiez la variante observe(LifecycleOwner, Observer) pour que votre observateur NE reçoive PAS d’événements après que LifecycleOwner n’est plus actif. En gros, cela signifie que lorsque votre activité de fragment n’est plus active, vous ne perdez pas cet écouteur.

Troisièmement , à la suite de la mise à disposition des données, vous devez mettre à jour votre objet LiveData.

@Override
public void onResponse(Call<GamesResponse> call, Response<GamesResponse> response) {
    if(response.body().response.equals("success")){
        List<Game> newGames = response.body().data; // Assuming this is a list
        list.setValue(newGames); // Update your LiveData object by calling setValue
    }
}

En appelant setValue() sur votre LiveData, ceci entraînera l'appel de onChanged sur l'écouteur de votre vue et votre interface utilisateur devrait être mise à jour automatiquement.

0
elmorabea