Обозреватель Livedata вызывается навсегда, даже с помощью removeObserver - PullRequest
0 голосов
/ 31 марта 2020

Я столкнулся с проблемой, которая сводит меня с ума.

У меня есть 4 фрагмента внутри действия. Лог c: FragA -> FragB -> Frag C -> FragD -> FragA -> ...

Я подключен к веб-сокетам, которые публикуют значения liveata. Чтобы перейти от FragB к Frag C, я жду события. В первый раз все работает нормально, веб-сокеты получены, событие запущено, и я собираюсь на Frag C.

Но, во второй раз (после Frag D -> Frag A), если Я go возвращаюсь к fragB, то же самое событие запускается еще раз. Пользователь не видит FragB и прибывает на Frag C.

. Это реальное поведение, но я не ожидаю этого.

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

Я попытался удалитьObserver в onDestroyView (), он работает и наблюдатель удаляется, но как только фрагмент снова попадает внутрь onActivityCreated (), и я наблюдаю liveata, наблюдатель мгновенно срабатывает ... Я всегда использую "viewLifecycleOwner" в качестве владельца.

Есть ли способ отменить выполнение liveData, если я когда-нибудь go вернусь к созданному фрагменту?

Все мои фраги расширяют ScopeFragment:

abstract class ScopedFragment : Fragment(), CoroutineScope {
    private lateinit var job: Job

    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        job = Job()
    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }
}

Мои liveData:

class MyLiveDatas {
    private val _myLiveData = MutableLiveData<CustomType>()
    val myLiveData: LiveData<CustomType>
        get() = _myLiveData


    fun customTrigger(webSocketMessage: WebSocketMessage) {
        val createdCustomType = CreatedCustomType(webSocketMessage)
        _myLiveData.post(createdCustomType)
    }
}

My Фрагмент:

class MyFragment: ScopedFragment(), KodeinAware {

    override val kodein by closestKodein()
    private val myLiveData: MyLiveDatas by instance()

    private val myLiveDataObserver = Observer<CustomType> { customType ->
        ... my actions
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        myLiveDatas.myLiveData.observe(viewLifecycleOwner, myLiveDataObserver)
    }


    override fun onDestroyView() {
        super.onDestroyView()

        myLiveDatas.myLiveData.removeObserver(myLiveDataObserver)

        // I've also try removeObservers with viewLifecycleOwner
    }
}

Большое спасибо!

Ответы [ 4 ]

1 голос
/ 31 марта 2020

Попробуйте наблюдать LiveData на onCreate() жизненного цикла Fragment с владельцем жизненного цикла как Activity и удалить наблюдателя на onDestroy() жизненного цикла Fragmen t.

Или, если это не тренируется использовать класс событий.

open class Event<out T>(private val content: T) {

var hasBeenHandled = false
    private set // Allow external read but not write

/**
 * Returns the content and prevents its use again.
 */
fun getContentIfNotHandled(): T? {
    return if (hasBeenHandled) {
        null
    } else {
        hasBeenHandled = true
        content
    }
}

/**
 * Returns the content, even if it's already been handled.
 */
fun peekContent(): T = content
}
1 голос
/ 31 марта 2020

Вам нужно использовать пользовательские живые данные, на случай, если вам нужно одно событие

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

class SingleLiveEvent<T> : MediatorLiveData<T>() {

    private val observers = ArraySet<ObserverWrapper<in T>>()

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        val wrapper = ObserverWrapper(observer)
        observers.add(wrapper)
        super.observe(owner, wrapper)
    }

    @MainThread
    override fun removeObserver(observer: Observer<in T>) {
        if (observers.remove(observer)) {
            super.removeObserver(observer)
            return
        }
        val iterator = observers.iterator()
        while (iterator.hasNext()) {
            val wrapper = iterator.next()
            if (wrapper.observer == observer) {
                iterator.remove()
                super.removeObserver(wrapper)
                break
            }
        }
    }

    @MainThread
    override fun setValue(t: T?) {
        observers.forEach { it.newValue() }
        super.setValue(t)
    }

    private class ObserverWrapper<T>(val observer: Observer<T>) : Observer<T> {

        private var pending = false

        override fun onChanged(t: T?) {
            if (pending) {
                pending = false
                observer.onChanged(t)
            }
        }

        fun newValue() {
            pending = true
        }
    }
}
0 голосов
/ 31 марта 2020

LiveData аналогична BehaviorRelay и воспроизводит последнее значение, о котором было сказано:

Live Данные не являются Live Событие , они не предназначены для диспетчеризация событий.

Обычная шина событий, PublishRelay или что-то вроде EventEmitter лучше подходят для этой проблемы.

Google разработал LiveData<Event<T>> и EventObserver , но если вы когда-либо используете observe(lifecycleOwner, Observer { вместо observe(lifecycleOwner, EventObserver {, он будет работать неправильно, что показывает, что это запах кода (LiveData<Event<T>> не работает с Observer, только EventObserver, но его метод observe по-прежнему принимает Observer s.)

Так что лично я бы предпочел использовать эту библиотеку EventEmitter, о которой я упоминал выше, с классом помощника LiveEvent .

// ViewModel
    private val eventEmitter = EventEmitter<Events>()
    val controllerEvents: EventSource<Events> = eventEmitter

// Fragment
    viewModel.controllerEvents.observe(viewLifecycleOwner) { event: ControllerEvents ->
        when (event) {
            is ControllerEvents.NewWordAdded -> showToast("Added ${event.word}")
        }.safe()
    }
0 голосов
/ 31 марта 2020

В этой статье описываются два способа достижения того, чего вы хотите.

Альтернатива 1: Оберните ваши живые данные в класс, который гарантирует, что значение наблюдается только один раз.

/**
 * Used as a wrapper for data that is exposed via a LiveData that represents an event.
 */
open class Event<out T>(private val content: T) {

    var hasBeenHandled = false
        private set // Allow external read but not write

    /**
     * Returns the content and prevents its use again.
     */
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    /**
     * Returns the content, even if it's already been handled.
     */
    fun peekContent(): T = content
}

Альтернатива 2: использовать пользовательский класс данных в реальном времени ( SingleLiveEvent ), который выдает значение только один раз.

...