Fragment getLayoutInflater() vs. LayoutInflater.from(getContext()) - android

Can anyone explain what's the difference between A and B in the following code snippet?
It's from a DialogFragment.
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// A) this works
binding = DialogFragmentPeriodPickerBinding.inflate(LayoutInflater.from(context))
// B) this doesn't work: no compiler error, but dialog won't show
binding = DialogFragmentPeriodPickerBinding.inflate(layoutInflater)
[..] // binding.root is later on passed to setView(binding.root) of MaterialAlertDialogBuilder
}

Assuming you're using Fragment 1.2.3 (or Fragment 1.3.0-alpha02, which contains the same fix for a StackoverflowError when using layoutInflater in onCreateDialog()), you should always use layoutInflater. The fragment's layoutInflater is the only one that can correctly inflate <fragment> tags or its replacement, FragmentContainerView and connect those child fragments to your DialogFragment correctly.
However, if you're not using child fragments in your XML, you shouldn't see any difference between the two.

there is not much difference between layoutInflater and LayoutInflater.from(getContext()) in fact that two are pretty same thing if you look at the source code. problem with your code is that when you call layoutInflater in onCreateDialog its not set yet so our good friend android is trying to set now but it fails. because under the hood it calls onCreateDialog(SavedInstance) yes the function where you mentioned layoutInflater in order to get context from the dialog. so there we have infinite loop which will most likely lead us to crash.

Related

Can somebody explain me the difference between these two usages?I am aware of the fragment inflation but the other one is confusing,is both same?

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
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 a ActivityMainBinding 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/
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 Views. onCreateView is a method in a Fragment where you need to return an inflated set of Views.
When it comes to actually inflating those Views, 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 Views 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 Fragments - 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

MVVM architecture with custom view

I want to make a custom view in android with MVVM architecture. First of all, I want to ask, is ViewModel working perfectly with a custom view as it works in case of activity or fragment? Can we get ViewModel from ViewModel provider in a custom view?
If I need to make a separate custom view what will the correct approach?
A better alternative would be to use the new API view.findViewTreeViewModelStoreOwner() which gives you the viewModelStoreOwner(Fragment if view is attached to the fragment o/w activity)
You can create ViewModelProvider and then get the ViewModel.
Below is an example of code in Kotlin
private val viewModel by lazy(LazyThreadSafetyMode.NONE) {
ViewModelProvider(viewModelStoreOwner).get(ViewModel::class.java)
}
Similarly, there are other similar APIs like view.findViewTreeLifecycleOwner() and view.findViewTreeSavedStateRegistryOwner()
It is a much cleaner approach as you don't have to typecast your context into Activity or Fragment and will scale to other implementations of ViewModelStoreOwner as well.
One thing to note here is that view may have a shorter life span compared to Activity/Fragment and so you might have to make a custom view Lifecycle(so that your LiveData subscription gets managed properly) using LifecycleRegistry based on onAttachedToWindow and onDetachedFromWindow callbacks
Q: Can we get ViewModel from ViewModel provider in a custom view?
Ans: Simple answer would be yes you can !
But How? (Further explanation) ViewModelProviders required either context as Activity or Fragment. So you can retrieve context from your CustomView class using getContext() which would be Activity/Fragment where you're using it.
Cast that context to either of type & provide it to ViewModelProviders which will give you object of that Activity/Fragment container.
Hence using like this, you can share ViewModel between your CustomView and Activity/Fragment.
Side Note: You can also make your CustomView implement LifeCycleObserver, in such way you can also make your view respect lifecycle of Activity/Fragment for initialization/destruction stuffs.

Does inflater require Activity's context?

I sometimes inject link to LayoutInflater by Dagger, and produce it in module from Application context like this: LayoutInflater.from(application);. It reduces lines of code.
But colleges tell me that it is wrong way, and it has to be given from Activity context by LayoutInflater.from(MainActivity.this);
Is it true? Does behaviour of layout inflater depend on type of context?
Yes it is true. There's a big difference considering styles.
The LayoutInflater creates the views by calling their constructors. There it passes the context you passed to it. So if you use the application context instead of an activity context you might lack some information.
It's the same issue like using application context for creating views directly. Activities may define different styles and their contexts wrap these information.
Taking into consideration how you could get it, there's not a big difference. Internally LayoutInflater.cloneInContext(Context) is called to apply different context configurations.
Create a copy of the existing LayoutInflater object, with the copy pointing to a different Context than the original. This is used by ContextThemeWrapper to create a new LayoutInflater to go along with the new Context theme.
With the application context you don't get this.
If I understand things correctly, in case of using application context for creating LayoutInflater you have a chance to loose your Theme settings. See here for more details.
UPDATED
From source code of layout inflater:
Object[] args = mConstructorArgs;
args[1] = attrs;
constructor.setAccessible(true);
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
As you can see, your context (in your case application context) passes in constructions of View. It means that scope of your view will be application, not activity.
If we Application context means, the inflater instance exist throughout the application until the app is killed. In other case, if we use activity context the inflater instance will be removed once activity is destroyed.
you can use getApplicationContext() when you know you need a Context for something that may live longer than any other likely Context you have at your disposal like services .
So It is best practice to use activity context when you dont need your object that will hold for long time or to global scale.
Hope it will help.

why mostly only onCreateView() is being used in most of fragment examples not onViewCreated() [duplicate]

What's the essential difference between these two methods? When I create a TextView, should I use one over the other for performance?
Edit:
What's the difference from
onCreateView() {
root = some view
View v = new View(some context);
root.add(v);
return root;
}
onViewCreated() {
View v = new View(some context);
getView().add(v);
}
We face some crashes initializing view in onCreateView.
You should inflate your layout in onCreateView but shouldn't initialize other views using findViewById in onCreateView.
Because sometimes view is not properly initialized. So always use findViewById in onViewCreated(when view is fully created) and it also passes the view as parameter.
onViewCreated is a make sure that view is fully created.
onViewCreated android Documentation
Called immediately after onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle) has returned, but before any saved state has been restored in to the view. This gives subclasses a chance to initialize themselves once they know their view hierarchy has been completely created. The fragment's view hierarchy is not however attached to its parent at this point.
onViewCreated is called immediately after onCreateView (the method you initialize and create all your objects, including your TextView), so it's not a matter of performance.
From the developer site:
onViewCreated(View view, Bundle savedInstanceState)
Called immediately after onCreateView(LayoutInflater, ViewGroup, Bundle) has returned, but before any saved state has been restored in to the view. This gives subclasses a chance to initialize themselves once they know their view hierarchy has been completely created. The fragment's view hierarchy is not however attached to its parent at this point.
Source: Fragment#onViewCreated
It's better to do any assignment of subviews to fields in onViewCreated. This is because the framework does an automatic null check for you to ensure that your Fragment's view hierarchy has been created and inflated (if using an XML layout file) properly.
Code snippet from: FragmentManger.java
// This calls onCreateView()
f.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), null, f.mSavedFragmentState);
// Null check avoids possible NPEs in onViewCreated
// It's also safe to call getView() during or after onViewCreated()
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
if (f.mHidden) f.mView.setVisibility(View.GONE);
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
onCreateView() is the Fragment equivalent of onCreate() for Activities and runs during the View creation.
onViewCreated() runs after the View has been created.
should I use one over the other for performance? NO. There's no evidence of a performance boost.
There is actually an onCreate() method in Fragments too, but it's rarely used (I do never use it, nor find a good use case for it).
I always use onCreateView() in Fragments as a replacement for onCreate().
And I'm happy with that.
The docs for Fragment.onCreateView() now says:
It is recommended to only inflate the layout in this method and move logic that operates on the returned View to onViewCreated(View, Bundle).
No need for us to understand why; we just need to do as the docs says, but it would be interesting to know why this recommendation exists. My best guess is separation of concern, but IMHO this makes it a little bit more complicated than it has to be.
onCreateView returns the inflated view. OnViewCreated is called just after onCreateView and get has parameter the inflated view. Its return type is void
Ok, so If we are going to talk about onCreateView() and onViewCreated(). It is worth while to talk a little about the fragment lifecycle. The full documentation about the fragment lifecycle can be found HERE, I recommend that you read up on it, as we will only be talking about the states relevant to onCreateView() and onViewCreated().
The fragment lifecycle contains 5 states:
1) INITIALIZED
2) CREATED
3) STARTED
4) RESUMED
5) DESTROYED
The reason that we need to talk about the fragment lifecycle is because both onCreateView() and onViewCreated() get called during the CREATED state in the lifecycle.
So when a fragment is instantiated, it begins in the INITIALIZED state, for example when you see:
CustomFragment frag1 = new CustomFragment() //`INITIALIZED` state
CustomFragment.class //`INITIALIZED` state
The .class syntax is the class literal syntax and for a brief summary I would recommend reading the blog post HERE
For a fragment to transition into other lifecycle states, it must be added to a Fragment Manager.
The Fragment Manager is responsible for determining what state its fragment should be in and then moving them into that state.
Difference between onCreateView() and onViewCreated()
Once the fragment has been added to the Fragment Manager, onAttach() is called to attach the fragment to the host activity.
Once onAttch() is called the fragment enters the CREATED state. It is in this state that the Android system begins creating the fragment's view. This can be done a few ways, for example the documentation states:
In most cases, you can use the fragment constructors that take a #LayoutId, which automatically inflates the view at the appropriate time. You can also override onCreateView() to programmatically inflate or create your fragment's view
If we look at the documentation for onCreateView() we see that:
It is recommended to only inflate the layout in this method and move logic that operates on the returned View to onViewCreated
Conclusion
Now combining everything we can come to the conclusion, both onCreateView() and onViewCreated() are called during the CREATED state of a fragment's life cycle. However, onCreateView() is called first and should only be used to inflate the fragment's view. onViewCreated() is called second and all logic pertaining to operations on the inflated view should be in this method.
i think the main different between these is when you use kotlin.in onCreateView() every Time you want to access to view in your xml file you should use findViewById but in onViewCreated you can simply access to your view just by calling the id of it.
The main reason I would use onViewCreated is since it separates any initialization logic from the view hierarchy inflation/creation logic which should go in the onCreateView . All other performance characteristics look the same.
In the Google Documentation
There is a new solution to declare the xml resource in the Fragment superclass
class ExampleFragment : Fragment(R.layout.example_fragment) { }
Then bind the view in onViewCreate
private lateinit var binding : ExampleFragmentBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = ExampleFragmentBinding.bind(view)
}
In this way you do not need to inflate view in onCreatedView or handle the view in onDestroy
onCreateView is used in fragment to create layout and inflate view.
onViewCreated is used to reference the view created by above method.
Lastly it is a good practice to define action listener in onActivityCreated.

Difference between onCreateView and onViewCreated in Fragment

What's the essential difference between these two methods? When I create a TextView, should I use one over the other for performance?
Edit:
What's the difference from
onCreateView() {
root = some view
View v = new View(some context);
root.add(v);
return root;
}
onViewCreated() {
View v = new View(some context);
getView().add(v);
}
We face some crashes initializing view in onCreateView.
You should inflate your layout in onCreateView but shouldn't initialize other views using findViewById in onCreateView.
Because sometimes view is not properly initialized. So always use findViewById in onViewCreated(when view is fully created) and it also passes the view as parameter.
onViewCreated is a make sure that view is fully created.
onViewCreated android Documentation
Called immediately after onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle) has returned, but before any saved state has been restored in to the view. This gives subclasses a chance to initialize themselves once they know their view hierarchy has been completely created. The fragment's view hierarchy is not however attached to its parent at this point.
onViewCreated is called immediately after onCreateView (the method you initialize and create all your objects, including your TextView), so it's not a matter of performance.
From the developer site:
onViewCreated(View view, Bundle savedInstanceState)
Called immediately after onCreateView(LayoutInflater, ViewGroup, Bundle) has returned, but before any saved state has been restored in to the view. This gives subclasses a chance to initialize themselves once they know their view hierarchy has been completely created. The fragment's view hierarchy is not however attached to its parent at this point.
Source: Fragment#onViewCreated
It's better to do any assignment of subviews to fields in onViewCreated. This is because the framework does an automatic null check for you to ensure that your Fragment's view hierarchy has been created and inflated (if using an XML layout file) properly.
Code snippet from: FragmentManger.java
// This calls onCreateView()
f.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), null, f.mSavedFragmentState);
// Null check avoids possible NPEs in onViewCreated
// It's also safe to call getView() during or after onViewCreated()
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
if (f.mHidden) f.mView.setVisibility(View.GONE);
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
onCreateView() is the Fragment equivalent of onCreate() for Activities and runs during the View creation.
onViewCreated() runs after the View has been created.
should I use one over the other for performance? NO. There's no evidence of a performance boost.
There is actually an onCreate() method in Fragments too, but it's rarely used (I do never use it, nor find a good use case for it).
I always use onCreateView() in Fragments as a replacement for onCreate().
And I'm happy with that.
The docs for Fragment.onCreateView() now says:
It is recommended to only inflate the layout in this method and move logic that operates on the returned View to onViewCreated(View, Bundle).
No need for us to understand why; we just need to do as the docs says, but it would be interesting to know why this recommendation exists. My best guess is separation of concern, but IMHO this makes it a little bit more complicated than it has to be.
onCreateView returns the inflated view. OnViewCreated is called just after onCreateView and get has parameter the inflated view. Its return type is void
Ok, so If we are going to talk about onCreateView() and onViewCreated(). It is worth while to talk a little about the fragment lifecycle. The full documentation about the fragment lifecycle can be found HERE, I recommend that you read up on it, as we will only be talking about the states relevant to onCreateView() and onViewCreated().
The fragment lifecycle contains 5 states:
1) INITIALIZED
2) CREATED
3) STARTED
4) RESUMED
5) DESTROYED
The reason that we need to talk about the fragment lifecycle is because both onCreateView() and onViewCreated() get called during the CREATED state in the lifecycle.
So when a fragment is instantiated, it begins in the INITIALIZED state, for example when you see:
CustomFragment frag1 = new CustomFragment() //`INITIALIZED` state
CustomFragment.class //`INITIALIZED` state
The .class syntax is the class literal syntax and for a brief summary I would recommend reading the blog post HERE
For a fragment to transition into other lifecycle states, it must be added to a Fragment Manager.
The Fragment Manager is responsible for determining what state its fragment should be in and then moving them into that state.
Difference between onCreateView() and onViewCreated()
Once the fragment has been added to the Fragment Manager, onAttach() is called to attach the fragment to the host activity.
Once onAttch() is called the fragment enters the CREATED state. It is in this state that the Android system begins creating the fragment's view. This can be done a few ways, for example the documentation states:
In most cases, you can use the fragment constructors that take a #LayoutId, which automatically inflates the view at the appropriate time. You can also override onCreateView() to programmatically inflate or create your fragment's view
If we look at the documentation for onCreateView() we see that:
It is recommended to only inflate the layout in this method and move logic that operates on the returned View to onViewCreated
Conclusion
Now combining everything we can come to the conclusion, both onCreateView() and onViewCreated() are called during the CREATED state of a fragment's life cycle. However, onCreateView() is called first and should only be used to inflate the fragment's view. onViewCreated() is called second and all logic pertaining to operations on the inflated view should be in this method.
i think the main different between these is when you use kotlin.in onCreateView() every Time you want to access to view in your xml file you should use findViewById but in onViewCreated you can simply access to your view just by calling the id of it.
The main reason I would use onViewCreated is since it separates any initialization logic from the view hierarchy inflation/creation logic which should go in the onCreateView . All other performance characteristics look the same.
In the Google Documentation
There is a new solution to declare the xml resource in the Fragment superclass
class ExampleFragment : Fragment(R.layout.example_fragment) { }
Then bind the view in onViewCreate
private lateinit var binding : ExampleFragmentBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = ExampleFragmentBinding.bind(view)
}
In this way you do not need to inflate view in onCreatedView or handle the view in onDestroy
onCreateView is used in fragment to create layout and inflate view.
onViewCreated is used to reference the view created by above method.
Lastly it is a good practice to define action listener in onActivityCreated.

Categories

Resources