0

I'm playing around with Kotlin on Android and one thing makes me confused.

When I converted few Fragments from Java to Kotlin I got this:

class XFragment : Fragment() {
    
    private var binding: FragmentXBinding? = null

    override fun onCreateView(inflater: LayoutInflater,
                          container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = FragmentUhfReadBinding.inflate(inflater, container, false)
        return binding!!.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding!!.slPower.addOnChangeListener(this)
        binding!!.btnClearTagList.setOnClickListener(this)
    }

    // ...

    private fun updateUi(){
        binding!!.someTextView.text = getSomeTextViewText()
        binding!!.someSlider.value = getSomeSliderValue()
    }

}

I can't make binding non-nullable, because it has to be initialized after XFragment class constructor, in onCreateView() or later.

So with this approach it has to be nullable and I have to put !! everywhere.

Is there some way to avoid these !!?

Kamil
  • 13,363
  • 24
  • 88
  • 183

1 Answers1

6

The official documentation suggests this strategy:

private var _binding: FragmentXBinding? = null

// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

Ultimately, it becomes just like requireActivity() and requireContext(). You just need to remember not to use it in a callback that might get called outside the view lifecycle.

Note, you can create your view using the super-constructor layout parameter and then bind to the pre-existing view in onViewCreated. Then you might not even need to have it in a property. I rarely need to do anything with it outside onViewCreated() and functions directly called by it:

class XFragment : Fragment(R.layout.fragment_x) {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val binding = FragmentXBinding.bind(view)
        binding.slPower.addOnChangeListener(this)
        binding.btnClearTagList.setOnClickListener(this)
    }

}
Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • 2
    Similar alternative: use https://github.com/Zhuinden/fragmentviewbindingdelegate-kt – CommonsWare Oct 14 '22 at 19:34
  • Yes, I was going to come back and add that after digging up the link, thank you. – Tenfour04 Oct 14 '22 at 19:36
  • That official strategy looks even more ugly than `!!` everywhere in my opinion, however second solution with keeping non-nullable `binding` in `onViewCreated` scope looks really good. – Kamil Oct 14 '22 at 19:45
  • But what about clearing `binding` in that second solution? Is binding cleared automatically? – Kamil Oct 14 '22 at 19:55
  • @CommonsWare The alternative you provided looks perfect and code is very easy to understand. Thanks for very useful link! – Kamil Oct 14 '22 at 20:01
  • @Kamil: It's been working well for me on a mid-sized project. It is not a "silver bullet", and from time to time we screw up and try referencing the binding after `onDestroyView()` (e.g., animation listener completion callback). The official solution is not an improvement in that regard -- it's pretty much just a mismatch between how view binding works and how fragments work. – CommonsWare Oct 14 '22 at 20:22
  • The second solution doesn’t leak it because the reference only lives for as long as the `onViewCreated()` function and any view listeners you use it in. So when the views are destroyed, those captured references are dropped too. – Tenfour04 Oct 14 '22 at 20:54
  • Is there any benefit to the non-null getter approach over just making `binding` a `lateinit` property? I feel like there's a reason the former is recommended but I've never worked out why – cactustictacs Oct 14 '22 at 21:52
  • 1
    @cactustictacs There's no way to drop the strong reference if it's non-nullable, so during the time the Fragment instance is detached, after `onDestroyView()`, it is leaking its own outdated views. I don't know enough about the ways in which a Fragment instance might be held in memory for reuse to be able to say for sure whether it's kind of a trivial, short-lived leak or not, but it is at least "improper". Note that for Activities, they do recommend the `lateinit` approach in the official documentation. Activity instances are never reused after `onDestroy()`. – Tenfour04 Oct 14 '22 at 22:54
  • @Tenfour04 oh ok, so it's the ability to manually clear the reference with a null once it's no longer needed - I didn't check that link but I see what they're doing in the example now. Thanks, it's a good thing to know! – cactustictacs Oct 14 '22 at 23:32