-
-
Save gmk57/aefa53e9736d4d4fb2284596fb62710d to your computer and use it in GitHub Desktop.
| import android.view.LayoutInflater | |
| import android.view.View | |
| import android.view.ViewGroup | |
| import androidx.appcompat.app.AppCompatActivity | |
| import androidx.fragment.app.DialogFragment | |
| import androidx.fragment.app.Fragment | |
| import androidx.lifecycle.DefaultLifecycleObserver | |
| import androidx.lifecycle.Lifecycle | |
| import androidx.lifecycle.LifecycleOwner | |
| import androidx.viewbinding.ViewBinding | |
| import kotlin.properties.ReadOnlyProperty | |
| import kotlin.reflect.KProperty | |
| /** Activity binding delegate, may be used since onCreate up to onDestroy (inclusive) */ | |
| inline fun <T : ViewBinding> AppCompatActivity.viewBinding(crossinline factory: (LayoutInflater) -> T) = | |
| lazy(LazyThreadSafetyMode.NONE) { | |
| factory(layoutInflater) | |
| } | |
| /** Fragment binding delegate, may be used since onViewCreated up to onDestroyView (inclusive) */ | |
| fun <T : ViewBinding> Fragment.viewBinding(factory: (View) -> T): ReadOnlyProperty<Fragment, T> = | |
| object : ReadOnlyProperty<Fragment, T>, DefaultLifecycleObserver { | |
| private var binding: T? = null | |
| override fun getValue(thisRef: Fragment, property: KProperty<*>): T = | |
| binding ?: factory(requireView()).also { | |
| // if binding is accessed after Lifecycle is DESTROYED, create new instance, but don't cache it | |
| if (viewLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { | |
| viewLifecycleOwner.lifecycle.addObserver(this) | |
| binding = it | |
| } | |
| } | |
| override fun onDestroy(owner: LifecycleOwner) { | |
| binding = null | |
| } | |
| } | |
| /** Binding delegate for DialogFragments implementing onCreateDialog (like Activities, they don't | |
| * have a separate view lifecycle), may be used since onCreateDialog up to onDestroy (inclusive) */ | |
| inline fun <T : ViewBinding> DialogFragment.viewBinding(crossinline factory: (LayoutInflater) -> T) = | |
| lazy(LazyThreadSafetyMode.NONE) { | |
| factory(layoutInflater) | |
| } | |
| /** Not really a delegate, just a small helper for RecyclerView.ViewHolders */ | |
| inline fun <T : ViewBinding> ViewGroup.viewBinding(factory: (LayoutInflater, ViewGroup, Boolean) -> T) = | |
| factory(LayoutInflater.from(context), this, false) |
| class MainActivity : AppCompatActivity() { | |
| private val binding by viewBinding(ActivityMainBinding::inflate) | |
| override fun onCreate(savedInstanceState: Bundle?) { | |
| super.onCreate(savedInstanceState) | |
| setContentView(binding.root) | |
| binding.button.text = "Bound!" | |
| } | |
| } |
| // Don't forget to pass layoutId in Fragment constructor | |
| class RegularFragment : Fragment(R.layout.fragment) { | |
| private val binding by viewBinding(FragmentBinding::bind) | |
| override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | |
| super.onViewCreated(view, savedInstanceState) | |
| binding.button.text = "Bound!" | |
| } | |
| } |
| // DialogFragment with onCreateDialog doesn't have a view lifecycle, so we need a different delegate | |
| class DialogFragment1 : DialogFragment() { | |
| private val binding by viewBinding(FragmentBinding::inflate) | |
| override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | |
| binding.button.text = "Bound!" | |
| return AlertDialog.Builder(requireContext()).setView(binding.root).create() | |
| } | |
| } |
| // For DialogFragment with full-blown view we can use a regular Fragment delegate (actually the | |
| // whole code here is exactly the same as in RegularFragment) | |
| // NB: Constructor with layoutId was only recently added (in Fragment 1.3.0) | |
| class DialogFragment2 : DialogFragment(R.layout.fragment) { | |
| private val binding by viewBinding(FragmentBinding::bind) | |
| override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | |
| super.onViewCreated(view, savedInstanceState) | |
| binding.button.text = "Bound!" | |
| } | |
| } |
| // For RecyclerView we don't need any delegates, just a property. | |
| // Unfortunately, here we have a name overloading: View Binding vs "binding" holder to data (onBindViewHolder). | |
| // ViewGroup.viewBinding() helper function can reduce boilerplate a little. | |
| class Adapter1 : ListAdapter<String, Adapter1.Holder>(Differ()) { | |
| override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { | |
| return Holder(parent.viewBinding(ListItemBinding::inflate)) | |
| } | |
| override fun onBindViewHolder(holder: Holder, position: Int) { | |
| holder.binding.textView.text = getItem(position) | |
| } | |
| class Holder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) | |
| private class Differ : DiffUtil.ItemCallback<String>() { ... } | |
| } |
| // Alternatively, we can use generic BoundHolder for all Adapters | |
| class Adapter2 : ListAdapter<String, BoundHolder<ListItemBinding>>(Differ()) { | |
| override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BoundHolder<ListItemBinding> { | |
| return BoundHolder(parent.viewBinding(ListItemBinding::inflate)) | |
| } | |
| override fun onBindViewHolder(holder: BoundHolder<ListItemBinding>, position: Int) { | |
| holder.binding.textView.text = getItem(position) | |
| } | |
| private class Differ : DiffUtil.ItemCallback<String>() { ... } | |
| } | |
| open class BoundHolder<T : ViewBinding>(val binding: T) : RecyclerView.ViewHolder(binding.root) |
| // Personally, I prefer to encapsulate view creation & manipulation inside ViewHolder. | |
| // In this case BoundHolder can be used as a superclass. | |
| class Adapter3 : ListAdapter<String, Adapter3.Holder>(Differ()) { | |
| override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = Holder(parent) | |
| override fun onBindViewHolder(holder: Holder, position: Int) = holder.bind(getItem(position)) | |
| class Holder(parent: ViewGroup) : BoundHolder<ListItemBinding>(parent.viewBinding(ListItemBinding::inflate)) { | |
| fun bind(item: String) { | |
| binding.textView.text = item | |
| } | |
| } | |
| private class Differ : DiffUtil.ItemCallback<String>() { ... } | |
| } | |
| abstract class BoundHolder<T : ViewBinding>(protected val binding: T) : RecyclerView.ViewHolder(binding.root) |
helo @gmk57 so i already use this code and roll it to production. but im getting some intermitten crash on crashlytic saying {someFragmetnName} did not return a View from onCreateView() or this was called before onCreateView(). but i cant repoduce why this happend :v did you got same problem with this one? all i know is this are illegalStateException are from requireView() code.
and i already check all my code was always call binding on onViewCreated method
@KevinAngga, I've only seen this error when accessing ViewBinding outside the view lifecycle (onViewCreated-onDestroyView). This may happen if you register some callbacks/listeners/observers (e.g. in onViewCreated), and for some reason they get triggered when view does not exist. Coroutines, when incorrectly used, may cause this too. Can you share your code and/or stack trace?
@gmk57 i cant share the code / stact trace tho cause this one are company code. but when i tried this code and accessing some binding on destroyView it wont throw that same error ._. dunno why this happen on our user device lol, any way thank for the feedback :)
@gmk57 i have tried to implement this approach in custom views but dont work, you could show a example of this implementation in custom views, please.
@hqfranca Since a view has a same lifecycle as its child views, you don't really need a delegate. Simple property works fine:
class SomeView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) :
FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
private val binding = SomeViewBinding.inflate(LayoutInflater.from(context), this, true)
}
My ViewGroup.viewBinding helper does not fit well here, because it passes false as a last parameter (as it should for RecyclerView.ViewHolder). You can create a second helper, but I'm not sure if it's really worth the effort: it would only save you 27 chars. ;)
lol so sorry @gmk57 i made some silly error by myself xD. yea nothing wrong with this one, its can be use to viewdatabinding too