单个 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&lt;List&lt;Journal&gt;&gt; getBookmarkedJournals();@Query("SELECT * FROM journal_table WHERE delete_flag=0 ORDER BY date") LiveData&lt;List&lt;Journal&gt;&gt; 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

Mvc 列出嵌套在 Viewmodel 中的多个视图

LiveData“按引用传递”初始值

ViewModel 在导航导航中没有被清除,并且 viewmodel 中的实时数据保持活动状态

如何知道 ViewModel 中单个地图的完成情况?

如何将正确的 viewModel 注入单个 viewController