简体   繁体   中英

Multiple LiveData objects in single ViewModel

The structure of my application is as follows:

  • MainActivity (Activity) containing Bottom Navigation View with three fragments nested below
    • HomeFragment (Fragment) containing TabLayout with ViewPager with following two tabs
      • Journal (Fragment)
      • Bookmarks (Fragment)
    • Fragment B (Fragment)
    • Fragment C (Fragment)

I am using Room to maintain all the records of journals. I'm observing one LiveData object each in Journal and Bookmarks fragment. These LiveData objects are returned by my JournalViewModel class.

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

I'm instantiating this ViewModel inside onActivityCreated() method of both Fragments.

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

However, the problem when I use this approach is as I delete make some changes in any of the Fragment like delete or update some Journal some other Journal's date field changes randomly.

I was able to solve this issue by using single LiveData object and observe it in both fragments. The changes I had to make in BookmarkFragment is as follows:

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

It works properly now.

However, I want to know why it didn't work using my first approach which was to use two different LiveData objects and observe them in different fragments.

Are multiple LiveData objects not meant to be used in single ViewModel?

OR

Are two instances of same ViewModel not allowed to exist together while making changes and fetching different LiveData objects from the same table simultaneously?

I found out the reason causing this problem.

As I was using LiveData with getViewLifecycleOwner() as the LifecycleOwner , the observer I passed as parameter was never getting removed. So, after switching to a different tab, there were two active observers observing different LiveData objects of same ViewModel.

The way this issue can be solved is by storing the LiveData object in a variable then removing the observer as you switch to different fragment.

In my scenario, I solved this issue by doing the following:

//store LiveData object in a variable
LiveData<List<Journal>> currentLiveData = journalViewModel.getAllJournals();

//observe this livedata object
currentLiveData.observer(observer);

Then remove this observer in a suitable Lifecycle method or anywhere that suits your needs like

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM