Модель просмотра продолжает создавать экземпляр Live Data

Я создал экземпляр модели представления в onCreate методе действия.

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

Затем у меня есть метод AddTicket, который использует viewModel для обращения к службе, и при ответе от viewModel я отклоняю загрузочную анимацию.

 public void addTicket(View view){

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

Теперь после добавления заявки пользователь может повторно нажать кнопку Add Ticket, и метод addTicket() будет вызван снова.

но на этот раз observer, определенный в ViewModel, вызывается 2 раза, что приводит к 2 сетевым вызовам и выполнению 2 dismissLoadingAnimation.

И если я продолжаю нажимать кнопку addTicket, количество выполняющих наблюдателей, определенных внутри ViewModel keep, увеличивается.

Это код моей модели просмотра.

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

}

person dev90    schedule 15.12.2018    source источник
comment
Я думаю, вы не хотели помещать sectionViewModel.getSeats внутри sectionViewMode.getData, это правильно?   -  person Ahmed Ashraf    schedule 16.12.2018
comment
@AhmedAshrafGamal: Сначала я вызываю sectionViewModel.getData, когда я получаю результат от этого метода, мне нужно вызвать sectionViewModel.getSeats   -  person dev90    schedule 16.12.2018
comment
попробуйте Ведение журнала, а не отладку Log.i (тест, тест); отладка внутри потоков иногда дает неверные результаты.   -  person Master Fathi    schedule 16.12.2018
comment
Вы уверены, что ваша служба не вызывает onResponse дважды? Можете ли вы это показать?   -  person Man    schedule 22.12.2018


Ответы (5)


Количество выполняющих наблюдателей, определенных внутри ViewModel keep, увеличивается, поскольку с каждым щелчком вы регистрируете новых наблюдателей. Вы не должны регистрировать наблюдателя с помощью метода onClick().

Вы должны сделать это в onCreate() методе вашего Activity или в onViewCreated методе вашего фрагмента. Если Ты это сделаешь, то не нужно будет removeObserver, когда Ты закончишь работу. Lifecycle механизм накроет это за вас.

Но если вы действительно хотите получить ответ на свой вопрос, вот как вы можете это сделать.

yourViewModel.yourList.removeObservers(this)

Передача this означает передачу вашего Activity, или есть второй способ:

yourViewModel.yourList.removeObserver(observer)

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

  • Цель Viewmodel - предоставить наблюдаемые (Livedata)
  • Цель View (Activity / Fragment) - получить эти наблюдаемые и наблюдать за ними.
  • Всякий раз, когда происходит изменение в этих наблюдаемых (Livedata), изменение автоматически отправляется активным подписанным владельцам (Activity / Fragment), поэтому вам не нужно удалять их в onPause / onStop, поскольку это не обязательно

Я могу предложить несколько изменений в вашем коде, чтобы решить проблему с указанными выше указателями.

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

}

Просмотр

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

Надеюсь, это поможет :)

person Aron_A    schedule 25.12.2018

Вам нужно вызвать observe только один раз, я предпочитаю сделать это в onResume, а затем вызвать removeObserver в onPause:

Добавляет данного наблюдателя в список наблюдателей

Вы продолжаете добавлять слушателей к данным, поэтому получаете несколько обратных вызовов.

Изменить:
Я взял свой существующий образец кода для Fragment и переименовал все (надеюсь), здесь нет примера для установки данных в ViewModel, но в вашем случае он должен быть ticketViewModel.AddTicket(id); .

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

}
person MikeL    schedule 23.12.2018
comment
Вы можете показать мне код вызова метода removeObserver? - person dev90; 23.12.2018

У меня была аналогичная проблема. Вы можете попробовать использовать SingleLiveEvent

Или, в моем более сложном случае, мне пришлось использовать собственный наблюдатель. Это выглядело бы так:

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

И используя это как:

public void addTicket(View view){

     ticketViewModel.AddTicket(id).observe(this, myCustomObserver);
}
person Jurij Pitulja    schedule 24.12.2018

Если вы хотите внести некоторые изменения, я думаю, мы сможем справиться с этим более простым способом.

LiveData предназначен для использования для хранения значения свойства представления.


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

}

В onCreate вашей операции / фрагмента

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

Основная концепция - разделить обязанности. Действие / фрагмент не нужно знать, какой процесс происходит, им нужно только знать, каковы текущие свойства / состояние дочерних представлений.

Нам необходимо поддерживать LiveData в ViewModels для каждого изменяющегося свойства / состояния в зависимости от представлений. ViewModel должен обрабатывать состояния просмотра в зависимости от того, что происходит.

Единственная ответственность, которую Activity / Fragment несет в отношении процесса, - это запустить его и забыть, а ViewModel должен обрабатывать все (например, информировать репозитории о выполнении работы и изменять свойства представления).

В вашем случае addTicket - это процесс, о статусе которого Activity / Fragment не должно знать. Единственная ответственность Activity / Fragment относительно этого процесса - запустить его.

ViewModel - это тот, кому необходимо проанализировать состояние процесса (выполняется / успешно / не удалось) и дать соответствующие значения LiveDatas для информирования соответствующих представлений.

person Pavan Varma    schedule 24.12.2018