1

So i am migrating my code from Kotlin Synthetic to ViewBinding but when i write ViewBinding code its so much boilerplate like the first assign all views as lateinit var to use them in the whole class and then initialize these views in onViewCreated so then i use by lazy delegate to overcome that issue and everything work fines but when i try my app and it is single-activity-architecture based so when i go from Afragment to BFragment and then come back to AFragment some views like (toolbar not showing its title and drawer icons and menu) not inflated and i debug and realize that lazy delegate causing this because its giving first-time assigned value and also the variable is also immutable val so i thought may be this causing the issue so i code custom mutable lazy delegate but this doesn't help either i don't know what i am doing please suggest anything.

MutableLazyDelegate.kt

class MutableLazyDelegate<T>(val initializer: () -> T) : ReadWriteProperty<Any?, T> {

    private object UNINITIALIZED_VALUE

    private var value: Any? = UNINITIALIZED_VALUE

    @Suppress("UNCHECKED_CAST")
    override fun getValue(thisRef: Any?, property: KProperty<*>): T =
        if (value == UNINITIALIZED_VALUE)
            synchronized(this) {
                if (value == UNINITIALIZED_VALUE) initializer().also {
                    value = it
                } else value as T
            }
        else value as T

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        synchronized(this) {
            this.value = value
        }
    }
}

fun <T> mutableLazy(initializer: () -> T) = MutableLazyDelegate(initializer)

AFragment

class AFragment : Fragment(R.layout.a_fragment) {

    private val binding by viewBinding(AFragmentBinding::bind)

    private var toolbar by mutableLazy { binding.toolbar }

    ....
}

FragmentViewBindingDelegate

class FragmentViewBindingDelegate<T : ViewBinding>(
    val fragment: Fragment,
    val viewBindingFactory: (View) -> T
) : ReadOnlyProperty<Fragment, T> {

    var binding: T? = null

    init {
        fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
            val viewLifecycleOwnerLiveDataObserver =
                Observer<LifecycleOwner?> {
                    val viewLifecycleOwner = it ?: return@Observer
                    viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
                        override fun onDestroy(owner: LifecycleOwner) {
                            binding = null
                        }
                    })
                }

            override fun onCreate(owner: LifecycleOwner) {
                fragment.viewLifecycleOwnerLiveData.observeForever(
                    viewLifecycleOwnerLiveDataObserver
                )
            }

            override fun onDestroy(owner: LifecycleOwner) {
                fragment.viewLifecycleOwnerLiveData.removeObserver(
                    viewLifecycleOwnerLiveDataObserver
                )
            }
        })
    }

    override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
        val binding = binding
        if (binding != null) return binding
        val lifecycle = fragment.viewLifecycleOwner.lifecycle
        if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
            throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
        }
        return viewBindingFactory(thisRef.requireView()).also { this.binding = it }
    }
}

fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) =
    FragmentViewBindingDelegate(this, viewBindingFactory)
Burhan Khanzada
  • 945
  • 9
  • 27
  • is this what you are looking for https://github.com/Zhuinden/fragmentviewbindingdelegate-kt – Raghunandan Feb 10 '21 at 06:11
  • hm this look promising i will try this tomorrow the will give update – Burhan Khanzada Feb 10 '21 at 06:14
  • @Raghunandan well its the same code of that library i just extracted that in my code instead adding library and also it's not related to my problem because its about view not view binding itself – Burhan Khanzada Feb 10 '21 at 06:26
  • what do you mean. can you clarify `private val binding by viewBinding(MyFragmentBinding::bind)` is this not viewbinding? – Raghunandan Feb 10 '21 at 06:29
  • i mean that thois view binding delegate is working bine but when i initiliaze my view from viewbinding as val by lazy or var mutable lazy my view not showing properly – Burhan Khanzada Feb 10 '21 at 06:34
  • you don't need lazy for your case in my opinion. the reason to use the code in above link is to prevent leaks by setting binding to null. since using that readwriteproperty you will reduce boiler plate code – Raghunandan Feb 10 '21 at 07:24

1 Answers1

2

The solution in your case is to ditch the mutableLazy delegate and instead use an accessor

private val toolbar: Toolbar get() = binding.toolbar 

Although in most cases, I'd recommend grabbing the toolbar from the binding variable that is assigned to a val in the method where you're using it

override fun onViewCreated(...) {
    super onViewCreated(...) 
    val binding = binding
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428