I want to know why "private val binding get() = _binding!!" was used here?
private var _binding: ResultProfileBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
I am assuming that you got that code from this page in the documentation.
Their objective is to give you a way to access the _binding value without needing to deal with the fact that _binding can be null. In the portion of their example that you did not include, they have a comment on binding that points out that it can only be used between onCreateView() and onDestroyView(). If you are in a part of your code where you can guarantee that your code will execute between those two callbacks, you can reference binding, which will return the value of _binding coerced into a not-null type (ResultProfileBinding instead of ResultProfileBinding?).
However, if you get it wrong, and you try referencing binding before onCreateView() or after onDestroyView(), you will crash with a NullPointerException.
Personally, I would avoid this approach.
Related
From a Google Codelab (can't remember which one), they adviced doing the following for fragments:
class MyFragment : Fragment() {
private var _binding: MyFragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
_binding = MyFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
And then accessing the views with e.g. binding.button1.
Is there a specific reason for doing it like this, with _binding and binding? Are there better methods? Perhaps an extension for Fragments - like a BaseFragment - to avoid code duplication.
It's not recommended to use BaseFragment or BaseActivity or BaseViewModel... it will just add boilerplate code to your project.
For binding you can just use it like this:
Declaration:
private var binding: MyFragmentBinding? = null
onCreateView:
binding = MyFragmentBinding.inflate(inflater, container, false)
binding?.root
Usage:
binding?.button...
binding?.text...
binding?.cardView...
onDestroyView:
binding = null
And everything is going to work just fine but we use the null check a lot (?) and it's making the code messy and we need to get a lot of null checks if we need something from a certain view, so we are sure that between onCreateView and onDestroyView, the binding is not null so we have _binding and binding:
private var _binding: MyFragmentBinding? = null
private val binding get() = _binding!!
We make _binding mutable with var so we can give it a value, and we make it nullable so we can clear it later.
And we have binding that have a custom getter so that means that each time we call binding it's going to return the latest value from _binding and force that it's not null with !!.
Now we seperate our variables, we have _binding to initialize and clear our binding, and we have binding that is immutable and not nullable to use it only for accessing views without null check ?
See this question for some answers about the reason why binding needs to be nullable in a fragment.
See this answer of mine where I linked some articles about the problems with BaseFragments. You can usually achieve the code reuse without the drawbacks of inheritance by using extension properties and functions.
Here is an example of a property delegate that takes care of releasing the ViewBinding reference when necessary and rebuilding it when necessary. If you use this, all you need is a single binding property. Example is from the article about this tool.
class FirstFragment: Fragment(R.layout.first_fragment) {
private val binding by viewBinding(FirstFragmentBinding::bind)
override fun onViewCreated(view: View, bundle: Bundle?) {
super.onViewCreated(view, bundle)
binding.buttonPressMe.onClick {
showToast("Hello binding!")
}
}
I just saw that CommonsWare has adressed this issue in this post.
Here is the parent class:
abstract class ViewBindingFragment<Binding : ViewBinding>(
private val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> Binding
) : Fragment() {
private var binding: Binding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
return bindingInflater(inflater, container, false).apply { binding = this }.root
}
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
protected fun requireBinding(): Binding = binding
?: throw IllegalStateException("You used the binding before onCreateView() or after onDestroyView()")
protected fun useBinding(bindingUse: (Binding) -> Unit) {
bindingUse(requireBinding())
}
}
He then subclasses ViewBindingFragment like so:
class ListFragment :
ViewBindingFragment<TodoRosterBinding>(TodoRosterBinding::inflate) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
useBinding { binding ->
binding.items.layoutManager = LinearLayoutManager(context)
}
}
}
Though I am not sure it will eventually lead to less code, if useBinding { binding -> } needs to be called in several functions.
I now need to keep the status when I return, so I write as follows:
#AndroidEntryPoint
class IndexFragment : Fragment() {
private var rootView: View? = null
private var isFirstLoad = true
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
if (isFirstLoad) {
initView()
}
return rootView
}
private fun initView() {
binding = FragmentIndexBinding.inflate(layoutInflater)
rootView = binding?.root
}
}
I'm a novice android.
Leakcanary will report the rootView memory leak problem.I don't know whether this is a false alarm of Leakcanary or a real memory leak. I added a shared element transition and a page in a nested fragment. In addition, I can't think of how to keep the state when the previous page returns.
If you take a look at the documentation viewBinding in fragments should look like:
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null // <-- this prevents memory leaks! (and I think that's the problem in your case)
}
edit:
You might be confused why we are nullifying the _binding variable in onDestroyView, so here's the explanation:
The fragment outlives the view, which means that if we forget to clear the binding reference in onDestroyView this will cause a memory leak.
I've recently picked up an Android Studio project I started a year ago in Kotlin.
It features three fragments that can be navigated through by a bottom navigation bar.
Now, to break my current issue down to a simple example that even doesn't work for me:
Given there's a the editText object exercise in fragment_home.xml and I want to call and alter it in HomeFragment.kt.
I checked every source of advice I could find from Google & Stackoverflow and came up with the following code in HomeFragment.kt (partially pre-coded by AndroidStudio):
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val root: View = binding.root
val view: View = inflater!!.inflate(R.layout.fragment_home,container,false)
view.exercise.setText("This is an exceptionally hardcoded string")
The last line stands for every object I tried to reach. I also tried onClickListening for buttons like so:
val btnNewExercise = view.findViewById<Button>(R.id.btn_new_exercise)
btnNewExercise.setOnClickListener {view
Toast.makeText(view.context, "New exercise will be generated", Toast.LENGTH_SHORT).show()
println("Generated a new exercise")
}
but nothing happens when I start the app/ hit the buttons - I seem to can't get through to the actual view's objects to access them. Even ran into NullPointerExceptions on my way to a solution.
I could supply the fragment and layout files if needed - just thought this way it might be easier at first.
If anybody could tell me where I'm wrong I'd be really grateful! Thanks in advance!
You inflated the layout twice.
Remove this. You already inflated the layout using view binding in the FragmentHomeBinding.inflate... call
val view: View = inflater!!.inflate(R.layout.fragment_home,container,false)
and replace
view.exercise.setText("This is an exceptionally hardcoded string")
with (using binding
binding.exercise.setText("This is an exceptionally hardcoded string")
then the last line on your onCreateView should be
return binding.root
Note: You should have these class properties:
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
So it will look like this:
//move your view model as a class property so it will be accessible by other class methods
private val homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!//transform to immutable
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
//use the immutable view binding property
binding.exercise.setText("This is an exceptionally hardcoded string")
return binding.root
}
For more info, read view binding
Try this solution.
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.exercise.text="This is an exceptionally hardcoded string"
binding.btnNewExercise.setOnClickListener {
Toast.makeText(view.context, "New exercise will be generated", Toast.LENGTH_SHORT).show()
println("Generated a new exercise")
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
You need to return the view you made in the OnCreateView .
This question already has an answer here:
What is the difference between binding and _binding?
(1 answer)
Closed 1 year ago.
Let's look at the official example:
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
Why are there 2 similar variables: _binding and binding? Why can't we use one variable?
This is to have a non-nullable view of the binding property for convenience in the rest of the code, but still have the possibility to manage the actual nullable value in onCreateView/onDestroyView via _binding.
The lateinit approach in activities is nicer, but with lateinit you can't "reset" the binding back to null, so it's not applicable for fragments.
I was going through developer docs for Data binding. I found the following snippet:
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
Can anyone let me know the principle and advantage of using two variables for binding which are used in same fragment?
At first glance, it seems like lateinit would be a more natural choice. However, the Fragment instance is still usable after onDestroyView since Fragment instances can be torn down and reattached later. lateinit won't let you change the parameter back into uninitialized state, so it's not suitable for this purpose.
Using !! can result in Kotlin NPEs, which is not great. I would suggest modifying the sample code to provide better documentation and error reporting, like this:
/** This property is only valid between onCreateView and onDestroyView. */
private val binding get() = _binding ?:
error("Binding is only valid between onCreateView and onDestroyView.")
But practically, your Fragment is not going to be so complicated that you would have trouble tracking down an error like this anyway.
binding is a non-null property with nullable backing field, so when you access it you don't have to constantly use ? to check for nullability.
It will however throw KotlinNullPointerException if accessed when it's not valid as described by the comment.
EDIT
this solution will cause a memory leak, as pointed out by IR42, and here is why
ORIGINAL ANSWER
null safety, but I think using lateinit is a better solution for that purpose
private lateinit var binding : ResultProfileBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = ResultProfileBinding.inflate(inflater, container, false)
return binding.root
}