Why my ViewModel is not calling the .observe() and notifying the Adapter ,if the MutableLiveData is changing?

EDIT: The real problem was that the submitList() was not triggering the onCreateViewHolder() in the recyclerView.

The MutableLiveData inside the ViewModel is updating, but the .observe() that notifies the adapter is not triggered.

I am trying to display data from a Firebase database, and I wanted to use an HandlerThread that references a child and adds a childEventListener to make things easier among fragments.

But this problem persists even without the HandlerThread, even if I place the whole firebase reference inside the Fragment, the ViewModel still does not triggers the .observe()

public class CategoryListViewModel2 extends ViewModel {

    private static final String TAG = "CategoryListViewModel";
    private MutableLiveData<List<CategoryListItem>> mData;
    private ValueSetter mValueSetter;

    public CategoryListViewModel2() {
        mValueSetter = new ValueSetter();
        mData = new MutableLiveData<>();
    }

    public MutableLiveData<List<CategoryListItem>> getData() {
        return mData;
    }

    public void getCategories(DataSnapshot snapshot) {
        Log.d(TAG, "getCategories: ");
        mValueSetter.populateCategories(snapshot, mData);

        Log.d(TAG, "getCategories: checkSize is: " + checkSize());
        if (checkSize()) {
            Log.d(TAG, "getCategories: mData is: " + mData.getValue().get(5));
        }
    }

    private boolean checkSize() {
        return mData.getValue().size() > 5;
    }
}

The checksize() method is telling me the MutableLiveData is indeed increasing in size.

The ValueSetter is a class that gets the snapShot places it in a List and then sets the new value of the MutableLiveData the code for the ValueStter is:

public class ValueSetter {
    private static final String TAG = "ValueSetter";

    private List<CategoryListItem> items;

    public ValueSetter() {
        items = new ArrayList<>();
    }

    public void populateCategories(DataSnapshot snapshot, MutableLiveData<List<CategoryListItem>> mData) {    
        CategoryListItem item = snapshot.getValue(CategoryListItem.class);
        items.add(item);

        mData.setValue(items);
}

My Fragment retrieves the initialized HandlerThread from the Main Activity and then proceeds with the usual: Databindings, sets the RecyclerView, creates de Adapter and sets it, Then the ViewModel via factory so that its constructor gets called, then the ViewModel submits the list, and just in case it even notifies the adapter, but even this doesn’t work…

public class CatalogFragment extends Fragment {

        private static final String TAG = "CatalogFragmentRecycler";

        private DatabaseHandlerThread mDatabaseHandlerThread;
        private CategoryListViewModel2 mViewModel2;

        public CatalogFragment() {
            Log.d(TAG, "CatalogFragment: ");
        }

        @Override
        public void onAttach(@NonNull Context context) {
            super.onAttach(context);
            Log.d(TAG, "onAttach: ");
            if (context instanceof MainActivity) {
                mDatabaseHandlerThread =((MainActivity)context).getDatabaseHandlerThread();
            }
        }

        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

            FragmentCatalogBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_catalog,container,false);

            binding.categoriesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
            binding.categoriesRecyclerView.setHasFixedSize(true);

            final CategoriesAdapter adapter = new CategoriesAdapter();
            binding.categoriesRecyclerView.setAdapter(adapter);

            ViewModelFactory factory = new ViewModelFactory();
            mViewModel2 = ViewModelProviders.of(this, factory).get(CategoryListViewModel2.class);
            mViewModel2.getData().observe(this, categoryListItems -> {

                adapter.submitList(categoryListItems);
                adapter.notifyDataSetChanged();
  binding.categoriesRecyclerView.smoothScrollToPosition(adapter.getItemCount());
            });

            return binding.getRoot();
        }

        @Override
        public void onStart() {
            super.onStart();
            Log.d(TAG, "onStart: ");

            mDatabaseHandlerThread.sendMsg("C", dataSnapshot -> mViewModel2.getCategories(dataSnapshot));
    }

The smoothScrollToPosition() is the only snippet of code that is somewhat solving my issue with the recyclerView and its adapter not being notified, the problem is that this snippet only solves the issue on the first start of the app, but if a child is added the recyclerView refuses to update.

If I remove the smoothScrollToPosition() line of code, the recyclerView shows nothing, and it only populates the list after I touch it literally, with my finger.

On the onStart, you can see my message sent, which has the reference of the child “C”, and the ViewModel that takes care of the data snapshot.

Because Ive done this before, I know that in order for the viewmodel to be able to change the view, it must be done in the same thread that created the view, that means that the onChildEventListener runs my custom listener on the Ui Thread with the runOnUiThread().

@Override
public void handleMessage(Message msg) {
    Log.d(TAG, "handleMessage: ");
    while (!transfer) {
        try {
            wait();
        } catch (InterruptedException e)  {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
    Log.d(TAG, "handleMessage: ");
    mReference = mReference.child(child);
    Log.d(TAG, "handleMessage: reference is: " + 
    mReference.toString());
    transfer = false;
    mReference.addChildEventListener(firebaseListener());
    transfer = true;
    //mReference.removeEventListener(firebaseListener());
}

This is the handler inside My HandlerThread, the removeEventListener snippet is commented so that events are still being listened if children are added.

In the case where the entire Database reference with the Listener is placed inside the fragment, the ViewModel refuses to trigger the .observe(). when I use the same : mViewModel2.getCategories(dataSnapshot) from inside the ChildEventListener().

public class CategoriesAdapter extends ListAdapter<CategoryListItem, 
CategoriesAdapter.CategoriesHolder> {

    private static final String TAG = "CategoriesAdapter";

    private LayoutInflater mLayoutInflater;

    public CategoriesAdapter() {
        super(DIFF_CALLBACK);
    }

    private static final DiffUtil.ItemCallback<CategoryListItem> 
DIFF_CALLBACK = new DiffUtil.ItemCallback<CategoryListItem>() {
        @Override
        public boolean areItemsTheSame(@NonNull CategoryListItem oldItem, 
@NonNull CategoryListItem newItem) {
            return oldItem.getS() == newItem.getS();
        }

        @SuppressLint("DiffUtilEquals")
        @Override
        public boolean areContentsTheSame(@NonNull CategoryListItem 
oldItem, @NonNull CategoryListItem newItem) {
            Log.d(TAG, "areContentsTheSame: " + newItem.toString());
            return oldItem.getS().equals(newItem.getS());
        }
    };

    @NonNull
    @Override
    public CategoriesHolder onCreateViewHolder(@NonNull ViewGroup parent, 
int viewType) {
        Log.d(TAG, "onCreateViewHolder: ");
        if (mLayoutInflater == null) {
            mLayoutInflater = LayoutInflater.from(parent.getContext());
            Log.d(TAG, "onCreateViewHolder: inflater is: " + 
mLayoutInflater.toString());
        }

        Log.d(TAG, "onCreateViewHolder: inflater is: " + mLayoutInflater);

        CategoryListItemBinding binding = 
DataBindingUtil.inflate(mLayoutInflater,
                R.layout.category_list_item,
                parent,
                false);
        return new CategoriesHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull CategoriesHolder holder, int 
position) {
        CategoryListItem currentitem = getItem(position);
        holder.setData(currentitem);
    }

    class CategoriesHolder extends RecyclerView.ViewHolder {
        private CategoryListItemBinding binding;

        public CategoriesHolder(@NonNull CategoryListItemBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }

        void setData (CategoryListItem item) {
            binding.setCategoryListItem(item);
        }
    }
}

the S in getS() is the only field in the snapshot retrieved at the position.

1
Leave a Reply

avatar
1 Comment threads
0 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
1 Comment authors
Jason Recent comment authors
  Subscribe  
newest oldest most voted
Notify of
Jason
Guest

Upon more testing, it seemed that the .observe() was indeed being called, the problem was that the .submtList() was not triggering the onCreateViewHolder() Not really an answer but after some reading it seems that it has something to do with it not being compatible with something other than Room. https://stackoverflow.com/a/50062174/11214643 at least that answer managed to access the Diff_Callback, but still no response from the onCreateViewHolder()… I also tried placing a notifyDataSetChaged() inside the overridden submitList() method, but nothing happened. I also tried placing an adapter.notifyDataSetChanged inside the .observe() Listener but it didn’t work… Finally the real answer was combining… Read more »