I have 2 layouts: one for v19+ and another for earlier versions. They contain different views with different ids. How can I say Android DataBinding framework that I want to work with both layouts? It generates views only for one layout (selects randomly).
layout/temp_view.xml :
<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="block"
type="ru.temp.model.content.blocks.WebMediaBlock" />
<import type="ru.temp.model.Types.ProviderTypes" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:background="#android:color/white">
<ImageView
android:id="#+id/provider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingTop="#dimen/size5"
android:src="#{ProviderTypes.fromString(block.provider).getResId()}" />
</FrameLayout>
</layout>
layout-v19/temp_view.xml :
<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="block"
type="ru.temp.model.content.blocks.WebMediaBlock" />
<import type="ru.temp.model.Types.ProviderTypes" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:background="#android:color/white">
<ru.temp.utils.EmbedView
android:id="#+id/media_embed"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ru.temp.structure.static_material.CreditsView
android:id="#+id/credits_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
</layout>
Update:
Finally I've found out the root of the problem. I don't know why but it does not generate *BindingImpl files when use minSdkVersion 21. If specify earlier versions it works as said #yigit
Data binding will generate a base class that serves as a common interface for both (the variables). Data binding will take care of creating the right instance when you call DataBindingUtil.setContentView(activity, R.layout.tmp_view).
For instance, in your example, it will generate
TempViewBinding, TempViewBindingImpl and TempViewBindingV19Impl.
You can check these classes inside <app module>/build/intermediates/classes/<your package>/databinding/ after you compile.
TempViewBinding is the base class and it will have the combination of variables and views for each layout.
If you are not seeing them in the IDE, might be an auto-completion bug in AS (please file a bug).
Related
I've been searching this a lot today and only found some unclear answers regarding this question.
I'm having multiple TextView and EditText in a form, all of them being customised by my theme and i'd like to reuse the same included layout each time, but having parameters to it like the text inside the TextView or hint inside the EditText.
My form.xml right now:
<LinearLayout android: orientation="horizontal">
<TextView android:text="Username"/>
<EditText android:id="edittext_username"
android:hint="Enter username..."
android:inputType="text"/>
</LinearLayout>
<LinearLayout android: orientation="horizontal">
<TextView android:text="Password"/>
<EditText android:id="edittext_password"
android:hint="Enter password..."
android:inputType="password"/>
</LinearLayout>
... other fields ...
What i'd like in the main form.xml:
<LinearLayout android:orientation="vertical" ...>
<include layout="form_edittext"
app:textview_text="#{`Username`}"
app:edittext_hint="#{`Enter username...`}"
... other parameters ...
/>
<include layout="form_edittext"
app:textview_text="#{`Password`}"
app:edittext_hint="#{`Enter password...`}"
... other parameters ...
/>
</LinearLayout>
also the form_edittext.xml:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="textview_text" type="java.lang.String"/>
<variable name="edittext_id" type="java.lang.String"/>
<variable name="edittext_inputType" type="java.lang.String"/>
<variable name="edittext_hint" type="java.lang.String"/>
</data>
<TextView android:text="#{textview_text}"/>
<EditText android:id="#{edittext_id}"
android:hint="#{edittext_hint}"
android:inputType="#{edittext_inputType}"/>
</layout>
I'm still a beginner and i do not know if this is possible.
On this post, the guy replied he used data binding (as in example shown by me)
How to Re-using Layouts with <include/> with parameters? (you may see the first answer).
However, using this method give me the error of XML not recognizing identifiers like "textview_text" (the variables that i'm accessing via #{...}. If you got other solutions i'd appreciate if you share them. Cheers!
UPDATE on iCantC's answer: (but the textview's text and the hint remain empty).
Main layout:
<?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">
...
<include layout="#layout/activity_login_layout_input"
android:id="#+id/activity_login_layout_input_emailAddress"
app:textviewt="#{#string/email}" //the string is "Email Address"
app:edittexth="#{#string/emailhint}" /> //the string is "Your email address..."
...
</layout>
Included layout:
<layout>
<data>
<variable
name="textviewt"
type="String"/>
<variable
name="edittexth"
type="String"/>
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:showDividers="middle">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{textviewt}"
android:textColor="#color/c1"
android:textSize="12sp"/>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="26dp">
<androidx.appcompat.widget.AppCompatEditText
android:id="#+id/edittext"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_gravity="center|start"
android:layout_marginStart="26dp"
android:background="#drawable/activity_login_background"
android:hint="#{edittexth}"
android:padding="6dp"
android:textAlignment="center"
android:textColor="#color/c2"
android:textColorHint="#color/grey"
android:textSize="14sp"/>
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="start"
android:adjustViewBounds="true"
android:src="#drawable/activity_login_edittext_drawable" />
</FrameLayout>
</LinearLayout>
</layout>
It seems like you want to include a common layout and then pass dynamic parameters from the main layout to included layout, here are the steps,
Step 1: Enable DataBinding in your project
//In the build.gradle file in the app module, add this
android {
...
dataBinding {
enabled = true
}
}
Step 2: Create your common_layout_included.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="username"
type="String"/>
</data>
<LinearLayout
..
>
<TextView
android:id="#+id/tv_username"
android:text="#{username}"/>
</LinearLayout>
</layout>
Step 3: Include common_layout_included.xml in your main_layout.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">
<LinearLayout
..
>
<include
android:id="#+id/includedLayout"
layout="#layout/common_layout_included"
app:username="#{#string/username}" // here we pass any String
/>
</LinearLayout>
</layout>
Step 4: Make sure you inflate the layout using DataBinding way
//In the onCreate of your Activity
val binding = DataBindingUtil.setContentView(this,R.layout.main_layout)
That's it, you are good to go. If still some error appears just do File -> Invalidate Caches/Restart
One last thing, I saw that you are assigning the id's to the view's dynamically android:id="#{edittext_id}", in all of my experience, I never really encountered a use case where I would be motivated to do this. I don't even know if it's possible and even if possible I doubt it's a good practice.
I'm trying to use a view with databinding using the include tag multiple times.
It works great when the app runs, but in preview I don't manage to make it work in the parent.
Instead, I can do it on the view itself, but it implies that if I use it 5 times in the same layout, I'll have 5 exact copies. It's not a blocking issue, but for debugging/ UI, it makes things a bit more complicated.
Here's the code of the replicated view:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View"/>
<import type="android.text.TextUtils"/>
<variable
name="title"
type="String" />
<variable
name="value"
type="String" />
</data>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/white"
android:text="#{(!TextUtils.isEmpty(title) ? title : `Title`).concat(`:`), default=`Title:`}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#{!TextUtils.isEmpty(value) ? #color/darkBlue : #color/red, default=#color/red }"
android:text="#{(!TextUtils.isEmpty(value) ? value : `Unknown`), default=`Unknown`}" />
</LinearLayout>
</layout>
And here's how it's included:
<include layout="#layout/titled_entry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
bind:title="#{`Firstname`}"
bind:value="#{user.firstName}" />
In Android it looks like this
But in preview it looks like this
Does anyone know how to fix it ?
Thanks, and have a good day in those complicated times.
Store your string in string.xml
And set that as default value
Like below
android:text='#{viewModel.firstName, default=#string/firstName}`
I want to update data items in the RecyclerView after API call. I have used ObservableArrayList. After api call, I have used notifyChange() in setter method. When I am passing the ObservableArrayList to the XML then I am getting error like this:
msg:Cannot find the setter for attribute 'bind:categories_list' with parameter type android.databinding.ObservableArrayList<T> on android.support.v7.widget.RecyclerView
XML is:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="list"
type="ObservableArrayList" />
<import type="android.databinding.ObservableArrayList" />
</data>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="#color/white">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
bind:categories_list="#{list}" />
</android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>
</layout>
You need to use the #BindingAdapter("categories_list") annotation where you are handling the list. Check this answer for more details
EDIT
Not sure if you followed the right approach. The syntax looks weird. Usually here:
<variable
name="list"
type="ObservableArrayList" />
goes a model class of your own, and importing it and using it with both declarations also look weird to me, did you try:
<variable
name="list"
type="android.databinding.ObservableArrayList" />
?
Context
XML layouts in Android can get complicated. Hence, it is a good practice to break them down into conceptually independent modules. Consider the following example:
Main layout:
<layout>
<data>
<variable name="someVar" type="some.custom.Type"/>
</data>
<SomeLayout
...
android:someAttribute="#{someVar.someProperty}" />
<include layout="#layout/some_other_layout />
</layout>
and some_other_layout.xml:
<SomeOtherLayout
...
android:someOtherAttribute="#{someVar.someOtherProperty}" />
Problem
Is it possible to use the same data-binding context (whatever is inside <data>) in two separated layouts (like in the given example)?
Doing this naively results in java.lang.IllegalStateException.
From the Data Binding Library documentation:
Variables may be passed into an included layout's binding from the containing layout by using the application namespace and the variable name in an attribute:
<?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>
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.