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
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);
}
});
}
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.