数据更改时不调用 LiveData onChanged
LiveData onChanged not being called when data changes
我刚开始使用 LiveData
和 ViewModels
,我不完全了解数据是如何更新的。
根据 Android Dev site:
Instead of updating the UI every time the app data changes, your
observer can update the UI every time there's a change.
这句话对我来说没有意义。我正在使用 Sqlite 数据库(没有 Room),我在 ViewModel
的 getItems()
方法中异步获取数据。这是否意味着在数据库中添加或更新数据时,LiveData
会自动检测并调用 onChange
?
根据 Android 开发网站,它说要开始观察 onCreate
中的一个 LiveData
对象。在使用 LiveData
之前,我通过查询数据库然后调用 notifyDataSetChanged()
.
来刷新 onResume
中的数据
我的这段代码现在在 Fragment
加载时显示数据,但是如果我删除或向数据库添加新数据,UI 不会反映更改。我只是觉得我对 LiveData
的工作原理没有基本的了解,而且我在任何地方都找不到很好的解释。
帮助我 Whosebug,你是我唯一的希望。
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);
}
}
您的代码似乎是正确的。您正在正确设置所有内容,每次 LiveData
值更改时,您都会在 ListFragment
中的 Observer
上收到回调。
但是,使用该代码,LiveData 值永远不会改变,因此您的 LiveData 的 Observer 只会被调用一次。您只在 onCreate
中调用 MyViewModel.getItems
一次,此方法将做两件事:return LiveData 和获取项目列表。
您的第一种方法是将 ViewModel getItems
方法分为两部分:一个用于获取 LiveData,一个用于刷新实际的项目列表。
在 Fragment 中,您将像以前一样在 onResume()
中刷新数据。
看起来像这样:
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()
}
}
要更进一步,您需要更改实际刷新项目的方式。如果你想在数据库修改时在你的 ViewModel 中得到通知,你还需要在 ViewModel 和数据库之间的通信中使用观察者模式(Room with LiveData、RxJava 的 Observable 或新的协程 Flow 是很好的起点)
这里是一个使用 RxJava 的伪算法示例(但这取决于您选择使用哪个 Observer 库):
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;
}
}
最后一部分是在您的数据库中创建 Observable class(使用像 Room 一样自动执行它的库,或者创建您自己的 PublishSubject
)。
如果 private MutableLiveData<List<String>> items
尚未初始化,您将仅检索一次项目:
public MutableLiveData<List<String>> getItems() {
if (items == null) {
items = new MutableLiveData<>();
getItems();// it's called only once
}
return items;
}
解决此问题的一种方法是对您的数据使用 ContentObserver
:
...
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();
}
};
...
不要忘记注册它以获得数据库更改的通知:
...
contentResolver.registerContentObserver(<your data content uri>, true, contentObserver);
...
我还建议创建一个单独的 Repository
层负责数据检索,如下所示:
...
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;
}
...
然后,您的 ViewModel
将需要注入存储库对象,因此需要 ViewModelFactory
构建您的 ViewModel
的实例,但总体用法类似于:
public class MyViewModel extends AndroidViewModel {
...
public MutableLiveData<List<String>> getItems() {
dataRepository.getItems();
}
...
}
要获取更多信息,请阅读 Guide to app architecture
您的代码似乎设置正确。但是您的 Observers
的 onChanged()
方法将被调用一次,因为您的代码中没有任何部分通知它您对基础数据所做的更改。
我建议您使用 MutableLiveData
的 setValue()
或 postValue()
方法明确通知您的观察者数据的变化,具体取决于它们是否从主线程调用或后台线程分别。或者更好的办法是使用其数据访问对象 (DAO) 对象实现 ROOM
数据库。
如果您还不愿意使用 ROOM
,这里是我做了类似事情的代码片段:
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)
}
}
}
从列表中删除一个值后,新列表将通过 postValue()
方法发送到 Livedata
进行更新。简单地删除这个值而不发布它不会触发 Livedata
的 Observer
的 onchange()
方法。我希望这对某人有所帮助。
我刚开始使用 LiveData
和 ViewModels
,我不完全了解数据是如何更新的。
根据 Android Dev site:
Instead of updating the UI every time the app data changes, your observer can update the UI every time there's a change.
这句话对我来说没有意义。我正在使用 Sqlite 数据库(没有 Room),我在 ViewModel
的 getItems()
方法中异步获取数据。这是否意味着在数据库中添加或更新数据时,LiveData
会自动检测并调用 onChange
?
根据 Android 开发网站,它说要开始观察 onCreate
中的一个 LiveData
对象。在使用 LiveData
之前,我通过查询数据库然后调用 notifyDataSetChanged()
.
onResume
中的数据
我的这段代码现在在 Fragment
加载时显示数据,但是如果我删除或向数据库添加新数据,UI 不会反映更改。我只是觉得我对 LiveData
的工作原理没有基本的了解,而且我在任何地方都找不到很好的解释。
帮助我 Whosebug,你是我唯一的希望。
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);
}
}
您的代码似乎是正确的。您正在正确设置所有内容,每次 LiveData
值更改时,您都会在 ListFragment
中的 Observer
上收到回调。
但是,使用该代码,LiveData 值永远不会改变,因此您的 LiveData 的 Observer 只会被调用一次。您只在 onCreate
中调用 MyViewModel.getItems
一次,此方法将做两件事:return LiveData 和获取项目列表。
您的第一种方法是将 ViewModel getItems
方法分为两部分:一个用于获取 LiveData,一个用于刷新实际的项目列表。
在 Fragment 中,您将像以前一样在 onResume()
中刷新数据。
看起来像这样:
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()
}
}
要更进一步,您需要更改实际刷新项目的方式。如果你想在数据库修改时在你的 ViewModel 中得到通知,你还需要在 ViewModel 和数据库之间的通信中使用观察者模式(Room with LiveData、RxJava 的 Observable 或新的协程 Flow 是很好的起点)
这里是一个使用 RxJava 的伪算法示例(但这取决于您选择使用哪个 Observer 库):
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;
}
}
最后一部分是在您的数据库中创建 Observable class(使用像 Room 一样自动执行它的库,或者创建您自己的 PublishSubject
)。
如果 private MutableLiveData<List<String>> items
尚未初始化,您将仅检索一次项目:
public MutableLiveData<List<String>> getItems() {
if (items == null) {
items = new MutableLiveData<>();
getItems();// it's called only once
}
return items;
}
解决此问题的一种方法是对您的数据使用 ContentObserver
:
...
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();
}
};
...
不要忘记注册它以获得数据库更改的通知:
...
contentResolver.registerContentObserver(<your data content uri>, true, contentObserver);
...
我还建议创建一个单独的 Repository
层负责数据检索,如下所示:
...
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;
}
...
然后,您的 ViewModel
将需要注入存储库对象,因此需要 ViewModelFactory
构建您的 ViewModel
的实例,但总体用法类似于:
public class MyViewModel extends AndroidViewModel {
...
public MutableLiveData<List<String>> getItems() {
dataRepository.getItems();
}
...
}
要获取更多信息,请阅读 Guide to app architecture
您的代码似乎设置正确。但是您的 Observers
的 onChanged()
方法将被调用一次,因为您的代码中没有任何部分通知它您对基础数据所做的更改。
我建议您使用 MutableLiveData
的 setValue()
或 postValue()
方法明确通知您的观察者数据的变化,具体取决于它们是否从主线程调用或后台线程分别。或者更好的办法是使用其数据访问对象 (DAO) 对象实现 ROOM
数据库。
如果您还不愿意使用 ROOM
,这里是我做了类似事情的代码片段:
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)
}
}
}
从列表中删除一个值后,新列表将通过 postValue()
方法发送到 Livedata
进行更新。简单地删除这个值而不发布它不会触发 Livedata
的 Observer
的 onchange()
方法。我希望这对某人有所帮助。