【问题标题】:LiveData onChanged not being called when data changes数据更改时未调用 LiveData onChanged
【发布时间】:2020-04-23 11:09:44
【问题描述】:

我不熟悉使用LiveData 和ViewModels,我不完全了解数据是如何更新的。

根据Android Dev site:

您无需在每次应用数据更改时都更新 UI,而是 观察者可以在每次发生变化时更新 UI。

这句话对我来说没有意义。我正在使用 Sqlite 数据库(没有 Room),并且我在 ViewModel 的 getItems() 方法中异步获取数据。这是否意味着在数据库中添加或更新数据时,LiveData 会自动检测并调用onChange?

根据 Android 开发网站,它说要开始观察 onCreate 中的 LiveData 对象。在使用LiveData之前,我通过查询数据库然后调用notifyDataSetChanged()来刷新onResume中的数据。

此代码现在在Fragment 加载时显示数据,但如果我删除或向数据库添加新数据,UI 不会反映更改。我只是觉得我对LiveData 的工作原理没有基本的了解,而且我在任何地方都找不到好的解释。

帮助我 StackOverflow,你是我唯一的希望。

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

}

【问题讨论】:

  • 问题是你只能在getItems() 方法中得到这些项目一次。要使其按预期工作,您需要使用 Room DAO 并观察 ViewModel 中的 DAO。当 ViewModel 中的 DAO 观察者检测到底层数据发生变化时,然后更新 LiveData,然后 Fragment 将获取更新后的数据。
  • 我明白了。因此,如果我不使用 Room,使用 LiveData 是否有意义?
  • 在数据库中删除或添加元素后,是否可以再次调用private void getItems() 函数?如果是,请再次调用。
  • 我可以将getItems()公开吗?
  • 我遇到了与 OP 相同的问题,但我的问题是我的 Fragment 没有获得与我的 Activity 相同的 ViewModel 实例曾是。更多信息:stackoverflow.com/questions/61373456/… .

标签: android android-livedata android-viewmodel


【解决方案1】:

如果 private MutableLiveData&lt;List&lt;String&gt;&gt; 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

【讨论】:

    【解决方案2】:

    您的代码似乎正确。您正在正确设置所有内容,每次LiveData 值更改时,您都会在ListFragment 中收到Observer 的回调。

    但是,使用该代码,LiveData 值将永远不会改变,因此您的 LiveData 的 Observer 只会被调用一次。您只在onCreate 中调用MyViewModel.getItems 一次,此方法将做两件事:返回 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;
        }
    }
    

    最后一部分是在您的 Database 类中创建 Observable(使用像 Room 一样自动执行此操作的库或创建您自己的 PublishSubject)。

    【讨论】:

      【解决方案3】:

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

      我建议您使用MutableLiveData 的setValue() 或postValue() 方法明确通知您的观察者数据的变化,具体取决于它们是分别从主线程还是后台线程调用。或者更好地使用其数据访问对象 (DAO) 对象实现 ROOM 数据库。

      如果你还不愿意使用ROOM,这里有一个代码sn-p,我在其中做了类似的事情:

      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() 方法。我希望这对某人有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-08-10
        • 1970-01-01
        • 1970-01-01
        • 2019-01-18
        • 1970-01-01
        • 2018-10-02
        • 1970-01-01
        相关资源
        最近更新 更多