Skip to content

Instantly share code, notes, and snippets.

@Aidanvii7
Last active December 8, 2021 09:49
Show Gist options
  • Save Aidanvii7/cc0596accefd7bc1b4eaca6a6eff2826 to your computer and use it in GitHub Desktop.
Save Aidanvii7/cc0596accefd7bc1b4eaca6a6eff2826 to your computer and use it in GitHub Desktop.
'fix' for correct lifecycle behaviour when using StateFlow in databinding expressions, issue here https://issuetracker.google.com/issues/184935697
class ExampleFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View = FragmentExampleBinding.inflate(inflater, container, false)
.also { fragmentBinding ->
bindViewLifecycleOwnerTo(fragmentBinding)
// bind whatever else you need to
}.root
}
import androidx.annotation.MainThread
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
/**
* See the [androidx.databinding.ViewDataBindingKtx] class as to why this works (specifically setLifecycleOwner and startCollection)
* [androidx.databinding.ViewDataBindingKtx] has been copied to the gist from the androidx.databinding:databinding-ktx artifact, do not copy into project!
*/
@MainThread
infix fun Fragment.bindViewLifecycleOwnerTo(binding: ViewDataBinding) {
LifecycleObserverImpl(
fragment = this,
binding = binding,
)
}
private class LifecycleObserverImpl(
private val fragment: Fragment,
private val binding: ViewDataBinding,
) : DefaultLifecycleObserver {
init {
fragment.viewLifecycleOwner.lifecycle.addObserver(this)
}
override fun onStart(owner: LifecycleOwner) {
// triggers startCollection in ViewDataBindingKtx
binding.lifecycleOwner = fragment.viewLifecycleOwner
}
override fun onStop(owner: LifecycleOwner) {
// triggers observerJob?.cancel() in ViewDataBindingKtx
binding.lifecycleOwner = null
}
}
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.databinding;
import androidx.annotation.RestrictTo
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import java.lang.ref.ReferenceQueue
import java.lang.ref.WeakReference
/**
* Helper methods for data binding Ktx features.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object ViewDataBindingKtx {
/**
* Method object extracted out to attach a listener to a bound StateFlow object.
*/
private val CREATE_STATE_FLOW_LISTENER =
CreateWeakListener { viewDataBinding, localFieldId, referenceQueue ->
StateFlowListener(viewDataBinding, localFieldId, referenceQueue)
.listener
}
@Suppress("unused") // called by generated code
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@JvmStatic
fun updateStateFlowRegistration(
viewDataBinding: ViewDataBinding,
localFieldId: Int,
observable: Flow<*>?
): Boolean {
viewDataBinding.mInStateFlowRegisterObserver = true
try {
return viewDataBinding.updateRegistration(
localFieldId, observable, CREATE_STATE_FLOW_LISTENER
)
} finally {
viewDataBinding.mInStateFlowRegisterObserver = false
}
}
internal class StateFlowListener(
binder: ViewDataBinding?,
localFieldId: Int,
referenceQueue: ReferenceQueue<ViewDataBinding>
) : ObservableReference<Flow<Any?>> {
// keep this weak so that we don't end up leaking the lifecycle owner if the
// binding is GC'ed. (see: b/176886060)
private var _lifecycleOwnerRef : WeakReference<LifecycleOwner>? = null
private var observerJob : Job? = null
private val listener = WeakListener<Flow<Any?>>(
binder, localFieldId, this, referenceQueue
)
override fun getListener(): WeakListener<Flow<Any?>> {
return listener
}
override fun addListener(target: Flow<Any?>?) {
val owner = _lifecycleOwnerRef?.get() ?: return
if (target != null) {
startCollection(owner, target)
}
}
override fun removeListener(target: Flow<Any?>?) {
observerJob?.cancel()
observerJob = null
}
private fun startCollection(owner: LifecycleOwner, flow: Flow<Any?>) {
observerJob?.cancel()
observerJob = owner.lifecycleScope.launchWhenCreated {
flow.collect {
listener.binder?.handleFieldChange(listener.mLocalFieldId, listener.target, 0)
}
}
}
override fun setLifecycleOwner(lifecycleOwner: LifecycleOwner?) {
if (_lifecycleOwnerRef?.get() === lifecycleOwner) {
return
}
observerJob?.cancel()
if (lifecycleOwner == null) {
_lifecycleOwnerRef = null
return
}
_lifecycleOwnerRef = WeakReference(lifecycleOwner)
val target = listener.target
if (target != null) {
startCollection(lifecycleOwner, target)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment