View Model keeps creating instance of Live Data

前端 未结 5 1407
隐瞒了意图╮
隐瞒了意图╮ 2021-01-18 16:47

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

    ticketViewModel = ViewModelProviders.of(this).get(TicketViewModel.cl         


        
相关标签:
5条回答
  • 2021-01-18 17:15

    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
        }
    }
    
    0 讨论(0)
  • 2021-01-18 17:29

    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);
    }
    
    0 讨论(0)
  • 2021-01-18 17:35

    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;
            }
        }
    
    }
    
    0 讨论(0)
  • 2021-01-18 17:35

    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

    0 讨论(0)
  • 2021-01-18 17:36
    • 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 :)

    0 讨论(0)
提交回复
热议问题