View Model keeps creating instance of Live Data

2020-04-07 04:16发布

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

}

5条回答
Luminary・发光体
2楼-- · 2020-04-07 04:52

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);
}
查看更多
家丑人穷心不美
3楼-- · 2020-04-07 04:54

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

查看更多
Summer. ? 凉城
4楼-- · 2020-04-07 04:56
  • 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 :)

查看更多
别忘想泡老子
5楼-- · 2020-04-07 05:02

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
    }
}
查看更多
对你真心纯属浪费
6楼-- · 2020-04-07 05:12

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

}
查看更多
登录 后发表回答