How using ViewBinding with an abstract base class

后端 未结 3 961
礼貌的吻别
礼貌的吻别 2020-12-10 20:31

I started using ViewBinding. After searching for some example or advice how to use ViewBinding with an abstract base class that should handle same logic on views expected to

3条回答
  •  隐瞒了意图╮
    2020-12-10 21:10

    I have also come up with a Base Class solution that uses effectively final variables. My main goal was to :

    1. handle all the binding lifecycle in a base class
    2. let child class provide the binding class instance without using that route on its own (for eg if i had an abstract function abstract fun getBind():T , the child class could implement it and call it directly. I didn't wanted that as that would make the whole point of keeping bindings in base class moot ,I believe )

    So here it is. First the current structure of my app. The activities won't inflate themselves, the base class would do for them:

    Child Activities and Fragments:

    class MainActivity : BaseActivityCurrent(){
    
        var i = 0
    
        override val contentView: Int
            get() = R.layout.main_activity
    
    
        override fun setup() {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, MainFragment())
                .commitNow()
    
            syntheticApproachActivity()
        }
    
    
        private fun syntheticApproachActivity() {
            btText?.setOnClickListener { tvText?.text = "The current click count is ${++i}"  }
        }
    
    
        private fun fidApproachActivity() {
            val bt = findViewById

    Base Activities and Fragments :

    
    abstract class BaseActivityCurrent :AppCompatActivity(){
    
        abstract val contentView: Int
    
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(contentView)
            setup()
        }
    
        abstract fun setup()
    
    }
    abstract class BaseFragmentCurrent : Fragment(){
    
    
        abstract val contentView: Int
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            return inflater.inflate(contentView,container,false)
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            setup()
        }
    
        abstract fun setup()
    
    
    }
    

    As you can see the children classes were always easy to scale as base activities would do all the heavy work. and Since synthetics were being used extensively, there was not much of a problem. To use binding classes with the previously mentioned constraints I would:

    1. Need the child classes to implement functions that would provide data back to the parent fragments. That's the easy part, simply creating more abstract functions that return child's Binding Class's Instance would do.

    2. Store the child class's view binding in a variable (say val binding:T) such that the base class could nullify it in on destroy ad handle the lifecycle accordingly. A little tricky since the child's Binding class instance type is not known before hand. But making the parent as generic ( ) will do the job

    3. returning the view back to the system for inflation. again, easy because thankfully for most of the components, the system accepts an inflated view and having the child's binding instance will let me provide a view back to the system

    4. Preventing the child class from using the route created in point 1 directly . think about it: if a child class had a function getBind(){...} that returns their own binding class instance, why won't they use that and instead use super.binding ? and what is stopping them from using the getBind() function in the onDestroy(), where the bindings should rather not be accessed?

    So that's why I made that function void and passed a mutable list into it. the child class would now add their binding to the list that would be accessed by the parent. if they don't , it will throw an NPE . If they try to use it in on destroy or an other place, it will again throw an illegalstate exception . I also create a handy high order function withBinding(..) for easy usage.

    Base Binding activity and fragment:

    
    
    abstract class BaseActivityFinal : AppCompatActivity() {
    
        private var binding: VB_CHILD? = null
    
    
        //lifecycle
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(getInflatedLayout(layoutInflater))
            setup()
        }
        override fun onDestroy() {
            super.onDestroy()
            this.binding = null
        }
    
    
        //internal functions
        private fun getInflatedLayout(inflater: LayoutInflater): View {
            val tempList = mutableListOf()
            attachBinding(tempList, inflater)
            this.binding = tempList[0]
    
    
            return binding?.root?: error("Please add your inflated binding class instance at 0th position in list")
        }
    
        //abstract functions
        abstract fun attachBinding(list: MutableList, layoutInflater: LayoutInflater)
    
        abstract fun setup()
    
        //helpers
        fun withBinding(block: (VB_CHILD.() -> Unit)?): VB_CHILD {
            val bindingAfterRunning:VB_CHILD? = binding?.apply { block?.invoke(this) }
            return bindingAfterRunning
                ?:  error("Accessing binding outside of lifecycle: ${this::class.java.simpleName}")
        }
    
    
    }
    
    //--------------------------------------------------------------------------
    
    abstract class BaseFragmentFinal : Fragment() {
    
        private var binding: VB_CHILD? = null
    
    
        //lifecycle
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ) = getInflatedView(inflater, container, false)
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            setup()
        }
    
        override fun onDestroy() {
            super.onDestroy()
            this.binding = null
        }
    
    
        //internal functions
        private fun getInflatedView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            attachToRoot: Boolean
        ): View {
            val tempList = mutableListOf()
            attachBinding(tempList, inflater, container, attachToRoot)
            this.binding = tempList[0]
            return binding?.root
                ?: error("Please add your inflated binding class instance at 0th position in list")
    
        }
    
        //abstract functions
        abstract fun attachBinding(
            list: MutableList,
            layoutInflater: LayoutInflater,
            container: ViewGroup?,
            attachToRoot: Boolean
        )
    
        abstract fun setup()
    
        //helpers
        fun withBinding(block: (VB_CHILD.() -> Unit)?): VB_CHILD {
            val bindingAfterRunning:VB_CHILD? = binding?.apply { block?.invoke(this) }
            return bindingAfterRunning
                ?:  error("Accessing binding outside of lifecycle: ${this::class.java.simpleName}")
        }
    
    }
    
    

    Child activity and fragment:

    
    class MainActivityFinal:BaseActivityFinal() {
        var i = 0
    
        override fun setup() {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, MainFragmentFinal())
                .commitNow()
    
            viewBindingApproach()
        }
        
        private fun viewBindingApproach() {
            withBinding {
                btText.setOnClickListener { tvText.text = "The current click count is ${++i}"  }
                btText.performClick()
            }
    
        }
        
        override fun attachBinding(list: MutableList, layoutInflater: LayoutInflater) {
            list.add(MainActivityBinding.inflate(layoutInflater))
        }
    }
    
    //-------------------------------------------------------------------
    
    class MainFragmentFinal : BaseFragmentFinal() {
       
        override fun setup() {
            bindingApproach()
        }
    
        private fun bindingApproach() {
            withBinding {
                rbGroup.setOnCheckedChangeListener{ _, id ->
                    when(id){
                        radioBt1.id -> tvFragOutPut.text = "You Opt in for additional content"
                        radioBt2.id -> tvFragOutPut.text = "You DO NOT Opt in for additional content"
                    }
                }
                radioBt1.isChecked = true
                radioBt2.isChecked = false
    
                cbox.setOnCheckedChangeListener { _, bool ->
                    radioBt1.isEnabled = !bool
                    radioBt2.isEnabled = !bool
                }
            }
        }
    
    
        override fun attachBinding(
            list: MutableList,
            layoutInflater: LayoutInflater,
            container: ViewGroup?,
            attachToRoot: Boolean
        ) {
            list.add(MainFragmentBinding.inflate(layoutInflater,container,attachToRoot))
        }
    
    
    }
    
    
    

提交回复
热议问题