Как сделать недействительными ViewModel/LiveData через некоторое время?

#android #mvvm #android-livedata #android-viewmodel

Вопрос:

Как рекомендовано в последней документации, я использую ViewModel/LiveData для отображения некоторых данных:

 public class EventDatesViewModel extends AndroidViewModel {

    private final MutableLiveData<String> timeToEvent = new MutableLiveData<>();

    ...

    public LiveData<String> getTimeToEvent() {
        if (timeToEvent == null)
            new Thread(this::calculateTimeToEvent).start();
        return timeToEvent;
    }

    private void calculateTimeToEvent() {

        ...

        timeToEvent.postValue(t);
    }
}
 

Я использую это в своем фрагменте:

 @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        View root = inflater.inflate(R.layout.fragment_garbage, container, false);

        EventDatesViewModel model = new ViewModelProvider(requireActivity(), new ViewModelProvider.AndroidViewModelFactory(requireActivity().getApplication())).get(EventDatesViewModel.class);
        model.getTimeToEvent().observe(this, timeToEvent -> {
            ((TextView)root.findViewById(R.id.textTimeToEvent)).setText(timeToEvent);
        });
        return root;
    }
 

Теперь проблема в том, что это что timeToEvent -то вроде «2 дня 4 часа». Поэтому, если я оставлю приложение открытым, в какой-то момент оно устареет и его нужно будет пересчитать заново. Если приложение не открыто или находится в фоновом режиме, я не хочу выполнять довольно дорогостоящие вычисления.

Как я могу добиться этого периодического перерасчета?


Что не сработало

Я добавил обработчик, который используется postDelayed() для планирования пересчета.

     private final Handler handler = new Handler();
    private final Runnable runnable = new Runnable() {
        @Override
        public void run() {
            new Thread(() -> calculateTimeToEvent()).start();
            handler.postDelayed(this, 10000);
        }
    };
 
  public LiveData<String> getTimeToEvent() {
        if (timeToEvent == null) {
            new Thread(this::calculateTimeToEvent).start();
            handler.postDelayed(runnable, 10000);
        }
        return timeToEvent;
    }
 
     @Override
    protected void onCleared() {
        super.onCleared();
        handler.removeCallbacks(runnable);
    }
 

К сожалению, это запускает вычисления, даже когда приложение находится в фоновом режиме или устройство спит. Я записал запуски calculateTimeToEvent() с отметками времени в файл и вижу, что они происходят каждые 10 секунд без перерыва.

Комментарии:

1. Вы спрашиваете, как планировать повторяющиеся операции каждый 2 days 4 hours раз ?

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

3. 1.) почему вы используете «новый ViewModelProvider». androidviewмоделфактор(требуемая активность().getApplication ()))` явно? У фрагмента уже есть фабрика поставщиков моделей представления по умолчанию, которая имеет лучшее поведение 2.) почему вы используете this в качестве владельца жизненного цикла вместо getViewLifecycleOwner() ?

4. @EpicPandaForce 1) В моей модели представления мне нужно Context , чтобы показывать тосты, так что это AndroidViewModel и был самый короткий код, который я смог найти, который может создать экземпляр AndroidViewModel . 2) Я скопировал этот код отсюда , но, похоже, я перепутал два примера.

5. @EpicPandaForce Действительно, после обновления appcompat до 1.3.1 (с 1.2.0) завод по умолчанию работает , и это каким-то образом вызвало появление ворса, который я должен использовать getViewLifecycleOwner() вместо this , как вы и сказали. Спасибо.

Ответ №1:

Поскольку вы используете Java, вы можете использовать обработчик, используемый postDelayed для запуска работающего через X промежуток времени, а затем запланировать его снова после запуска

Это котлин, но его можно легко превратить обратно в Java

В вашем представлении модель

 private val _handler = Handler()

private val _runnable = object : Runnable {
    override fun run() {
        calculateTimeToEvent()
        _handler.postDelayed(this, 3600000)
    }

}
 

Затем в какой-то момент вызовите это в своей модели просмотра, чтобы начать повторяющуюся операцию

 _handler.postDelayed(_runnable, 3600000)
 

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

 _handler.removeCallbacks(_runnable)
 

Также есть возможность использовать RxJava и использовать interval

Комментарии:

1. Это postDelayed автоматически приостанавливается, если представление не отображается? Например, когда у пользователя открыто другое приложение или экран телефона выключен?

2. Так как действие больше не находится вокруг ViewModel, то больше не требуется, поэтому onCleared (где вы вызываете removeCallbacks ) называется остановкой повторяющейся операции

3. @AndreKR, тогда это означает, что модель представления все еще используется ie. активность все еще находится в состоянии, когда она может быть возобновлена. Вы всегда можете создать метод в своей модели просмотра, чтобы отменить обратные вызовы от вашей активности в onPause, например, когда активность больше не активна (приостановлена), но не уничтожена

4. Тогда почему бы просто не попросить фрагмент вызвать метод обновления в ViewModel? Но мне кажется, что я должен использовать эту информацию о владельце/наблюдателе жизненного цикла?

5. Вы, конечно, могли бы, но вы сказали, что хотите, чтобы он работал, пока модель просмотра активна. Поскольку при изменении конфигурации, таком как поворот экрана, действие/фрагмент отменит/перезапустит таймеры, так как они воссоздаются, в отличие от объектов ViewModel, сохраняются вместе с их данными об изменении конфигурации, поэтому таймер на 1 час теоретически может превратиться в таймер на 2 часа