64

I would like to set background color or null on my view using DataBinding library but I get an exception trying to run it.

java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.Integer.intValue()' on a null object reference

This is how I do it:

android:background="@{article.sponsored ? @color/sponsored_article_background : null}"

I also tried setting conversion but it didn't work.

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

Eventually, I resolved it with workaround using @BindingAdapter but I would like to know how to do it properly.

Damian Petla
  • 8,963
  • 5
  • 44
  • 47

7 Answers7

107

Reason:

First thing to know is that DataBinding library already provides a convertColorToDrawable binding converter located in android.databinding.adapters.Converters.convertColorToDrawable(int).

Using android:background should "theoretically" work, because it has a corresponding setBackground(Drawable) method. The problem is that it sees that you try to pass a color as a first argument so it tried to launch this converter before applying it to setBackground(Drawable) method. If databinding decides to use a converter it will use it on both arguments, so also on null, right before applying a final result to a setter.
Because null cannot be castes to int (and you cannot invoke intValue() on it) it throws NullPointerException.

There is a mention about the fact that mixed argument types are not supported in official Data Binding Guide.

Here are two solutions for this problem. Although you can use any of these two solutions, the first one is much easier.

Solutions:

1. As drawable

If you define your color not as a color but as a drawable in your resources (it can be in our colors.xml file:

<drawable name="sponsored_article_background">#your_color</drawable>

or

<drawable name="sponsored_article_background">@color/sponsored_article_background</drawable>

then you should be able to use android:background like you originally wanted to but providing drawable instead of color:

android:background="@{article.sponsored ? @drawable/sponsored_article_background : null}"

Here arguments has compatible types: first is Drawable and second is null so it can also be cast to a Drawable.

2. As resource id

app:backgroundResource="@{article.sponsored ? R.color.sponsored_article_background : 0}"

but it will also require to add your R class import in data section:

<data>
    <import type="com.example.package.R" />
    <variable ... />
</data>

Passing 0 as a "null resource id" is safe because setBackgroundResource method of View checks whether resid is different than 0 and sets null as a background drawable otherwise. No unnecessary transparent drawable objects are created there.

public void setBackgroundResource(int resid) {
    if (resid != 0 && resid == mBackgroundResource) {
        return;
    }

    Drawable d= null;
    if (resid != 0) {
        d = mResources.getDrawable(resid);
    }
    setBackgroundDrawable(d);

    mBackgroundResource = resid;
}
Maciej Ciemięga
  • 10,125
  • 1
  • 41
  • 48
  • I am trying to use your second approach but I am not sure how to use "app:backgroundResource". I cannot find this attribute in app namespace. Can you provide some details? – marrock Mar 17 '16 at 14:25
  • While using DataBinding such attribute may not necessary exist, but it should work just fine. Please take a look at https://developer.android.com/tools/data-binding/guide.html#attribute_setters and its "Automatic Setters" section. – Maciej Ciemięga Mar 17 '16 at 20:17
  • 1
    why no one mentioned using @android:color/transparent. For me sounds like the same as not having a background – LeoFarage May 13 '16 at 21:41
  • 2
    I had this method on my model `public int getBackground(){ return read ? R.color.read_sms : R.color.unread_sms;}` and used `app:backgroundResource = "@{sms.getBackground}"` and worked perfectly. – Laranjeiro May 18 '16 at 09:57
  • 1
    @LeoFarage although it might appear the same, it technically isn't the same; using a color results in a ColorDrawable being created. Although that drawable will end up doing nothing, just like any other object, creating it takes time and uses up resources. Since it isn't actually doing anything, that is (a tiny bit) wasteful. Whether this will have a significant performance impact depends on the situation. – Lorne Laliberte Aug 02 '18 at 19:10
  • if anyone has problems with constrtaintLayout background then just update com.android.support.constraint version in your app build.gradle file to latest and try again . – Siamak Dec 25 '18 at 12:57
  • Great answer! Note that you should always provide a default value and it must not be a resource (using R) even if using them for the ternary operator values, rather it must be in the form `,default=@color/thedefault` if it is a resource that should be the default. – straya Mar 09 '21 at 06:28
  • Thanks for ``. Usage: `app:someColor="@{R.color.green}"`. – CoolMind Sep 29 '21 at 13:54
25

I think you have to try default color instead of null

like this

android:background="@{article.sponsored ? @color/sponsored_article_background : @color/your_default_color}"
pRaNaY
  • 24,642
  • 24
  • 96
  • 146
11

One approach you can use is to write a custom @BindingConversion to take care of this for you:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return color != 0 ? new ColorDrawable(color) : null;
}

With this, you can set any attribute that accepts a ColorDrawable to an integer color value (like 0 or @android:color/transparent) and have it automatically converted to the lightweight @null for you.

(Whereas the built-in convertColorToDrawable(int) convertor always creates a ColorDrawable object, even if the color is transparent.)

Note: in order for this method to be used in place of the built-in @BindingConversion, it must return a ColorDrawable and not a Drawable -- otherwise the built-in method will be seen as more specific/appropriate.


Another approach is to use a static method to convert from a color to a Drawable within your data binding expression, in order to make the value types match. For example, you could import the built-in Converters class:

<data>
    <import type="androidx.databinding.adapters.Converters"/>
</data>

...and write your expression like this:

android:background="@{article.sponsored ? Converters.convertColorToDrawable(@color/sponsored_article_background) : null}"

...although I would personally recommend putting this kind of conditional logic in your data binding adapter method instead, e.g. using a getArticleBackground() method that returns a Drawable or null. In general things are easier to debug and keep track of if you avoid putting decision logic within your layout files.

Lorne Laliberte
  • 6,261
  • 2
  • 35
  • 36
1

Try this:

@Bindable
private int color;

and in constructor

color = Color.parseColor("your color in String for examp.(#ffffff)")

in xml:

android:textColor = "@{data.color}"
C. Alen
  • 184
  • 1
  • 10
0

In this article you can find two good solutions, in my case though, only one of work because I wanted to change background tint in a Material Button, here's my Binding adapter:

First, create a Kotlin file and paste this adapter method:

package com.nyp.kartak.utilities

import android.content.res.ColorStateList
import androidx.databinding.BindingAdapter
import com.google.android.material.button.MaterialButton
import com.nyp.kartak.model.ReceiptUserPurchaseModel

@BindingAdapter("backgroundTintBinding")
fun backgroundTintBinding(button: MaterialButton, model: ReceiptUserPurchaseModel) {
    button.backgroundTintList = ColorStateList.valueOf(button.resources.getColor( model.color))
}

Second use it in your xml:

<data>
    <variable
        name="model"
        type="com.nyp.kartak.model.ReceiptUserPurchaseModel" />
</data>

// .....

    <com.google.android.material.button.MaterialButton
        android:id="@+id/payBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{model.getAction()}"
        app:backgroundTintBinding="@{model}" />
Amin Keshavarzian
  • 3,646
  • 1
  • 37
  • 38
0

It's really old post but I want to suggest one more solution.

  1. DECLARE CUSTOM STYLES/BACKGROUNDS IN DRAWABLE**

I have four similar styles so I'll paste just one of them.

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="@color/colorAccent">
    <item>
        <layer-list>
            <item>
                <shape android:shape="oval">
                    <stroke
                        android:width="1dp"
                        android:color="@color/colorAccent" />
                    <solid android:color="@color/colorPrimaryDark" />
                    <size
                        android:width="200dp"
                        android:height="200dp" />
                </shape>
            </item>
            <item
                android:width="100dp"
                android:height="100dp"
                android:gravity="center"
                android:drawable="@drawable/ic_car_white" />
        </layer-list>
    </item>
</ripple>

When I'll set this style my button look like below:

enter image description here

  1. Prepare bindable value in your model/handler class

In my case I have below code in ActivityMainEventHandler class

@Bindable
public Drawable getConenctButtonStyle() {
    // here i'm checking connection state but you can do own conditions
    ConnectionState state = Communication.getInstance().getConnectionState();
    if (state != null) switch (state) {
        case CONNECTED:
            return ctx.getDrawable(R.drawable.circle_btn_state_green);
        case CONNECTING:
        case DISCONNECTING:
            return ctx.getDrawable(R.drawable.circle_btn_state_orange);
        case DISCONNECTED:
            return ctx.getDrawable(R.drawable.circle_btn_state_red);
    }
    return ctx.getDrawable(R.drawable.circle_btn_state_first);
}
  1. Pass your class to the your view

Activity onCreate:

 bind = DataBindingUtil.setContentView(this, R.layout.activity_main);
        handler = new ActivityMainEventHandler(this, bind);
        bind.setMainHandler(handler);

XML of our activity

   <data>
            <variable
                name="mainHandler"
                type="xx.xxx.packagename.eventHandlers.ActivityMainEventHandler" />
        </data>
  1. Set your background to view as below

Markup:

 android:background="@{mainHandler.conenctButtonStyle}"
  1. Then if you want check conditions of step2 again and redraw view just call

Code:

//BR.conenctButtonStyle it's automatically generated id
bind.notifyPropertyChanged(BR.conenctButtonStyle);
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Paweł
  • 205
  • 2
  • 11
0

Another solution for this, if you just want to set backgroundTint, but not whole background, you can use it like this:

You will need to import ContextCompat, if your min api is 21:

<import type="androidx.core.content.ContextCompat" />

...

app:backgroundTintList="@{ContextCompat.getColorStateList(context, [funtion_to_get_your_color_res_id]))}"