Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Post or Set MutableLiveData - Observer onChanged not called

I discover the new android architecture component and I want to test the couple ViewModel / LiveData through a small test application. The latter has two fragments (in a ViewPager), the first creates/updates a list of cards (via an EditText) and the second displays all the cards.

My ViewModel:


public class CardsScanListViewModel extends AndroidViewModel {

    private MutableLiveData> cardsLiveData = new MutableLiveData();
    private HashMap cardsMap = new HashMap();

    public CardsScanListViewModel(@NonNull Application application) {
        super(application);
    }

    public MutableLiveData> getCardsLiveData() {
        return this.cardsLiveData;
    }

    public void saveOrUpdateCard(String id) {
        if(!cardsMap.containsKey(id)) {
            cardsMap.put(id, new Card(id, new AtomicInteger(0)));
        }
        cardsMap.get(id).getCount().incrementAndGet();
        this.cardsLiveData.postValue(cardsMap);
    }
}

My second fragment:

public class CardsListFragment extends Fragment {

    CardsAdapter cardsAdapter;

    RecyclerView recyclerCardsList;

    public CardsListFragment() {}

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final CardsScanListViewModel viewModel =
                ViewModelProviders.of(this).get(CardsScanListViewModel.class);

        observeViewModel(viewModel);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_cards_list, container, false);
        recyclerCardsList = v.findViewById(R.id.recyclerCardsList);
        recyclerCardsList.setLayoutManager(new LinearLayoutManager(getActivity()));
        cardsAdapter = new CardsAdapter(getActivity());
        recyclerCardsList.setAdapter(cardsAdapter);

        return v;
    }

    private void observeViewModel(CardsScanListViewModel viewModel) {
        viewModel.getCardsLiveData().observe(this, new Observer  > () {
            @Override
            public void onChanged(@Nullable HashMap  cards) {
                if (cards != null) {
                    cardsAdapter.setCardsList(cards.values());
                }
            }
        });
    }
}

TheHashMap, like my MutableLiveData, update well but my second fragment doesn't receive the information via observer.

like image 399
giles jeremy Avatar asked Dec 30 '17 10:12

giles jeremy


People also ask

What is MutableLiveData in android?

The MutableLiveData class exposes the setValue(T) and postValue(T) methods publicly and you must use these if you need to edit the value stored in a LiveData object. Usually MutableLiveData is used in the ViewModel and then the ViewModel only exposes immutable LiveData objects to the observers.

Why use MutableLiveData in android?

MutableLiveData is commonly used since it provides the postValue() , setValue() methods publicly, something that LiveData class doesn't provide. LiveData/MutableLiveData is commonly used in updating data in a RecyclerView from a collection type(List, ArrayList etc).

What is MutableLiveData in kotlin?

Live data or Mutable Live Data is an observable data holder class. We need this class because it makes it easier to handle the data inside View Model because it's aware of Android Lifecycles such as app components, activities, fragments, and services. The data will always update and aware of its Lifecycles.

Why LiveData Observer is being triggered twice?

Your fragment A views are created again and the observer is getting triggered once with cached livedata value and antoher time because the vendor type was set again in onViewCreated. Since we are using switchmap in viewmodel and the livedata was set again the observer in fragment A was getting triggered twice.


1 Answers

You are observing the new instance of ViewModel instead of observing the same ViewModel used by your First Fragment.

final CardsScanListViewModel viewModel =
            ViewModelProviders.of(this).get(CardsScanListViewModel.class);

Above code initialize new instance of CardsScanListViewModel for your second fragment CardsListFragment, because you passed this as context. If you update any data from this fragment it will update in this instance of ViewModel.

It works in your first Fragment because it updates data and observes data from same instance of ViewModel

To keep data common among ViewModels initiate view model by passing activity context instead of fragment context in both the fragments.

final CardsScanListViewModel viewModel =
            ViewModelProviders.of(getActivity()).get(CardsScanListViewModel.class);

This will create single instance of CardsScanListViewModel and data will be shared between fragments as they are observing LiveData from single instance of ViewModel.

For confirmation, you need to add notifyDataSetChanged() after updating the list if you haven't done that in adapter itself

private void observeViewModel(CardsScanListViewModel viewModel) {
    viewModel.getCardsLiveData().observe(this, new Observer  > () {
        @Override
        public void onChanged(@Nullable HashMap  cards) {
            if (cards != null) {
                cardsAdapter.setCardsList(cards.values());
                cardsAdapter.notifyDataSetChanged();
            }
        }
    });
}
like image 111
adityakamble49 Avatar answered Sep 24 '22 10:09

adityakamble49