Je suis nouveau dans l'utilisation de LiveData
et ViewModels
et je ne comprends pas parfaitement comment les données sont mises à jour.
D'après le site Android Dev :
Au lieu de mettre à jour l'interface utilisateur chaque fois que les données de l'application changent, votre l'observateur peut mettre à jour l'interface utilisateur à chaque fois qu'il y a un changement.
Cette phrase n'a pas de sens pour moi. J'utilise une base de données Sqlite (sans Room) et j'obtiens les données de manière asynchrone dans la méthode getItems ()
du ViewModel
. Cela signifie-t-il que lorsque des données sont ajoutées ou mises à jour dans la base de données, LiveData
les détecte automatiquement et onChange
est appelé?
Selon le développeur Android site, il dit de commencer à observer un objet LiveData
dans onCreate
. Avant d'utiliser LiveData
, j'actualisais les données dans onResume
en interrogeant la base de données puis en appelant notifyDataSetChanged()
.
Ceci le code que j'ai maintenant affiche les données lorsque le Fragment
se charge, mais si je supprime ou ajoute de nouvelles données à la base de données, l'interface utilisateur ne reflète pas le changement. Je pense juste que je n'ai pas une compréhension de base du fonctionnement de LiveData
et je ne trouve nulle part une bonne explication.
Aidez-moi StackOverflow, vous êtes mon seul espoir .
public class MyViewModel extends AndroidViewModel { private MutableLiveData<List<String>> items; private Application application; public MyViewModel(@NonNull Application application) { super(application); this.application = application; } public MutableLiveData<List<String>> getItems() { if (items == null) { items = new MutableLiveData<>(); getItems(); } return items; } private void getItems() { List<String> items = GetItems(); //async process this.items.setValue(items) } } public class ListFragment extends Fragment { @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyViewModel model = ViewModelProviders.of(getActivity()).get(MyViewModel.class); final Observer<List<String>> observer = new Observer<List<String>>() { @Override public void onChanged(List<String> items) { //update UI } }; model.getItems().observe(this, observer); } }
3 Réponses :
Votre code semble correct. Vous configurez tout correctement et vous recevrez un rappel sur votre Observer
dans le ListFragment
à chaque fois que la valeur de LiveData
change.
Cependant , avec ce code, la valeur LiveData ne changera jamais, donc votre LiveData's Observer ne sera appelé qu'une seule fois. Vous n'appelez MyViewModel.getItems
qu'une seule fois, dans onCreate
et cette méthode fera deux choses: renvoyer les LiveData ET récupérer la liste des éléments.
Une première approche pour vous serait de diviser votre méthode ViewModel getItems
en deux: une pour obtenir les LiveData et une pour actualiser la liste actuelle des éléments.
Dans le fragment, vous actualiseriez ensuite vos données dans onResume ()
, comme vous le faisiez auparavant.
Cela ressemblerait à ceci:
public class MyViewModel extends ViewModel { private final MutableLiveData<List<String>> items = new MutableLiveData<>(); public MyViewModel() { // In actual code, don't forget to dispose that subscription in ViewModel.onCleared() myDatabase.getDatabaseObservable() .subscribe(new Subscriber<List<String>>() { @Override public void onCompleted() { } @Override public void onNext(List<String> newItems) { items.setValue(newItems) } @Override public void onError() { } }) } public LiveData<List<String>> getItemsLiveData() { return items; } }
Pour aller plus loin, vous devrez changer la façon dont vous actualisez réellement les éléments. Si vous souhaitez être notifié dans votre ViewModel lorsque la base de données est modifiée, vous devrez également utiliser un pattern Observer dans la communication entre ViewModel et Database (Room with LiveData, RxJava's Observable ou le nouveau coroutine Flow sont de bons points de départ)
Voici un exemple de pseudo-algorithme utilisant RxJava (mais c'est à vous de choisir quelle bibliothèque Observer utiliser):
public class MyViewModel extends ViewModel { private final MutableLiveData<List<String>> items = new MutableLiveData<>(); public LiveData<List<String>> getItemsLiveData() { return items; } public void refreshItems() { List<String> items = GetItems(); //async process this.items.setValue(items) } } public class ListFragment extends Fragment { private MyViewModel model; @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); model = ViewModelProviders.of(getActivity()).get(MyViewModel.class); final Observer<List<String>> observer = new Observer<List<String>>() { @Override public void onChanged(List<String> items) { //update UI } }; model.getItemsLiveData().observe(this, observer); } @Override public void onResume() { model.refreshItems() } }
La dernière partie serait de créer cet Observable dans votre classe Database (soit avec une bibliothèque qui le fait automatiquement comme Room, soit en créant votre propre PublishSubject
).
Vous ne récupérez les éléments qu'une seule fois si éléments privés MutableLiveData
n'a pas encore été initialisé: >
public class MyViewModel extends AndroidViewModel { ... public MutableLiveData<List<String>> getItems() { dataRepository.getItems(); } ... }
Une façon de surmonter ce problème Le problème pourrait être d'utiliser ContentObserver
pour vos données:
... private final ContentObserver contentObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { // if there're any changes then perform the request and update the UI getItems(); } }; public LiveData<List<String>> getItems() { return new MutableLiveData<String>() { @Override public void onActive() { // your async operation here new Thread({ List<String> items = db.getItems(); postValue(items); }).start(); contentResolver.registerContentObserver(<your data content uri>, true, contentObserver); } @Override public void onInactive() { contentResolver.unregisterContentObserver(contentObserver); } } return liveData; } ...
N'oubliez pas de l'enregistrer pour être informé des changements dans la base de données: p>
... contentResolver.registerContentObserver(<your data content uri>, true, contentObserver); ...
Je suggère également de créer une couche Repository
distincte responsable de la récupération des données qui pourrait ressembler à ceci:
... private final ContentObserver contentObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { // if there're any changes then perform the request and update the UI getItems(); } }; ...
Votre code semble être configuré correctement. Mais la méthode onChanged ()
de vos Observers
sera appelée une fois car aucune partie de votre code ne l'avertit de la modification que vous apportez aux données sous-jacentes.
Je vous suggère d'informer explicitement votre observateur des modifications des données en utilisant la méthode MutableLiveData
de setValue ()
ou postValue ()
en fonction s'ils sont appelés respectivement depuis le thread principal ou le thread d'arrière-plan. Ou mieux encore, implémentez la base de données ROOM
avec ses objets DAO (Data Access Object).
Voici un extrait de code de l'endroit où j'ai fait quelque chose de similaire si vous ne souhaitez pas utiliser ROOM
pour l'instant:
class HomeViewModel : ViewModel() { private val _budgetList = MutableLiveData<ArrayList<Budget>>() val budgetList: LiveData<ArrayList<Budget>> = _budgetList private val _billList = MutableLiveData<ArrayList<Bill>>() val billList: LiveData<ArrayList<Bill>> = _billList init { _budgetList.value = getBudgetList() _billList.value = getBills() } fun deleteBudget(pos: Int) { _budgetList.apply { val newValue = value?.apply { removeAt(pos) } header position postValue(newValue) } }
}
Après avoir supprimé une valeur de la liste, la nouvelle liste est envoyée au Livedata
pour une mise à jour via la méthode postValue ()
. Le simple fait de supprimer cette valeur sans la publier ne déclenchera pas la méthode onchange ()
du Livedata
de Observer
. J'espère que cela aidera quelqu'un.
Le problème est que vous n'obtenez les éléments qu'une seule fois, dans votre méthode
getItems ()
. Pour que cela fonctionne comme prévu, vous devrez utiliser un DAO de salle et observer le DAO dans le ViewModel. Lorsque l'observateur DAO dans le ViewModel détecte des modifications dans les données sous-jacentes, mettez à jour les LiveData, puis le fragment obtiendra les données mises à jour.Je vois. Donc, si je n'utilise pas Room, est-il judicieux d'utiliser
LiveData
?Pouvez-vous rappeler la fonction
private void getItems ()
une fois que vous avez supprimé ou ajouté des éléments à votre base de données? Si oui, rappelez-le.Puis-je simplement rendre public
getItems ()
?J'ai eu le même problème que l'OP, mais mon problème était que mon Fragment n'obtenait pas la même instance du ViewModel que mon Activité . Plus d'informations ici: stackoverflow .com / questions / 61373456 /… .