Reusing Fragments/Bottomsheets with viewModel as data binding variable - android

I will give an example so as to easily explain my problem.
Consider FragmentA with ViewModelA and FragmentB with ViewModelB being used in my app.
I have a BottomSheetSample that is to be used in both FragmentA and FragmentB Since the bottom sheet is doing very little, like selecting a value I want to share the ViewModel of the fragment on which the bottom sheet is being displayed on, instead of having a separate ViewModel and transferring the data to the ViewModel of the fragment. I am binding the ViewModel like this to the nav graph.
Inside FragmentA
private val viewModel: ViewModelA by navGraphViewModels(R.id.nav_A_graph) { viewModelFactory }
Inside BottomSheetSample
private val viewModel: ViewModelA by navGraphViewModels(R.id.nav_A_graph) { viewModelFactory }
I am passing the ViewModel to the XML like this so as to use it with data-binding:
val binding: BottomSheetSampleBinding =
DataBindingUtil.inflate(inflater, R.layout.bottomSheet, container, false)
binding.viewModel = viewModel
sample bottom sheet XML:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.ui.fragmenta.ViewModelA" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{()->viewModel.oneSelected()} />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{()->viewModel.twoSelected()} />
</LinearLayout>
</layout>
Now the issue is that I have to use the same Bottom Sheet(BottomSheetSample) in FragmentB also. Since I am passing the ViewModel to the XML for data binding, I am not able to use the same bottom sheet as the current BottomSheetSampleBinding is expecting an object of ViewModelA.
So what I end up doing is creating a new BottomSheetSampleB which does exactly the same thing as BottomSheetSample and just change the ViewModel in the new XML to ViewModelB :
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.ui.fragmentb.ViewModelB" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{()->viewModel.oneSelected()} />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{()->viewModel.twoSelected()} />
</LinearLayout>
</layout>
Now if I have to use the same BottomSheet in a third fragment I have to copy the BottomSheetSample and do the whole thing again. This looks redundant as the same bottom sheet is being replicated again and again. But I don't know how to avoid doing this. Can someone tell me the right way to do this?
Hopefully, I am clear about my issue.

Create a BaseViewModel (Any name) and keep all the things required for bottom sheet in BaseViewModel . Use this BaseViewModel in Bottom Sheet
<layout>
<data>
<variable
name="viewModel"
type="com.ui.fragmentb.BaseViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{()->viewModel.oneSelected()}" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{()->viewModel.twoSelected()}" />
</LinearLayout>
</layout>
Extend your ViewModelA,ViewModelB with the BaseViewModel

Related

how to transfer onClick code from XML to Kotlin for recyclerview with data binding

Below code is the XML for the recyclerview item.
How to transfer android:onClick="#{() -> sumListener.onClick(sum)}"(last line) from XML to kotlin?
I would like to make a onLongClick, but XML has no onLongClick.
Please let me know if need more info.
<data>
<variable
name="sum"
type="com.cementcaibird.astock.database.Sum" />
<variable
name="sumListener"
type="com.cementcaibird.astock.main.SumListener" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/ll_item_container"
android:onClick="#{() -> sumListener.onClick(sum)}">
...(other view code)
It can be achieved with this method
<data>
<variable
name="sum"
type="com.cementcaibird.astock.database.Sum" />
<variable
name="sumListener"
type="com.cementcaibird.astock.main.SumListener" />
</data>
<LinearLayout
android:id="#+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/ll_item_container">
...(other view code)
In your Activity/Fragment
private lateinit var binding: YourBindingclass
..
binding.linearLayout.setOnClickListener { view->
// Handle your click here
}
binding.linearLayout.setOnLongClickListener{ view->
// Handle your long click here
true
}

How to use binding in both a fragment and within its included layout?

Say I have a fragment defined as:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="cpaTabViewModel"
type="org.romco.example.CpaTabViewModel" />
</data>
<LinearLayout
android:id="#+id/fragment_pac_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/testTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{cpaTabViewModel.testString}"
tools:text="test" />
<include
android:id="#+id/addPerformedPAField"
layout="#layout/add_performed_pa"
app:cpaTabViewModel="#{cpaTabViewModel}"/>
[...]
</LinearLayout>
</layout>
The included layout is add_performed_pa.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="cpaTabViewModel"
type="org.romco.example.CpaTabViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="24dp"
tools:showIn="#layout/cpa_tab_fragment">
<Spinner
android:id="#+id/inputActivitySpinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAlignment="textStart"
app:layout_constraintStart_toEndOf="#+id/cancelButton"
app:layout_constraintTop_toTopOf="#+id/cancelButton"
app:entries="#{cpaTabViewModel.activities}" <---- THIS CAUSES THE PROBLEM
tools:listitem="#layout/sample_textview" />
[...]
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
The class representing the fragment is CpaTabFragment.kt:
class CpaTabFragment : Fragment() {
private lateinit var cpaTabFragBinding: CpaTabFragmentBinding
private val cpaTabViewModel: CpaTabViewModel by lazy {
ViewModelProvider(this, CpaTabViewModel.Factory(activity?.application!!))
.get(CpaTabViewModel::class.java)
.apply {
setPeriodType(arguments?.getSerializable(ARG_SECTION_TYPE) as PlanPeriodType)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
cpaTabFragBinding = DataBindingUtil.inflate(inflater, R.layout.cpa_tab_fragment, container, false)
cpaTabFragBinding.testTextView.text = "TEST HEADER"
cpaTabFragBinding.addPerformedPAField.root.visibility = View.GONE
cpaTabFragBinding.cpaTabViewModel = cpaTabViewModel
// (this next line is replaced by passing the viewModel in the <include app:cpaTabViewModel="#{cpaTabViewModel} - I hope this is correct, but that's beside the point here)"
// cpaTabFragBinding.addPerformedPAField.cpaTabViewModel = cpaTabViewModel
return cpaTabFragBinding.root
}
Obviously, I removed most of the unimportant (or at least what I'm hoping is unimportant) code above, leaving only what I think is relevant here.
Without actually using data-binding in the add_performed_pa.xml layout (the line marked with "THIS CAUSES THE PROBLEM"), the entire thing works fine. I can access the included layout as a binding just fine, change it's root visibility, even access any elements in it, etc.
However, as soon as I try to add any data-binding into the included .xml, app won't compile, and the error is the following:
...\app\build\generated\source\kapt\debug\org\romco\example\DataBinderMapperImpl.java:18: error: cannot find symbol
import org.romco.example.databinding.AddPerformedPaBindingImpl;
I can't really find anyone else with this problem. Can you please help?

How inflate a Linear layout with custom view by databinding

I just want to make a map with some Vertical LinearLayout that in each of that have a Horizontal LinearLayout and in each of this layout wanna inflate a view that is like below code :
each_cell.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="live" type="Integer" />
<variable name="i" type="Integer" />
<variable name="j" type="Integer" />
</data>
<ImageView
android:id="#+id/itemView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
app:setI="#{i}"
app:setJ="#{j}"/>
each_row.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/row_instance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:orientation="horizontal">
</LinearLayout>
that I will add this Horizontal LL view to my main Vertical LL and the map will created
and kotlin code :
val cell = DataBindingUtil.inflate<EachCellBinding>(inflater , R.layout.each_cell , HorizontalLinearLayout.row_instance , true )
but the error is :
Required DataBindingComponent is null in class EachCellBindingImpl. A BindingAdapter in com.example.gameoflife.SartFragment.StartFragment is not static and requires an object to use, retrieved from the DataBindingComponent. If you don't use an inflation method taking a DataBindingComponent, use DataBindingUtil.setDefaultComponent or make all BindingAdapter methods static.
As shown in the error you have declared the binding adapter methods in com.example.gameoflife.SartFragment.StartFragment. Declaring it inside a class makes it non-static. But the binding adapter methods should be always static. So take the methods from inside the class and put it outside the class and it will become static and your error will be resolved.

How to initialise click listener on Recycler view adapter using Databinding?

I am going to make an universal adapter for all dynamic layouts , normally i handled all this things but i got stuck that how to initialise click listener using interface so that i define in whatever xml and get event in my class.
i am following this link:
https://developer.android.com/topic/libraries/data-binding/index.html
suppose this is my root xml of recycler view:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.firstName}"
android:onClick="#{handlers::onClickFriend}"
//>>> i want to initialise interface for position/view instead of #{handlers::onClickFriend}.
/>
</LinearLayout>
</layout>
Please give me link and solution , i will be thankful to you.
You can pass either user/position. If you want to pass position inside clickListener you must have to pass it as variable in xml same as user, and then
android:onClick="#{() -> handlers.onClickFriend(user)}
Or
<variable name="position" type="Integer"/>
and then
android:onClick="#{() -> handlers.onClickFriend(position)}

Android Data Binding: Why doesn't passing in variable to ViewStubs work like include layouts?

With Android data binding it is possible to set a variable on a an included layout like so (from the documentation):
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="#layout/name"
bind:user="#{user}"/>
<include layout="#layout/contact"
bind:user="#{user}"/>
</LinearLayout>
</layout>
I've tried doing the same thing to pass in variables when using a ViewStub, but it doesn't work. Why don't ViewStubs work like include layouts?
Passing data to ViewStubs is working as expected. You define your namespace and pass the variable in that namespace, and accept it as a regular <variable> in your ViewStub layout, as follows:
main_layout.xml:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:my-namespace="http://schemas.android.com/apk/res-auto">
<data>
<variable name="myData" type="com.example.SomeModel" />
</data>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ViewStub
android:id="#+id/view_stub"
android:inflatedId="#+id/view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="#layout/another_layout"
my-namespace:data="#{myData}"
/>
</RelativeLayout>
</layout>
another_layout.xml:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!-- No need to declare my-namespace here -->
<data>
<variable name="data" type="com.example.SomeModel" />
</data>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{data.someValue}" />
</RelativeLayout>
</layout>
ViewStubs are called out as being different in the Data Binding Library documentation.
ViewStubs
ViewStubs are a little different from normal Views. They start off invisible and when they either are made visible or are explicitly told to inflate, they replace themselves in the layout by inflating another layout.
Because the ViewStub essentially disappears from the View hierarchy, the View in the binding object must also disappear to allow collection. Because the Views are final, a ViewStubProxy object takes the place of the ViewStub, giving the developer access to the ViewStub when it exists and also access to the inflated View hierarchy when the ViewStub has been inflated.
When inflating another layout, a binding must be established for the new layout. Therefore, the ViewStubProxy must listen to the ViewStub's ViewStub.OnInflateListener and establish the binding at that time. Since only one can exist, the ViewStubProxy allows the developer to set an OnInflateListener on it that it will call after establishing the binding.

Categories

Resources