I'm following a tutorial on DataBinding and it replaces:
setContentView(R.layout.activity_main) with
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
I'm wondering how setting the value of binding to an instance with setContentView() sets the content view. Because it's not calling setContentView().
I'm also wondering how the ActivityMainBinding object is involved in the following assignment:
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
Have you followed the source code? :)
When you're calling
DataBindingUtil.setContentView(#NonNull Activity activity, int layoutId)
it calls
DataBindingUtil.setContentView(#NonNull Activity activity, int layoutId, #Nullable DataBindingComponent bindingComponent)
this method internally calls
activity.setContentView(layoutId);.
Read more here
And also if your view has id (like R.id.done_button) you don't have to call findViewById method. It should be available through the binding object, like
binding.doneButton
Related
This Android tutorial introduces the concept of view binding, with this section demonstrating how to use it. In this case, the view binding is set up using the following code.
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
The explanation for the call to ActivityMainBinding.inflate() is as follows:
This line initializes the binding object which you'll use to access
Views in the activity_main.xml layout.
What this does not explain is where the variable layoutInflater is defined.
When using Android Studio, the code completion suggests that the variable "comes from getLayoutInflater()":
getLayoutInflater() seems to be a method in Activity, but this doesn't help me understand what the reference to layoutInflater is doing, where it is defined, and how it is in scope at this point of the code. Can someone help me to understand this please?
ActivityMainBinding.java is the generated class by data binding which has a static method inflate(). When you pass the layoutInflater(it retrieve a standard LayoutInflater instance that is already hooked up to the current context) to inflate() it generates the same code under the code as we usually do while inflating the views and it fetches the layout name automatically.
So, the whole method is like
public static ActivityMainBinding inflate(#NonNull LayoutInflater inflater,
#Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.activity_main, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
I hope this is what you are looking and sure can help you. Thanks
I came at the same question and found layoutInflater was declared in NavigationMenuPresenter.java.
package com.google.android.material.internal;
...
public class NavigationMenuPresenter implements MenuPresenter {
...
LayoutInflater layoutInflater;
...
public void initForMenu(#NonNull Context context, #NonNull MenuBuilder menu) {
layoutInflater = LayoutInflater.from(context);
...
In my case, full path to the java script was
%HOMEDRIVE%%HOMEPATH%\.gradle\caches\modules-2\files-2.1\com.google.android.material\material\1.7.0\289bbb3a7fea52532f1163487f9469217ee608a9\material-1.7.0-sources.jar!\com\google\android\material\internal\NavigationMenuPresenter.java
After a little digging I found out that the getLayoutInflater comes from the Activity class. Activity class is parent of androidx.core.app.ComponentActivity that is parent of ComponentActivity that is parent of FragmentActivity that is parent of AppCompatActivity that is parent of your Activity class.
My project structure has a BaseActivity which is extended by multiple child activities, so the structure is like
BaseActivity
^
|
-------------------------------
| | |
ChildActivityA ChildActivityB ChildActivityC
I am using DataBinding with LiveData, and hence every time I need to set up the lifecycleOwner for the respective binding class i.e (in ChildActivityA)
val binding = DataBindingUtil.setContentView(R.layout.somelayout)
binding.lifecycleOwner = this#ChildActivityA
Now I need to repeat this boilerplate in each Activity, so instead I created a helper extension function to replace the above two lines i.e
fun <T : ViewDataBinding> BaseActivity.setDataBindingView(layoutId: Int): T {
val binding = DataBindingUtil.setContentView(this, layoutId)
binding.lifecycleOwner = this#BaseActivity
}
and then call in my ChildActivityA as
val binding = setDataBindingView(R.layout.someLayout)
As you can see the binding.lifecycleOwner is set to BaseActivity instead of the actual ChildActivityA, now will this cause any trouble? Will the binding.lifecycleOwner be still following the lifecycle of ChildActivityA?
this#ChildActivityA and this#BaseActivity refer to the same actual object in memory - there's only one activity object that exists. Therefore they're entirely equivalent.
I am trying to implement ViewModel, LiveData within a Fragment. After going through the Medium Blog, I tried to use the method getViewLifecycleOwner() while declaring the ViewModel Object.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
viewModel = new ViewModelProvider(getViewLifecycleOwner()).get(MainActivityViewModel.class);
}
But it shows up the following error,
androidx.lifecycle.ViewModelStoreOwner in ViewModelProvider cannot be applied to androidx.lifecycle.LifeCycleOwner
When I try to Cast this to ViewModelStoreOwner, app crashes by showing the below error,
java.lang.ClassCastException: androidx.fragment.app.FragmentViewLifecycleOwner cannot be cast to androidx.lifecycle.ViewModelStoreOwner
How to declare the lifecycle of the Fragment with the ViewModel efficiently?
Use this to scope your viewmodel to your fragment:
viewModel = new ViewModelProvider(this).get(MainActivityViewModel.class);
Or this if you want it scoped to your activity:
viewModel = new ViewModelProvider(requireActivity()).get(MainActivityViewModel.class);
Fragment is a subclass of LifecycleOwner. So you can directly pass Fragment reference to the LifecycleOwner parameter.
Code will be like this.
viewModel = new ViewModelProvider(this).get(MainActivityViewModel.class);
I have a fragment:
class MyFragment : BaseFragment() {
// my StudentsViewModel instance
lateinit var viewModel: StudentsViewModel
override fun onCreateView(...){
...
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(this).get(StudentsViewModel::class.java)
updateStudentList()
}
fun updateStudentList() {
// Compiler error on 'this': Use viewLifecycleOwner as the LifecycleOwner
viewModel.students.observe(this, Observer {
//TODO: populate recycler view
})
}
}
In my fragment, I have a instance of StudentsViewModel which is initiated in onViewCreated(...).
In, StudentsViewModel, students is a LiveData:
class StudentsViewModel : ViewModel() {
val students = liveData(Dispatchers.IO) {
...
}
}
Back to MyFragment, in function updateStudentList() I get compiler error complaining the this parameter I passed in to .observe(this, Observer{...}) that Use viewLifecycleOwner as the LifecycleOwner
Why I get this error? How to get rid of it?
Why I get this error?
Lint is recommending that you use the lifecycle of the fragment's views (viewLifecycleOwner) rather than the lifecycle of the fragment itself (this). Ian Lake and Jeremy Woods of Google go over the difference as part of this Android Developer Summit presentation, and Ibrahim Yilmaz covers the differences in this Medium post In a nutshell:
viewLifecycleOwner is tied to when the fragment has (and loses) its UI (onCreateView(), onDestroyView())
this is tied to the fragment's overall lifecycle (onCreate(), onDestroy()), which may be substantially longer
How to get rid of it?
Replace:
viewModel.students.observe(this, Observer {
//TODO: populate recycler view
})
with:
viewModel.students.observe(viewLifecycleOwner, Observer {
//TODO: populate recycler view
})
In your current code, if onDestroyView() is called, but onDestroy() is not, you will continue observing the LiveData, perhaps crashing when you try populating a non-existent RecyclerView. By using viewLifecycleOwner, you avoid that risk.
viewLifeCycleOwner is LifecycleOwner that represents the Fragment's View lifecycle. In most cases, this mirrors the lifecycle of the Fragment itself, but in cases of detached Fragments, the lifecycle of the Fragment can be considerably longer than the lifecycle of the View itself.
Fragment views get destroyed when a user navigates away from a fragment, even though the fragment itself is not destroyed. This essentially creates two lifecycles, the lifecycle of the fragment, and the lifecycle of the fragment's view. Referring to the fragment's lifecycle instead of the fragment view's lifecycle can cause subtle bugs when updating the fragment's view.
Instead of this use viewLifecycleOwner to observe LiveData
viewModel.students.observe(viewLifecycleOwner, Observer {
//TODO: populate recycler view
})
Captain obvious here, also useful could be this:
viewModel.searchConfiguration.observe(requireParentFragment().viewLifecycleOwner, Observer {}
I have little problem with pass Recyclerview item ID from Activity to ViewModel. I need this ID to edit objects.
Does anyone know how to do it in accordance with the MVVM architecture?
let's try this code, you can pass context object in constructor of ViewModel class and you can also pass binders object.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myAddressActivityBinding= DataBindingUtil.setContentView(this#MyAddressActivity, R.layout.my_address_activity)
mMyAddressViewModel=MyAddressViewModel(this#MyAddressActivity)
myAddressActivityBinding!!.viewModel=mMyAddressViewModel
}
}
here you can find variable or id something like this, this may be your ViewMoidel class in which you are getting context object.
class MyAddressViewModel(val mMyAddressActivity: MyAddressActivity) : BaseObservable(), DeleteAdressCallback {
private val tilEmail = mMyAddressActivity.myAddressActivityBinding!!.tilEmail
}
and possible you have bound your object in XML too by using data