Search code examples
androidandroid-livedataandroid-viewmodelmutablelivedata

Can't Observe LiveData from ViewModel in Fragment


I have a RecyclerView in my ViewPager's one of the Fragment. This RecyclerView show's data that are fetched from Server. It normally works, but I couldn't be able to implement the same feature using ViewModel and LiveData, because the livedata.observe method is not getting called when the livedata is being changed from the ViewModel.

Here's the Source Code of my MonthlyOrderViewModel.java class

public class MonthlyOrderViewModel extends AndroidViewModel {

private Retrofit retrofit;
private RetrofitServices retrofitServices;
private MutableLiveData<MonthlyOrderHistory> monthlyOrderHistoryMutableLiveData = new MutableLiveData<>();

public MonthlyOrderViewModel(@NonNull Application application) {
    super(application);
    retrofit = RetrofitFactory.getRetrofit();
    retrofitServices = retrofit.create(RetrofitServices.class);
}

public MutableLiveData<MonthlyOrderHistory> getMonthlyOrderHistoryMutableLiveData() {
    return monthlyOrderHistoryMutableLiveData;
}
public void fetchMonthlyOrders() {
    Call<MonthlyOrderHistory> call = retrofitServices.getAllMonthlyOrderHistory(IpharmaApplication.getInstance().getLoggedInUser().getId());
    call.enqueue(new Callback<MonthlyOrderHistory>() {
        @Override
        public void onResponse(Call<MonthlyOrderHistory> call, Response<MonthlyOrderHistory> response) {

            MonthlyOrderHistory monthlyOrderHistory = response.body();
            if(monthlyOrderHistory.getStatus().equalsIgnoreCase("success")) {

                List<UserOrder> userOrders = monthlyOrderHistory.getOrders().getUserOrder();
                if(userOrders != null && userOrders.size() != 0) {
                    DefaultScheduler.INSTANCE.execute(() -> {
                        monthlyOrderHistoryMutableLiveData.postValue(monthlyOrderHistory);
                        return Unit.INSTANCE;
                    });
                }
            }
        }

        @Override
        public void onFailure(Call<MonthlyOrderHistory> call, Throwable t) {

            t.printStackTrace();
        }
    });
}

}

DefaultScheduler is a Scheduler that by default handles operations in the AsyncScheduler. Here's the Source Code of my Scheduler.kt class


interface Scheduler {

    fun execute(task: () -> Unit)

    fun postToMainThread(task: () -> Unit)

    fun postDelayedToMainThread(delay: Long, task: () -> Unit)
}

//A shim [Scheduler] that by default handles operations in the [AsyncScheduler].
object DefaultScheduler : Scheduler {

    private var delegate: Scheduler = AsyncScheduler

    //Sets the new delegate scheduler, null to revert to the default async one.
    fun setDelegate(newDelegate: Scheduler?) {
        delegate = newDelegate ?: AsyncScheduler
    }

    override fun execute(task: () -> Unit) {
        delegate.execute(task)
    }

    override fun postToMainThread(task: () -> Unit) {
        delegate.postToMainThread(task)
    }

    override fun postDelayedToMainThread(delay: Long, task: () -> Unit) {
        delegate.postDelayedToMainThread(delay, task)
    }
}

//Runs tasks in a [ExecutorService] with a fixed thread of pools
internal object AsyncScheduler : Scheduler {

    private val executorService: ExecutorService = Executors.newFixedThreadPool(NUMBER_OF_THREADS)

    override fun execute(task: () -> Unit) {
        executorService.execute(task)
    }

    override fun postToMainThread(task: () -> Unit) {
        if (isMainThread()) {
            task()
        } else {
            val mainThreadHandler = Handler(Looper.getMainLooper())
            mainThreadHandler.post(task)
        }
    }

    private fun isMainThread(): Boolean {
        return Looper.getMainLooper().thread === Thread.currentThread()
    }

    override fun postDelayedToMainThread(delay: Long, task: () -> Unit) {
        val mainThreadHandler = Handler(Looper.getMainLooper())
        mainThreadHandler.postDelayed(task, delay)
    }
}

//Runs tasks synchronously.
object SyncScheduler : Scheduler {
    private val postDelayedTasks = mutableListOf<() -> Unit>()

    override fun execute(task: () -> Unit) {
        task()
    }
    override fun postToMainThread(task: () -> Unit) {
        task()
    }
    override fun postDelayedToMainThread(delay: Long, task: () -> Unit) {
        postDelayedTasks.add(task)
    }
    fun runAllScheduledPostDelayedTasks() {
        val tasks = postDelayedTasks.toList()
        clearScheduledPostdelayedTasks()
        for (task in tasks) {
            task()
        }
    }
    fun clearScheduledPostdelayedTasks() {
        postDelayedTasks.clear()
    }
}

And finally, I'm trying to observe the changes from my fragment's onCreateView method using this:

MonthlyOrderViewModel monthlyOrderViewModel = new MonthlyOrderViewModel(getActivity().getApplication());

monthlyOrderViewModel.getMonthlyOrderHistoryMutableLiveData().observe(this, monthlyOrderHistory -> {

    monthlyOrderRecyclerView = view.findViewById(R.id.monthlyOrderRecyclerView);

    monthlyOrderRecyclerViewAdapter = new OrderHistoryRecyclerViewAdapter(getContext(), monthlyOrderHistory.getOrders().getUserOrder());

    monthlyOrderRecyclerViewAdapter.notifyDataSetChanged();

    monthlyOrderRecyclerView.setAdapter(monthlyOrderRecyclerViewAdapter);       

    monthlyOrderRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

});

I tried to debug it and found that the retrofit had no problem fetching data. But the observe method is never getting called when I try to use them in my fragment.

Please suggest me how to solve this problem? Any help would be appreciated.


Solution

  • You shouldn't instantiate your ViewModel using new. You have to do this instead:

    MonthlyOrderViewModel monthlyOrderViewModel = ViewModelProviders.of(this).get(MonthlyOrderViewModel.class);
    

    If you are sure that the postValue is being executed in your ViewModel, then I think that this change should make it work. One more thing: you shouldn't be doing all that every time the observed LiveData notifies you of a change. I would move the next lines before the observe()

    monthlyOrderRecyclerView = view.findViewById(R.id.monthlyOrderRecyclerView);
    
    monthlyOrderRecyclerViewAdapter = new OrderHistoryRecyclerViewAdapter(getContext(), monthlyOrderHistory.getOrders().getUserOrder());
    
    monthlyOrderRecyclerView.setAdapter(monthlyOrderRecyclerViewAdapter);       
    
    monthlyOrderRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    

    Update

    As Fahim confirmed in the comments, the problem was that multiple instances of the sameViewModel were being created. If you need to share the same instance of a ViewModel, make sure to pass a reference of the Fragment/Activity that's the "owner" of that ViewModel. For example, if MyActivity has a MainActivityViewModel and it contains a child Fragment named MyFragment, then if that Fragment wants to get a reference to its parent's ViewModel it will have to do it in the following way:

    MainActivityViewModel mainActivityVM = ViewModelProviders
                                                .of(getActivity())
                                                .get(MainActivityViewModel.class);