Использование AlarmManager внутри Observer приводит к тому, что наблюдаемая () вызывается только один раз - PullRequest
0 голосов
/ 20 февраля 2019

Я реализовал архитектуру MVVM для приложения тревоги с помощью кнопки ToggleButton, которая активирует тревогу при ее включении и отключает тревогу при ее выключении.При щелчке по ToggleButton в ViewModel вызывается функция, которая на основе состояния переключения ToggleButton активирует или деактивирует сигнал тревоги, устанавливая значение LiveData с информацией о сигнале тревоги.

Во фрагменте я наблюдаю LiveData из ViewModel и устанавливаю сигнал тревоги, отправляя PendingIntent с AlarmManager, или отменяю сигнал тревоги, отменяя PendingIntent и AlarmManager.Проблема в том, что когда я запускаю приложение в первый раз, первый щелчок по ToggleButton запускает Обозреватель во Фрагменте, но после этого Обозреватель просто отказывается запускать даже после изменения значения LiveData.

Что я обнаружил, так это то, что удаление связанных с AlarmManager функций (setExactAndAllowWhileIdle () / cancel ()) каждый раз вызывает триггер Observer, но при добавлении этих функций Observer больше не реагирует на LiveData.изменения.

Я думаю, что это лучше объяснить кодом и журналами.

AlarmViewModel.kt

private val newToast: MutableLiveData<SingleEvent<String>> = MutableLiveData()
private val activateEvent: MutableLiveData<SingleEvent<AlarmData>> = MutableLiveData()
private val deactivateEvent: MutableLiveData<SingleEvent<AlarmData>> = MutableLiveData()
...
private fun activateAlarm(alarmData: AlarmData) {
    newToast.value = SingleEvent("Alarm has been set!")
    activateEvent.value = SingleEvent(alarmData)
}

private fun deactivateAlarm(alarmData: AlarmData) {
    newToast.value = SingleEvent("Alarm has been cleared")
    deactivateEvent.value = SingleEvent(alarmData)
}
...
fun observeNewToast(): LiveData<SingleEvent<String>> = newToast
fun observeActivateEvent(): LiveData<SingleEvent<AlarmData>> = activateEvent
fun observeDeactivateEvent(): LiveData<SingleEvent<AlarmData>> = deactivateEvent

AlarmFragment.kt

@Inject
lateinit var alarmManager: AlarmManager
private val viewModel: SingleAlarmViewModel by lazy {
    ViewModelProviders.of(activity!!).get(AlarmViewModel::class.java)
}
private lateinit var newToastObserver: Observer<SingleEvent<String>>
private lateinit var activateEventObserver: Observer<SingleEvent<AlarmData>>
private lateinit var deactivateEventObserver: Observer<SingleEvent<AlarmData>>
...
override fun onResume() {
    super.onResume()

    newToastObserver = Observer {
        it?.getContentIfNotHandled()?.let { toastText ->
            info("observeNewToast() -> message: $toastText")
            Toast.makeText(activity, toastText, Toast.LENGTH_SHORT).show()
        }
    }

    activateEventObserver = Observer {
        it?.getContentIfNotHandled()?.let { alarmData ->
            info("activateEventObserver -> $alarmData")

            val idInteger = alarmData.timeInMillis.toInt()
            val alarmTime = alarmData.timeInMillis

            val activateAlarmIntent = Intent(activity, AlarmReceiver::class.java)
            activateAlarmIntent.putExtra("message", alarmData.toString())
            val pendingIntent = PendingIntent.getBroadcast(
                activity,
                idInteger,
                activateAlarmIntent,
                PendingIntent.FLAG_UPDATE_CURRENT
            )
            AlarmManagerCompat.setExactAndAllowWhileIdle(
                alarmManager,
                AlarmManager.RTC_WAKEUP,
                alarmTime,
                pendingIntent
            )
        }
    }

    deactivateEventObserver = Observer {
        it?.getContentIfNotHandled()?.let { alarmData ->
            info("deactivateEventObserver -> $alarmData")

            val idInteger = alarmData.timeInMillis.toInt()

            val deactivateAlarmIntent = Intent(activity, AlarmReceiver::class.java)
            deactivateAlarmIntent.putExtra("message", alarmData.toString())
            val pendingIntent = PendingIntent.getBroadcast(
                activity,
                idInteger,
                deactivateAlarmIntent,
                PendingIntent.FLAG_UPDATE_CURRENT
            )
            pendingIntent.cancel()
            alarmManager.cancel(pendingIntent)
        }
    }
    viewModel.observeNewToast().observe(activity!!, newToastObserver)
    viewModel.observeActivateEvent().observe(activity!!, activateEventObserver)
    viewModel.observeDeactivateEvent().observe(activity!!, deactivateEventObserver)
}

override fun onPause() {
    super.onPause()
    viewModel.apply {
        observeNewToast().removeObserver(newToastObserver)
        observeActivateEvent().removeObserver(activateEventObserver)
        observeDeactivateEvent().removeObserver(deactivateEventObserver)
    }
}

ЗапускВ таком приложении, ActivEventObserver и deactivateEventObserver наблюдаются только один раз, а затем перестают наблюдаться, как показано в журналах ниже.

02-20 18:38:23.707 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:38:23.722 23671-23671/com.aly.alarm I/AlarmFragment: deactivateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:38:28.547 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:38:28.557 23671-23671/com.aly.alarm I/AlarmFragment: activateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:38:34.647 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:38:35.357 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:38:37.472 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:38:38.112 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:38:38.962 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:38:39.377 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!

Но если я удалю связанный с AlarmManager код, Наблюдатели будут вызываться все время,как и ожидалось.

Модифицированный AlarmFragment.kt

@Inject
lateinit var alarmManager: AlarmManager
private val viewModel: SingleAlarmViewModel by lazy {
    ViewModelProviders.of(activity!!).get(AlarmViewModel::class.java)
}
private lateinit var newToastObserver: Observer<SingleEvent<String>>
private lateinit var activateEventObserver: Observer<SingleEvent<AlarmData>>
private lateinit var deactivateEventObserver: Observer<SingleEvent<AlarmData>>
...
override fun onResume() {
    super.onResume()

    newToastObserver = Observer {
        it?.getContentIfNotHandled()?.let { toastText ->
            info("observeNewToast() -> message: $toastText")
            Toast.makeText(activity, toastText, Toast.LENGTH_SHORT).show()
        }
    }

    activateEventObserver = Observer {
        it?.getContentIfNotHandled()?.let { alarmData ->
            info("activateEventObserver -> $alarmData")

            val idInteger = alarmData.timeInMillis.toInt()
            val alarmTime = alarmData.timeInMillis

            val activateAlarmIntent = Intent(activity, AlarmReceiver::class.java)
            activateAlarmIntent.putExtra("message", alarmData.toString())
            val pendingIntent = PendingIntent.getBroadcast(
                activity,
                idInteger,
                activateAlarmIntent,
                PendingIntent.FLAG_UPDATE_CURRENT
            )

            // AlarmManager code commented out
            // AlarmManagerCompat.setExactAndAllowWhileIdle(
            //     alarmManager,
            //     AlarmManager.RTC_WAKEUP,
            //     alarmTime,
            //     pendingIntent
            // )
        }
    }

    deactivateEventObserver = Observer {
        it?.getContentIfNotHandled()?.let { alarmData ->
            info("deactivateEventObserver -> $alarmData")

            val idInteger = alarmData.timeInMillis.toInt()

            val deactivateAlarmIntent = Intent(activity, AlarmReceiver::class.java)
            deactivateAlarmIntent.putExtra("message", alarmData.toString())
            val pendingIntent = PendingIntent.getBroadcast(
                activity,
                idInteger,
                deactivateAlarmIntent,
                PendingIntent.FLAG_UPDATE_CURRENT
            )
            pendingIntent.cancel()

            // AlarmManager code commented out
            // alarmManager.cancel(pendingIntent)
        }
    }
    viewModel.observeNewToast().observe(activity!!, newToastObserver)
    viewModel.observeActivateEvent().observe(activity!!, activateEventObserver)
    viewModel.observeDeactivateEvent().observe(activity!!, deactivateEventObserver)
}

override fun onPause() {
    super.onPause()
    viewModel.apply {
        observeNewToast().removeObserver(newToastObserver)
        observeActivateEvent().removeObserver(activateEventObserver)
        observeDeactivateEvent().removeObserver(deactivateEventObserver)
    }
}

Журналы при запуске приложения выглядят так (Наблюдатель наблюдал, как и ожидалось):

02-20 18:44:18.207 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:44:18.222 24565-24565/com.aly.alarm I/AlarmFragment: deactivateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:18.367 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:44:18.382 24565-24565/com.aly.alarm I/AlarmFragment: activateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:21.277 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:44:21.297 24565-24565/com.aly.alarm I/AlarmFragment: deactivateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:22.177 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:44:22.197 24565-24565/com.aly.alarm I/AlarmFragment: activateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:22.842 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:44:22.857 24565-24565/com.aly.alarm I/AlarmFragment: deactivateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:23.447 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!

Я попытался установитьВладелец жизненного цикла ViewModel для родительского Activity и установка его обратно на Fragment, но это не имело значения.Удаление кода, связанного с AlarmManger, заставляет наблюдателя работать, но, поскольку я пытаюсь установить будильник через наблюдателя, это меня очень расстраивает.Почему AlarmManager заставляет Обозревателя перестать наблюдать после первого раза?Это где-то незарегистрировано?

Заранее спасибо.

1 Ответ

0 голосов
/ 20 февраля 2019

Оказывается, это была совершенно другая ошибка, которую я ... Я по ошибке забыл ввести AlarmManager в этот конкретный фрагмент, который я должен был ввести через Dagger.Спасибо за любой вклад!

...