This snippet was used in an activity(onCreate) :
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
This snippet was used in an Fragment(onCreateView) :
cameraView = inflater.inflate(R.layout.fragment_camera_view, container, false)
return cameraView
CodePudding user response:
There are a few reasons to use Binding over direct inflation:
- Runtime performance : with direct inflation findViewById() it performs a top-down search of the View hierarchy until it finds a match. It is not optimised, and has no internal cache, so if we make the call that method twice in quick succession with the same View ID, then both invocations will require the same top-down search. While with binding, there is no hidden cost of accessing the
text1
field of aActivityMainBinding
instance. - Crash avoidance : sometimes we encounter crashes using Kotlin synthetic view properties if we attempt to access them too early in the Android lifecycle before they have been initialised. While it is still necessary to perform the inflation of the layout and binding at the correct phase of the Android lifecycle, that initialisation will actually be done within the Activity or Fragment code making it much more visible precisely when in the lifecycle the initialisation is taking place.
- Build Time : The improvements in runtime performance will incur a cost at build-time because it uses code generation. It will also increase your APK size and method count.
Reference: https://blog.stylingandroid.com/view-binding-internals/
CodePudding user response:
Two separate things really. setContentView
is how an Activity
displays its view hierarchy. You can either inflate the hierarchy yourself and pass that in (like you're doing here, with binding.inflate
and then passing the resulting binding.root
), or you can call setContentView(R.layout.some_layout)
and it'll take care of inflating that layout itself.
onCreateView
is a Fragment
method, it gets called when the Fragment
is initialising, and it needs to create its view hierarchy. So you need to override this, inflate your hierarchy, and then return it at the end so the Fragment
can use it.
So setContentView
gets called in an Activity
, and can take a layout reference or an actual inflated set of View
s. onCreateView
is a method in a Fragment
where you need to return an inflated set of View
s.
When it comes to actually inflating those View
s, you have a few options. Your examples are each using a different approach - so on top of one being an Activity
and one being a Fragment
, where you do things differently, you're also mixing and matching the way you inflate stuff.
The standard way to inflate a layout is to just grab a LayoutInflater
and call inflate
on it, passing a layout XML file so it can use that as a recipe to create all the necessary View
objects and connect them together:
// Typically in an Activity you'd use 'this' as the Context, since an Activity -is- a Context
// In a Fragment's onCreateView you get an inflater passed in, in a RecyclerView.Adapter's
// onCreateViewHolder you have access to the 'parent' which you can grab a Context from, etc
val inflater = LayoutInflater.from(context)
// Inflate an XML file - the second parameter is the parent container, used for working out
// things like layout parameters (e.g. to make 'match_parent' work).
// The last parameter is about attaching the View you're inflating - this is usually false!
// We're handing off the View, the thing that's using it will handle attaching it
val view = inflater.inflate(R.layout.some_layout, container, false)
And that's it! Now you have a bunch of actual View
s you can hand over for display.
// in your activity
setContentView(view)
// at the end of onCreateView
return view
If you're using View Binding, you usually don't want to use a LayoutInflater
directly. The generated binding class (e.g. ActivityMainBinding
) has an inflate
call that does the inflation for you using the relevant XML file, and then it creates a binding object with variables for all the View
objects in that inflated hierarchy.
That way you don't need to look anything up with findViewById
- that's basically already been done for you, and you can access them with binding.someId
. The binding object holds the inflated hierarchy too, which you can access with binding.root
. You can pass that for display in the same way as before:
// in your activity
setContentView(binding.root)
// at the end of onCreateView
return binding.root
See what I mean? You do things differently in an Activity
and a Fragment
, but the way you inflate the views can be the same - it just depends on what you're doing, if you're using View Binding then you'll be using the binding class instead of inflater.inflate
.
For completeness's sake, you can also take an already-inflated view hierarchy and create a binding object from that, using the bind
method - e.g. ActivityMainBinding.bind(view)
. This is useful if the inflation has already been taken care of, so you want to skip that step and just look up all the views and get your binding object.
Remember, if you inflate a layout again, you get a completely different set of objects, and if you display those you can't see or interact with the original ones. Generally, you only want to inflate a layout once - having two sets of Views is a sign you've made a mistake, and can lead to bugs like "why aren't my click listeners working" (you set them on the other Views that you replaced) etc.
A typical situation where you might have an already-inflated layout is if you're using the Activity
and Fragment
constructors where you supply a layout XML resource as a parameter, like class MainActivity : AppCompatActivity(R.layout.activity_main)
. When you call super.onCreate()
that takes care of the setContentView
bit (and you might not need to override onCreate
at all if that's all it does!). To create your binding object, you just need to bind
to the Activity
's view
property.
Same thing for Fragment
s - you can put the layout file in the constructor, then there's no need to override onCreateView
since that's all taken care of. Instead, you can override onViewCreated
to do your setup - you get passed the inflated view
and you can bind to that in there