How to use data-binding with Fragment
AndroidAndroid FragmentsAndroid DatabindingAndroid Problem Overview
I'm trying to follow data-binding example from official google doc https://developer.android.com/tools/data-binding/guide.html
except that I'm trying to apply data-biding to a fragment, not an activity.
the error I'm currently getting when compiling is
Error:(37, 27) No resource type specified (at 'text' with value '@{marsdata.martianSols}.
onCreate
for fragment looks like this:
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MartianDataBinding binding = MartianDataBinding.inflate(getActivity().getLayoutInflater());
binding.setMarsdata(this);
}
onCreateView
for fragment looks like this:
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.martian_data, container, false);
}
and parts of my layout file for fragment looks like this:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="marsdata"
type="uk.co.darkruby.app.myapp.MarsDataProvider" />
</data>
...
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@{marsdata.martianSols}"
/>
</RelativeLayout>
</layout>
my suspicion is that MartianDataBinding
doesn't know which layout file it's supposed to be bound with - hence the error. Any suggestions?
Android Solutions
Solution 1 - Android
The data binding implementation must be in the onCreateView
method of the fragment, delete any data Binding that exist in your OnCreate
method,
your onCreateView
should look like this:
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
MartianDataBinding binding = DataBindingUtil.inflate(
inflater, R.layout.martian_data, container, false);
View view = binding.getRoot();
//here data must be an instance of the class MarsDataProvider
binding.setMarsdata(data);
return view;
}
Solution 2 - Android
You are actually encouraged to use the inflate
method of your generated Binding and not the DataBindingUtil:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
MainFragmentBinding binding = MainFragmentBinding.inflate(inflater, container, false);
//set variables in Binding
return binding.getRoot();
}
Docs for DataBindingUtil.inflate():
> Use this version only if layoutId is unknown in advance. Otherwise, use the generated Binding's inflate method to ensure type-safe inflation.
Solution 3 - Android
Even the other answers may work well, but I want tell best approach.
Binding class's inflate
as recommended in Android Documentation.
Use One option is to inflate by DataBindingUtil
but when only you don't know have generated binding class.
--You have auto generated binding class
, use that class instead of using DataBindingUtil
.
In Java
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
HomeFragmentBinding binding = HomeFragmentBinding.inflate(inflater, container, false);
//set binding variables here
return binding.getRoot();
}
In Kotlin
lateinit var binding: HomeFragmentBinding
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = HomeFragmentBinding.inflate(inflater, container, false)
return binding.root
}
In DataBindingUtil class documentation you can see.
> ### inflate > > T inflate (LayoutInflater inflater, > int layoutId, > ViewGroup parent, > boolean attachToParent) > > Use this version only if layoutId is unknown in advance. Otherwise, use the generated Binding's inflate method to ensure type-safe > inflation.
If your layout biniding class is not generated @See this answer.
Solution 4 - Android
If you are using ViewModel and LiveData This is the sufficient syntax
Kotlin Syntax:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return MartianDataBinding.inflate(
inflater,
container,
false
).apply {
lifecycleOwner = viewLifecycleOwner
vm = viewModel // Attach your view model here
}.root
}
Solution 5 - Android
Just as most have said, but dont forget to set LifeCycleOwner
Sample in Java
i.e
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
BindingClass binding = DataBindingUtil.inflate(inflater, R.layout.fragment_layout, container, false);
ModelClass model = ViewModelProviders.of(getActivity()).get(ViewModelClass.class);
binding.setLifecycleOwner(getActivity());
binding.setViewmodelclass(model);
//Your codes here
return binding.getRoot();
}
Solution 6 - Android
Try this in Android DataBinding
FragmentMainBinding binding;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false);
View rootView = binding.getRoot();
initInstances(savedInstanceState);
return rootView;
}
Solution 7 - Android
Kotlin syntax:
lateinit var binding: MartianDataBinding
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.martian_data, container, false)
return binding.root
}
Solution 8 - Android
One can simply retrieve view object as mentioned below
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = DataBindingUtil.inflate(inflater, R.layout.layout_file, container, false).getRoot();
return view;
}
Solution 9 - Android
A complete example in data binding Fragments
FragmentMyProgramsBinding is binding class generated for res/layout/fragment_my_programs
public class MyPrograms extends Fragment {
FragmentMyProgramsBinding fragmentMyProgramsBinding;
public MyPrograms() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
FragmentMyProgramsBinding fragmentMyProgramsBinding = DataBindingUtil.inflate(inflater, R
.layout.fragment_my_programs, container, false);
return fragmentMyProgramsBinding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
}
Solution 10 - Android
working in my code.
private FragmentSampleBinding dataBiding;
private SampleListAdapter mAdapter;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
dataBiding = DataBindingUtil.inflate(inflater, R.layout.fragment_sample, null, false);
return mView = dataBiding.getRoot();
}
Solution 11 - Android
I have been finding Answer for my application and here is the answer for Kotlin Language.
private lateinit var binding: FragmentForgetPasswordBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding=DataBindingUtil.inflate(inflater,R.layout.fragment_forget_password,container,false)
val viewModel=ViewModelProvider(this).get(ForgetPasswordViewModel::class.java)
binding.recoveryViewModel=viewModel
viewModel.forgetPasswordInterface=this
return binding.root
}
Solution 12 - Android
Another example in Kotlin:
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = DataBindingUtil
.inflate< MartianDataBinding >(
inflater,
R.layout.bla,
container,
false
)
binding.modelName = // ..
return binding.root
}
Note that the name "MartianDataBinding" depends on the name of the layout file. If the file is named "martian_data" then the correct name would be MartianDataBinding.
Solution 13 - Android
Very helpful blog about Databinding : https://link.medium.com/HQY2VizKO1
class FragmentBinding<out T : ViewDataBinding>(
@LayoutRes private val resId: Int
) : ReadOnlyProperty<Fragment, T> {
private var binding: T? = null
override operator fun getValue(
thisRef: Fragment,
property: KProperty<*>
): T = binding ?: createBinding(thisRef).also { binding = it }
private fun createBinding(
activity: Fragment
): T = DataBindingUtil.inflate(LayoutInflater.from(activity.context),resId,null,true)
}
Declare binding val like this in Fragment :
private val binding by FragmentBinding<FragmentLoginBinding>(R.layout.fragment_login)
Don't forget to write this in fragment
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return binding.root
}
Solution 14 - Android
This is how you can do it in kotlin:
//Pass the layout as parameter to the fragment constructor
class SecondFragment : Fragment(R.layout.fragment_second) {
private var _binding: FragmentSecondBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentSecondBinding.bind(view) //if the view is already inflated then we can just bind it to view binding.
}
//Note: Fragments outlive their views. Make sure you clean up any references to the binding class
// instance in the fragment's onDestroyView() method.
override fun onDestroyView() {
Toast.makeText(activity, "On destroy", Toast.LENGTH_SHORT).show()
super.onDestroyView()
_binding = null
}
}
You can access the view elements from your layouts like:
binding.tvName.text = "Messi"
where tvName is the id of the view element.
Solution 15 - Android
Everyone says about inflate()
, but what if we want to use it in onViewCreated()
?
You can use bind(view)
method of concrete binding class to get ViewDataBinding
instance for the view
.
Usually we write BaseFragment something like this (simplified):
// BaseFragment.kt
abstract fun layoutId(): Int
override fun onCreateView(inflater, container, savedInstanceState) =
inflater.inflate(layoutId(), container, false)
And use it in child fragment.
// ConcreteFragment.kt
override fun layoutId() = R.layout.fragment_concrete
override fun onViewCreated(view, savedInstanceState) {
val binding = FragmentConcreteBinding.bind(view)
// or
val binding = DataBindingUtil.bind<FragmentConcreteBinding>(view)
}
If all Fragments uses data binding, you can even make it simpler using type parameter.
abstract class BaseFragment<B: ViewDataBinding> : Fragment() {
abstract fun onViewCreated(binding: B, savedInstanceState: Bundle?)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
onViewCreated(DataBindingUtil.bind<B>(view)!!, savedInstanceState)
}
}
I don't know it's okay to assert non-null there, but.. you get the idea. If you want it to be nullable, you can do it.
Solution 16 - Android
Kotlin
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View?
{
val binding = FragmentFirstBinding.inflate(inflater,container,false)
return binding.root;
}
where FragmentFirstBinding is Automatic generated by android studio using view binding. In my code fragment name is FirstFragment.
First, you need to add this line to your build.Gradle(app module) file.
buildFeatures{
viewBinding true
}
Solution 17 - Android
Shortest way in Kotlin;
class HomeFragment : Fragment(R.layout.fragment_home) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentHomeBinding.bind(view)
// todo
}