数据更改时不调用 LiveData onChanged

LiveData onChanged not being called when data changes

我刚开始使用 LiveDataViewModels,我不完全了解数据是如何更新的。

根据 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),我在 ViewModelgetItems() 方法中异步获取数据。这是否意味着在数据库中添加或更新数据时,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

您的代码似乎设置正确。但是您的 ObserversonChanged() 方法将被调用一次,因为您的代码中没有任何部分通知它您对基础数据所做的更改。

我建议您使用 MutableLiveDatasetValue()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 进行更新。简单地删除这个值而不发布它不会触发 LivedataObserveronchange() 方法。我希望这对某人有所帮助。