Data Binding with theme attributes - android

I am trying out the new Android Databinding Library and I wanted to set the background color of ToolBar using a binding. By default the color should be colorPrimary (from the theme).
Before I was using DataBinding, my toolBar looked like
<android.support.v7.widget.Toolbar
android:id="#+id/mainToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
/>
After adding a binding, I wanted to set its background to colorPrimary if no color is bound - I'm using ternary operator for this (as mentioned in the guide) - but it causes an error, as theme attributes also have a "?" operator before their names. The compiler thinks I'm starting a new ternary operation.
<data>
<variable name="toolBarBackgroundColor" type="int"/>
</data>
...
<android.support.v7.widget.Toolbar
android:id="#+id/mainToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#{toolBarBackgroundColor!=0? toolBarBackgroundColor: ?attr/colorPrimary }"
/>
So is there a way I can access theme attributes inside a binding operation? Thanks!
Edit
I know I can get the colorPrimary attribute programmatically and bind it through java code. But I'm just wondering if there's an Xml-based solution for this or not.

The answer is a bit late, but maybe it helps someone.
For accessing theme attributes in data binding, you can use this:
(imagine that clickable is Boolean variable)
android:background="#{clickable ? android.R.attr.selectableItemBackground : android.R.color.transparent}"
No additional binding adapters or another things needed.

Finding a way using data-binding? Here is what I have done with test. First, create a custom binding adapter method:
#BindingAdapter({"app:customPrimaryBackground"})
public static void setCustomPrimaryBackground(View v, int resId) {
TypedValue typedValue = new TypedValue();
Context context = v.getContext();
if (resId == 0) {
context.getTheme().resolveAttribute(R.attr.colorPrimary, typedValue, true);
v.setBackgroundResource(typedValue.resourceId);
} else {
// Check the given resource ID is a color or drawable
context.getResources().getValue(resId, typedValue, true);
Drawable drawable;
if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) {
// It's a color
drawable = new ColorDrawable(typedValue.data);
} else {
drawable = ContextCompat.getDrawable(context, resId);
}
v.setBackground(drawable);
}
}
Second, your binding xml layout:
<data>
<variable name="toolBarBackgroundColor" type="int"/>
</data>
...
<android.support.v7.widget.Toolbar
android:id="#+id/mainToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:customPrimaryBackground="#{toolBarBackgroundColor}"
/>

If the question is still actual for someone, this is the example how to use custom and android attributes
app:textColorAttr="#{error ? com.example.R.attr.textColorError : android.R.attr.textColor}", where textColorAttr implemented using BindingAdapter, error is Boolean and com.example is your package name

I found another solution without custom attributes and binding adapters. The idea is to place an invisible View with android:background and android:foreground attributes in XML markup and use these attributes in a binding expression.
<data>
<variable name="toolBarBackgroundColor" type="int"/>
</data>
...
<android.support.v7.widget.Toolbar
android:id="#+id/mainToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#{toolBarBackgroundColor != 0 ? helperView.getBackground() : helperView.getForeground() }"
/>
<View
android:id="#+id/helperView"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#{toolBarBackgroundColor}"
android:foreground="?attr/colorPrimary"
android:visibility="gone"
/>

Related

How to set app: backgroundTint using Android Databinding LiveData

val inputUnderLineColor = MutableLiveData(R.color.red2)
app:backgroundTint="#{viewModel.inputUnderLineColor}"
I want to set the EditText UnderLine color value depending on the state, but I get the following error
Cannot find a setter for <android.widget.EditText app:backgroundTint> that accepts parameter type 'androidx.lifecycle.MutableLiveData<java.lang.Integer>'
How do you solve this ??
You can use it like below ContextCompat.getColor()
<layout>
<data>
<import type="android.support.v4.content.ContextCompat"/>
<variable name="viewModel" type="com.example.myapp.yourObject" />
</data>
...
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{data.text}"
android:textColor="#{ContextCompat.getColor(context, viewModel.inputUnderLineColor)}" />
</layout>
1. Make sure to import ContextCompat as shown above.
2. You can automagically 'context' as a method parameter for ContextCompat.getColor() because it will be automatically resolved to the view's context.

Identifiers must have user defined types from the XML file, databinding with observablefield

I want my view visibility to be dependent on condition behaviour so I am using ObservableField and with databinding trying to change view visibility but getting issue like "Identifiers must have user defined types from the XML file. InputType is missing it"
Code:
Kotlin File
var showView: ObservableField<Boolean>? = ObservableField(false)
//API call response
showView.set(true)
Layout File:
<TextView
android:id="#+id/textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="#{viewModel.showView ? View.VISIBLE : View.GONE}"/>
How to apply databinding with Observablefield of type boolean? I have used it for string text also and it's working but not working with boolean conditional statement.
I am not sure if that's the case here, but this error message is usually displayed when you reference a type in your binding expressions that hasn't been declared in the <data> section of your layout. The same way you declare the View type as an import, you should declare the type InputType.
<data>
<!-- Maybe an import for InputType is missing here? -->
<import type="android.view.View" />
<variable
name="viewModel"
type="com.yourpackage.YourViewModel"/>
</data>

set android:textAppearance with DataBinding

I am trying to set android:textAppearance with use of DataBinding , but it is not allowing me to use ?android:attr/textAppearanceLarge with ternary operator.
android:textAppearance="#{position==1 ? ?android:attr/textAppearanceLarge : ?android:attr/textAppearanceMedium}"
It is showing me compile time error <expr> expected, got '?'.
Does there any other way to use this with DataBinding?
You cannot use it directly, however here's trick I use in such cases. Create own styles using textAppearanceLarge and textAppearanceMedium as parents and then set these styles instead:
First create Foo style:
<style name="Foo" parent="TextAppearance.AppCompat.Large">
... [whatever you need to set or override ] ...
</style>
and do the same for for FooMedium. Then edit your layout file as shown below. Note you must import project's R class in <data> block first:
<data>
<import type="<your-package-id>.R"
</data>
Finally apply the appearance as you formerly wanted:
android:textAppearance="#{ position==1 ? R.style.Foo : R.style.FooMedium }"
You can use android.R.attr package instead of the ?android:attr
<?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.R.attr"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hello world"
android:textAppearance='#{age==1 ? android.R.attr.textAppearanceLarge : android.R.attr.textAppearanceMedium}'
tools:textAppearance="?android:textAppearanceLarge"
/>
</LinearLayout>
</layout>
Looks like DataBinding treats android:textAppearance in some special way (at least on Android Studio 3.2.1).
For example the following expression according to this should be fine, but it is not.
The following expression is being accepted by the compiler but it is doing nothing:
android:textAppearance="#{R.style.MyStyleTest}"
I have tried several options and only vanilla way worked for me:
android:textAppearance="#style/MyStyleTest"
As a Solution
I would suggest to use #BindingAdapter and perform all the logic there.
For example if you would like to work with references to attributes (which looks like: ?attr/...) use the following approach:
layout.xml
<TextView
...
bind:textAppearanceAttr="#{position==1 ? android.R.attr.textAppearanceLarge: android.R.attr.textAppearanceMedium}"
/>
sources.kt
#BindingAdapter(value = ["textAppearanceAttr"])
fun textAppearanceAttr(textView: TextView, #AttrRes attrRef: Int?) {
attrRef?.also {
val attrs = textView.context.obtainStyledAttributes(intArrayOf(attrRef))
val styleRes = attrs.getResourceId(0, -1)
attrs.recycle()
if (styleRes != -1) {
TextViewCompat.setTextAppearance(textView, styleRes)
}
}
}
or in case of style resource ids (#style/...):
layout.xml
bind:textAppearanceStyle="#{position==1 ? R.style.MyStyleTest1: R.style.MyStyleTest2}"
sources.kt
#BindingAdapter(value = ["textAppearanceStyle"])
fun textAppearanceStyle(textView: TextView, #StyleRes style: Int?) {
style?.also { TextViewCompat.setTextAppearance(textView, it) }
}
You should use textStyle instead of textAppearance,
Try to use code give below:
android:textStyle="#{position==1 ? #style/TextAppearance.Material.Large :#style/TextAppearance.Material.Medium}"
For more reference use below image

Android dataBinding - #BindingAdapter custom app namespace being ignored

I have created a custom bindingAdapter in android and when i pass in the color i want the color to change, this is actually for a test im working on just to make sure it works. Here is the code:
here is my view Model for the data binding:
public class User {
public ObservableInt visible;
public User(int visible) {
this.visible=new ObservableInt(visible);
}
#BindingAdapter({"app:bindColor"}) //notice the bindColor custom attribute
public static void setTextColor(TextView view,String color) {
if("green".equals(color))
view.setTextColor(Color.parseColor("#63f421"));
}
}
Now in my xml file which is binded to this model im expected to pass in a color so that the setTextColor method can use it:
<?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">
<data class="MainActivityBinder">
<variable name="user" type="com.example.android.floatingactionbuttonbasic.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:id="#+id/tv_one"
android:text="my first textview"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tv_two"
android:text="my second textview"
android:visibility="#{user.visible}"
app:bindColor="#{'green'}" //see im passing in the green string here
android:textColor="#android:color/holo_green_dark"/>
</LinearLayout>
</layout>
I am getting the error at runtime time of:
Error:(27, 65) error: package com.example.android.floatingactionbuttonbasic.databinding does not exist
Warning:Application namespace for attribute app:bindColor will be ignored.
Error:(24, 33) Identifiers must have user defined types from the XML file. een is missing it
Error:Execution failed for task ':Application:compileDebugJavaWithJavac'.
> java.lang.RuntimeException: Found data binding errors.
if i take out the bindingAdapter stuff it works perfectly with the other databinding stuff. Its just this custom binding thats not working. My project is titled floatingactionbuttonbasic btw.
I was able to get this to work. The issue was how i was passing in the string. It should have the `` around it.
app:bindColor="#{`green`}"
you can also do this :
app:bindColor='#{"green"}'
But what seems to not be allowed is this notation:
app:bindColor="#{'green'}"
i wrote a blog about it to help others if interested.

Android data binding not working with View 'android:tag' property

Trying to use the new Android Data Binding in my project, but I'm getting an error when trying to set the 'android:tag' property to some custom variable.
My menu_item.xml file:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="menuItem"
type="com.example.MenuItem" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:tag="#{menuItem}"
tools:ignore="UseCompoundDrawables">
<!--suppress AndroidUnknownAttribute -->
<ImageView
android:id="#+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:imageResource="#{menuItem.itemType.drawableId}" />
<TextView
android:id="#+id/displayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{menuItem.itemType.displayNameId}" />
</LinearLayout>
</layout>
My MenuItem class:
public class MenuItem {
public final ItemType itemType;
public MenuItem(ItemType itemType) {
this.itemType = itemType;
}
}
Part of the genetated MenyItemBinding.java:
public MenuItemBinding(View root) {
super(root, 0);
final Object[] bindings = mapBindings(root, 3, sIncludes, sViewsWithIds);
this.displayName = (android.widget.TextView) bindings[2];
this.displayName.setTag(null);
this.icon = (android.widget.ImageView) bindings[1];
this.icon.setTag(null);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(root.getResources().getString(com.myApp.R.string.#{menuItem}));
setRootTag(root);
invalidateAll();
}
And the error is in the generated class, when trying to set the Tag of the bound view.
Any ideas how to get around this? Preferably, not to use a custom LinearLayout to support this.
That is a bug. We haven't tried data binding tags, mostly because tags are special.
When targeting devices pre-ICS, Android data binding takes over the tag of the outermost element of the layout. This tag is used for mostly for binding lifecycle and is used by DataBindingUtil.findBinding() and DataBindingUtil.getBinding().
So, since data binding isn't working on tags, the only work-around is to not supply a tag to your LinearLayout or supply a fixed tag or resource string. If you are targeting ICS and above, it is valid to reassign the tag after binding the layout:
MenuItemBinding binding = MenuItemBinding.inflate(layoutInflater);
binding.getRoot().setTag(menuItem);
You can also create a BindingAdapter for a new attribute:
#BindingAdapter("specialTag")
public static void setSpecialTag(View view, Object value) {
view.setTag(value);
}
and then use it in your layout:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:specialTag="#{menuItem}"
tools:ignore="UseCompoundDrawables"/>
This will allow you to use findViewByTag() and all of the other things you expect.
However, this will NOT work if you target Honeycomb and earlier devices. There is no getting around that. You may be tempted to do something like this:
#BindingAdapter("specialTag")
public static void setSpecialTag(View view, Object value) {
view.setTag(R.id.app_tag, value);
}
You won't be able to use findViewByTag with that approach, but it will store whatever value you want when you use your view. But the reason we don't use ID'd tags with Honeycomb and earlier is that there is a memory leak when using ID'd tags, so don't do it.
I hope this helps. I'll file a bug internally to support data bound android:tags.

Categories

Resources