View Model keeps creating instance of Live Data

怎甘沉沦 提交于 2019-12-19 19:17:06

问题


I created the instance of View Model in onCreate method of an activity.

    ticketViewModel = ViewModelProviders.of(this).get(TicketViewModel.class);

Then i have a method, AddTicket, which uses viewModel to hit a service and on response from viewModel i dismiss loading animation.

 public void addTicket(View view){

     ticketViewModel.AddTicket(id).observe(this, response ->{
                        dismissLoadingAnimation();
    } 

Now after adding a ticket, user can repress the Add Ticket button, and the addTicket() method will be called again.

but this time observer defined in ViewModel gets called 2 times, resulting in 2 network calls, and 2 dismissLoadingAnimation execution.

And if i keep pressing addTicket button, the number of executing observer defined inside ViewModel keep increases.

This is my View Model code.

public class TicketViewModel extends AndroidViewModel implements IServiceResponse {

    MutableLiveData<String> mObservableResponse = new MutableLiveData<String>();


    public MutableLiveData AddTicket(String id){

        JsonObject jsonObject= new JsonObject();
        jsonObject.addProperty("id",  id);

        NetworkUtility networkUtility= new NetworkUtility(this, ADD_TICKET);
        networkUtility.hitService(URL, jsonObject, RequestMethods.POST);

        return mObservableResponse;
    }


     @Override
        public void onServiceResponse(String response, String callType){

        if(serviceTag.equalsIgnoreCase(ADD_TICKET)){    
             mObservableResponse.setValue("success");
        }
    }

}

回答1:


The number of executing observer defined inside ViewModel keep increases becasue with every click You're registering new observers. You're not supposed to register observer with onClick() method.

You should do it in onCreate() method of your Activity or in onViewCreated method of your fragment. If You'll do that, there won't be a need to removeObserver when You'll finish work. Lifecycle mechanism will cover it for you.

But if you really want answer for you question, this is how you can do it

yourViewModel.yourList.removeObservers(this)

Passing this means passing your Activity, or there is a second way:

yourViewModel.yourList.removeObserver(observer)

val observer = object : Observer<YourObject> {
    override fun onChanged(t: YourObject?) {
        //todo
    }
}



回答2:


  • The purpose of Viewmodel is to expose observables (Livedata)
  • The purpose of View(Activity/Fragment) is to get these observables and observe them
  • Whenever there is a change in these observables(Livedata) the change is automatically posted to the active subscribed owners(Activity/Fragment), so you need not remove them in onPause/onStop as it is not mandatory

I can suggest few changes to your code to solve the problem with the above mentioned pointers

ViewModel

public class TicketViewModel extends AndroidViewModel implements IServiceResponse {

    MutableLiveData<String> mObservableResponse = new MutableLiveData<String>();

   public LiveData<String> getResponseLiveData(){
        return mObservableResponse;
        }

    public void AddTicket(String id){

        JsonObject jsonObject= new JsonObject();
        jsonObject.addProperty("id",  id);

        NetworkUtility networkUtility= new NetworkUtility(this, ADD_TICKET);
        networkUtility.hitService(URL, jsonObject, RequestMethods.POST);

    }


     @Override
        public void onServiceResponse(String response, String callType){

        if(serviceTag.equalsIgnoreCase(ADD_TICKET)){    
             mObservableResponse.setValue("success");
        }
    }

}

View

    onCreate(){
    ticketViewModel = ViewModelProviders.of(this).get(TicketViewModel.class);
    observeForResponse();
    }

    private void observeForResponse(){
       ticketViewModel.getResponseLiveData().observe(this, response ->{
                            //do what has to be updated in UI
    }
    }

public void addTicket(View view){
     ticketViewModel.AddTicket(id);
    }

Hope this is of help :)




回答3:


You only need to call the observe once, I prefer to do it in onResume and then call removeObserver in onPause:

Adds the given observer to the observers list

You keep adding listeners to the data so you get multiple callbacks.

Edit:
I took an existing code sample of mine for a Fragment and renamed everything (I hope), there's no example here for setting the data into the ViewModel but it should be ticketViewModel.AddTicket(id); in your case.

public class ListFragment extends Fragment {

    private MyViewModel viewModel;
    private MyRecyclerViewAdapter recyclerViewAdapter;
    private Observer<List<DatabaseObject>> dataObserver;
    private RecyclerView recyclerView;

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_layout, container, false);
        initRecyclerView(rootView, getContext());
        initObservers();

        return rootView;
    }

    private void initRecyclerView(View rootView, Context context) {
        recyclerViewAdapter = new MyRecyclerViewAdapter(context);
        recyclerView = rootView.findViewById(R.id.recycler_view);
        recyclerView.setAdapter(recyclerViewAdapter);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.addItemDecoration(new DividerNoLastItemDecoration());
    }

    private void initObservers() {
        dataObserver = new Observer<List<DatabaseObject>>() {
            @Override
            public void onChanged(@Nullable final List<DatabaseObject> data) {
                recyclerViewAdapter.setData(data);
            }
        };
    }

    @Override
    public void onResume() {
        super.onResume();
        initViewModel();
    }

    private void initViewModel() {
        FragmentActivity activity = getActivity();
        if (activity != null) {
            viewModel = ViewModelProviders.of(activity).get(MyViewModel.class);
            viewModel.getData().observe(activity, dataObserver);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (viewModel != null) {
            viewModel.getData().removeObserver(dataObserver);
            viewModel = null;
        }
    }

}



回答4:


I had similar problem. You could try to use SingleLiveEvent

Or, in my, more complicated case, i had to use custom observer. It would looks like this:

public class CustomObserver implements Observer<YourType> {
    private MyViewModel mViewModel;

    public CustomObserver (){}

    public void setViewModel(MyViewModel model) {
        mViewModel = model;
    }

    @Override
    public void onChanged(@Nullable YourType object) {
        mViewModel.AddTicket(id).removeObserver(this); // removing previous 
        mmViewModel.refreshTickets(); // refreshing Data/UI
        // ... do the job here
        // in your case it`s: dismissLoadingAnimation();
    } 
}

And using it like:

public void addTicket(View view){

     ticketViewModel.AddTicket(id).observe(this, myCustomObserver);
}



回答5:


If you are willing to do some changes, i think we can handle it in much cleaner way

LiveData is meant to be used to contain a property value of a view


In ViewModel

public class TicketViewModel extends AndroidViewModel implements IServiceResponse {

    private MutableLiveData<Boolean> showLoadingAnimationLiveData = new MutableLiveData<String>();

    public LiveData<Boolean> getShowLoadingAnimationLiveData(){
        return showLoadingAnimationLiveData;
    }

    public void addTicket(String id){

        JsonObject jsonObject= new JsonObject();
        jsonObject.addProperty("id",  id);

        NetworkUtility networkUtility= new NetworkUtility(this, ADD_TICKET);
        networkUtility.hitService(URL, jsonObject, RequestMethods.POST);
        showLoadingAnimationLiveData.setValue(true);
    }


    @Override
    public void onServiceResponse(String response, String callType){
        if(serviceTag.equalsIgnoreCase(ADD_TICKET)){    
            showLoadingAnimationLiveData.setValue(false);
        }
    }

}

In 'onCreate' of your Activity/Fragment

ticketViewModel.getShowLoadingAnimationLiveData().observe(this,showLoadingAnimation->{
    if(showLoadingAnimation != null && showLoadingAnimation){
        startLoadingAnimation();
    }else{
        dismissLoadingAnimation();
    }
})

The main concept is to divide the responsibilities, Activity/Fragment doesn't need to know which process is going on, they only need to know what are the current properties/state of there child views.

We need to maintain a LiveData in ViewModels for each changing property/state depending on Views. ViewModel needs to handle the view states depending on whats happening.

Only responsibility the Activity/Fragment has about a process is to trigger it and forget and ViewModel needs handle everything(like informing Repositories to do the work and changing View Properties).

In your Case, 'addTicket' is a process about which Activity/Fragment doesn't need to know about there status. The only responsibility of Activity/Fragment about that process is to trigger it.

ViewModel is one who needs to analyze the state of process(in-progress/success/failed) and give appropriate values to the LiveDatas to inform the respective Views



来源:https://stackoverflow.com/questions/53797110/view-model-keeps-creating-instance-of-live-data

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!