单个 ViewModel 中的多个 LiveData 对象
Posted
技术标签:
【中文标题】单个 ViewModel 中的多个 LiveData 对象【英文标题】:Multiple LiveData objects in single ViewModel 【发布时间】:2020-08-11 21:52:11 【问题描述】:我的应用结构如下:
主活动(Activity) containing Bottom Navigation View with three fragments nested below
首页片段(Fragment) containing TabLayout with ViewPager with following two tabs
期刊(Fragment)
书签(Fragment)
片段B(Fragment)
片段C(Fragment)
我正在使用 Room 来维护期刊的所有记录。我在 Journal 和 Bookmarks 片段中分别观察到一个 LiveData 对象。这些 LiveData 对象由我的 JournalViewModel
类返回。
JournalDatabase.java
public abstract class JournalDatabase extends RoomDatabase
private static final int NUMBER_OF_THREADS = 4;
static final ExecutorService dbWriteExecutor = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
private static JournalDatabase INSTANCE;
static synchronized JournalDatabase getInstance(Context context)
if (INSTANCE == null)
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), JournalDatabase.class, "main_database")
.fallbackToDestructiveMigration()
.build();
return INSTANCE;
public abstract JournalDao journalDao();
JournalRepository.java
public class JournalRepository
private JournalDao journalDao;
private LiveData<List<Journal>> allJournals;
private LiveData<List<Journal>> bookmarkedJournals;
public JournalRepository(Application application)
JournalDatabase journalDatabase = JournalDatabase.getInstance(application);
journalDao = journalDatabase.journalDao();
allJournals = journalDao.getJournalsByDate();
bookmarkedJournals = journalDao.getBookmarkedJournals();
public void insert(Journal journal)
JournalDatabase.dbWriteExecutor.execute(() ->
journalDao.insert(journal);
);
public void update(Journal journal)
JournalDatabase.dbWriteExecutor.execute(() ->
journalDao.update(journal);
);
public void delete(Journal journal)
JournalDatabase.dbWriteExecutor.execute(() ->
journalDao.delete(journal);
);
public void deleteAll()
JournalDatabase.dbWriteExecutor.execute(() ->
journalDao.deleteAll();
);
public LiveData<List<Journal>> getAllJournals()
return allJournals;
public LiveData<List<Journal>> getBookmarkedJournals()
return bookmarkedJournals;
JournalViewModel.java
public class JournalViewModel extends androidViewModel
private JournalRepository repository;
private LiveData<List<Journal>> journals;
private LiveData<List<Journal>> bookmarkedJournals;
public JournalViewModel(@NonNull Application application)
super(application);
repository = new JournalRepository(application);
journals = repository.getAllJournals();
bookmarkedJournals = repository.getBookmarkedJournals();
public void insert(Journal journal)
repository.insert(journal);
public void update(Journal journal)
repository.update(journal);
public void delete(Journal journal)
repository.delete(journal);
public void deleteAll()
repository.deleteAll();
public LiveData<List<Journal>> getAllJournals()
return journals;
public LiveData<List<Journal>> getBookmarkedJournals()
return bookmarkedJournals;
我在两个 Fragment 的 onActivityCreated()
方法中实例化这个 ViewModel。
JournalFragment.java
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState)
super.onActivityCreated(savedInstanceState);
JournalFactory factory = new JournalFactory(requireActivity().getApplication());
journalViewModel = new ViewModelProvider(requireActivity(), factory).get(JournalViewModel.class);
journalViewModel.getAllJournals().observe(getViewLifecycleOwner(), new Observer<List<Journal>>()
@Override
public void onChanged(List<Journal> list)
journalAdapter.submitList(list);
);
BookmarksFragment.java
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState)
super.onActivityCreated(savedInstanceState);
JournalFactory factory = new JournalFactory(requireActivity().getApplication());
journalViewModel = new ViewModelProvider(requireActivity(), factory).get(JournalViewModel.class);
journalViewModel.getBookmarkedJournals().observe(getViewLifecycleOwner(), new Observer<List<Journal>>()
@Override
public void onChanged(List<Journal> list)
adapter.submitList(list);
);
但是,当我使用这种方法时,问题是当我删除时,对任何 Fragment 进行一些更改,例如删除或更新某些日志,其他日志的日期字段会随机更改。
我能够通过使用单个 LiveData 对象并在两个片段中观察它来解决这个问题。我必须在BookmarkFragment
中进行的更改如下:
BookmarksFragment.java
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState)
super.onActivityCreated(savedInstanceState);
JournalFactory factory = new JournalFactory(requireActivity().getApplication());
journalViewModel = new ViewModelProvider(requireActivity(), factory).get(JournalViewModel.class);
journalViewModel.getAllJournals().observe(getViewLifecycleOwner(), new Observer<List<Journal>>()
@Override
public void onChanged(List<Journal> list)
List<Journal> bookmarkedJournals = new ArrayList<>();
for (int i = 0; i < list.size(); i++)
if (list.get(i).getBookmark() == 1)
bookmarkedJournals.add(list.get(i));
adapter.submitList(bookmarkedJournals);
);
现在可以正常使用了。
但是,我想知道为什么使用我的第一种方法不起作用,即使用两个不同的 LiveData 对象并在不同的片段中观察它们。
多个 LiveData 对象是否不应该在单个 ViewModel 中使用?
或
在进行更改并同时从同一个表中获取不同的 LiveData 对象时,是否不允许同一 ViewModel 的两个实例同时存在?
【问题讨论】:
在 ViewModel 构造函数中实例化一个全新的 JournalRepository 表明您可能不会将 RoomDatabase 实例作为全局单例处理,并且默认情况下多个实例不会相互通信。如果没有看到更多代码,很难确定这是问题所在。见developer.android.com/training/data-storage/room底部的注释 @krage 我将数据库用作单例。application
参数检查任何现有的数据库实例,如果存在则返回。
那很好。将这个 JournalRepository
类本身也视为单例可能是个好主意。如果不查看该存储库类和您的 db/dao 类,很难说更多关于这个或其他问题行为的信息。
@krage 我已经添加了存储库和数据库类代码。 Dao 没有那么复杂,所以没有添加它以减少帖子中的代码。 @Query("SELECT * FROM journal_table WHERE bookmark=1 AND delete_flag=0 ORDER BY date DESC") LiveData<List<Journal>> getBookmarkedJournals();
和 @Query("SELECT * FROM journal_table WHERE delete_flag=0 ORDER BY date") LiveData<List<Journal>> getAllJournals();
是我用来检索记录的方法。
在这一点上,我猜这个问题不一定与 LiveData 相关。我会尽量减少不必要的交互的复杂性和可能性 - 将 db Executor 线程减少到 1,不要在片段之间共享视图模型,或者不要在存储库中缓存 LiveData 实例,或者只是绕过存储库作为它是 Dao 上的一个瘦包装器,然后使用调试器来检查 LiveData Observers 在每个片段中实际接收到的内容与您期望的内容。或许也可以查看适配器。
【参考方案1】:
我找到了导致这个问题的原因。
当我使用 LiveData 和 getViewLifecycleOwner()
作为 LifecycleOwner
时,我作为参数传递的观察者永远不会被删除。因此,在切换到不同的选项卡后,有两个活跃的观察者观察同一 ViewModel 的不同 LiveData
对象。
解决此问题的方法是将LiveData
对象存储在一个变量中,然后在您切换到不同的片段时移除观察者。
在我的场景中,我通过执行以下操作解决了这个问题:
//store LiveData object in a variable
LiveData<List<Journal>> currentLiveData = journalViewModel.getAllJournals();
//observe this livedata object
currentLiveData.observer(observer);
然后在合适的生命周期方法或任何适合您需要的地方删除此观察者,例如
@Override
public void onDestroyView()
super.onDestroyView();
//if you want to remove all observers
currentLiveData.removeObservers(getViewLifecycleOwner());
//if you want to remove particular observers
currentLiveData.removeObserver(observer);
【讨论】:
以上是关于单个 ViewModel 中的多个 LiveData 对象的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Knockout.js 将多个 View 绑定到单个 ViewModel