Skip to content

Commit c4539d7

Browse files
author
Lucas Sales
committed
ViewBinding delegates
1 parent 88be367 commit c4539d7

File tree

5 files changed

+154
-22
lines changed

5 files changed

+154
-22
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.monstarlab.extensions
2+
3+
import android.view.LayoutInflater
4+
import androidx.appcompat.app.AppCompatActivity
5+
import androidx.lifecycle.Lifecycle
6+
import androidx.lifecycle.LifecycleObserver
7+
import androidx.lifecycle.OnLifecycleEvent
8+
import androidx.viewbinding.ViewBinding
9+
import kotlin.properties.ReadOnlyProperty
10+
import kotlin.reflect.KProperty
11+
12+
class ActivityViewBindingDelegate<T : ViewBinding>(
13+
private val activity: AppCompatActivity,
14+
private val viewBinder: (LayoutInflater) -> T,
15+
private val beforeSetContent: () -> Unit = {}
16+
) : ReadOnlyProperty<AppCompatActivity, T>, LifecycleObserver {
17+
18+
private var activityBinding: T? = null
19+
20+
init {
21+
activity.lifecycle.addObserver(this)
22+
}
23+
24+
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
25+
fun createBinding() {
26+
initialize()
27+
beforeSetContent()
28+
activity.setContentView(activityBinding?.root)
29+
activity.lifecycle.removeObserver(this)
30+
}
31+
32+
private fun initialize() {
33+
if (activityBinding == null) {
34+
activityBinding = viewBinder(activity.layoutInflater)
35+
}
36+
}
37+
38+
override fun getValue(thisRef: AppCompatActivity, property: KProperty<*>): T {
39+
ensureMainThread()
40+
41+
initialize()
42+
return activityBinding!!
43+
}
44+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.monstarlab.extensions
2+
3+
import android.view.View
4+
import androidx.fragment.app.Fragment
5+
import androidx.lifecycle.*
6+
import androidx.viewbinding.ViewBinding
7+
import kotlin.properties.ReadOnlyProperty
8+
import kotlin.reflect.KProperty
9+
10+
class FragmentViewBindingDelegate<T : ViewBinding>(
11+
private val fragment: Fragment,
12+
private val viewBinder: (View) -> T,
13+
private val disposeEvents: T.() -> Unit = {}
14+
) : ReadOnlyProperty<Fragment, T>, LifecycleObserver {
15+
16+
private inline fun Fragment.observeLifecycleOwnerThroughLifecycleCreation(crossinline viewOwner: LifecycleOwner.() -> Unit) {
17+
lifecycle.addObserver(object : DefaultLifecycleObserver {
18+
override fun onCreate(owner: LifecycleOwner) {
19+
viewLifecycleOwnerLiveData.observe(
20+
this@observeLifecycleOwnerThroughLifecycleCreation,
21+
Observer { viewLifecycleOwner ->
22+
viewLifecycleOwner.viewOwner()
23+
})
24+
}
25+
})
26+
}
27+
28+
private var fragmentBinding: T? = null
29+
30+
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
31+
fun disposeBinding() {
32+
fragmentBinding?.disposeEvents()
33+
fragmentBinding = null
34+
}
35+
36+
init {
37+
fragment.observeLifecycleOwnerThroughLifecycleCreation {
38+
lifecycle.addObserver(this@FragmentViewBindingDelegate)
39+
}
40+
}
41+
42+
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
43+
44+
ensureMainThread()
45+
46+
val binding = fragmentBinding
47+
if (binding != null) {
48+
return binding
49+
}
50+
51+
val lifecycle = fragment.viewLifecycleOwner.lifecycle
52+
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
53+
throw IllegalStateException("Fragment views are destroyed.")
54+
}
55+
return viewBinder(thisRef.requireView()).also { fragmentBinding = it }
56+
}
57+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.monstarlab.extensions
2+
3+
import android.view.View
4+
import androidx.viewbinding.ViewBinding
5+
import kotlin.properties.ReadOnlyProperty
6+
import kotlin.reflect.KProperty
7+
8+
class GlobalViewBindingDelegate<T : ViewBinding>(val viewBinder: (View) -> T) :
9+
ReadOnlyProperty<View, T> {
10+
11+
override fun getValue(thisRef: View, property: KProperty<*>): T {
12+
return viewBinder(thisRef)
13+
}
14+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.monstarlab.extensions
2+
3+
import android.app.Activity
4+
import android.os.Looper
5+
import android.view.LayoutInflater
6+
import android.view.View
7+
import androidx.appcompat.app.AppCompatActivity
8+
import androidx.fragment.app.Fragment
9+
import androidx.viewbinding.ViewBinding
10+
11+
inline fun <T : ViewBinding> Activity.viewBinder(crossinline bindingInflater: (LayoutInflater) -> T) =
12+
lazy(LazyThreadSafetyMode.NONE) {
13+
bindingInflater.invoke(layoutInflater)
14+
}
15+
16+
fun <T : ViewBinding> AppCompatActivity.viewBinding(
17+
bindingInflater: (LayoutInflater) -> T,
18+
beforeSetContent: () -> Unit = {}
19+
) =
20+
ActivityViewBindingDelegate(this, bindingInflater, beforeSetContent)
21+
22+
fun <T : ViewBinding> Fragment.viewBinding(
23+
viewBindingFactory: (View) -> T,
24+
disposeEvents: T.() -> Unit = {}
25+
) =
26+
FragmentViewBindingDelegate(this, viewBindingFactory, disposeEvents)
27+
28+
fun <T : ViewBinding> globalViewBinding(viewBindingFactory: (View) -> T) =
29+
GlobalViewBindingDelegate(viewBindingFactory)
30+
31+
internal fun ensureMainThread() {
32+
if (Looper.myLooper() != Looper.getMainLooper()) {
33+
throw IllegalThreadStateException("View can be accessed only on the main thread.")
34+
}
35+
}

app/src/main/java/com/monstarlab/features/sample/SampleFragment.kt

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,18 @@
11
package com.monstarlab.features.sample
22

33
import android.os.Bundle
4-
import android.view.LayoutInflater
54
import android.view.View
6-
import android.view.ViewGroup
7-
import androidx.fragment.app.Fragment
85
import com.monstarlab.R
96
import com.monstarlab.base.BaseFragment
107
import com.monstarlab.databinding.FragmentSampleBinding
118
import com.monstarlab.extensions.collectFlow
9+
import com.monstarlab.extensions.viewBinding
1210

13-
class SampleFragment: BaseFragment(R.layout.fragment_sample) {
11+
class SampleFragment : BaseFragment(R.layout.fragment_sample) {
1412

15-
val viewModel by viewModel<SampleViewModel>()
13+
private val viewModel by viewModel<SampleViewModel>()
1614

17-
private var _binding: FragmentSampleBinding? = null
18-
private val binding get() = _binding!!
19-
20-
override fun onCreateView(
21-
inflater: LayoutInflater,
22-
container: ViewGroup?,
23-
savedInstanceState: Bundle?
24-
): View? {
25-
_binding = FragmentSampleBinding.inflate(inflater, container, false)
26-
val view = binding.root
27-
return view
28-
}
29-
30-
override fun onDestroyView() {
31-
super.onDestroyView()
32-
_binding = null
33-
}
15+
private val binding by viewBinding(FragmentSampleBinding::bind)
3416

3517
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
3618
super.onViewCreated(view, savedInstanceState)

0 commit comments

Comments
 (0)