4

I'm trying to eliminate all the warnings of my Android application and one of them is this:

viewModel.value is a boxed field but needs to be un-boxed to execute android:checked. This may cause NPE so Data Binding will safely unbox it. You can change the expression and explicitly wrap viewModel.value with safeUnbox() to prevent the warning

Where value is a generic ObservableField that comes from a super class:

public abstract class BaseDataTypeViewModel<T> extends BaseObservable  {
    public final ObservableField<T> value = new ObservableField<>();
    ...
}

And is extented somewhere as a Boolean:

public class CheckBooleanDataTypeViewModel extends BaseDataTypeViewModel<Boolean> {
    ...
}

I saw on data binding - safeUnbox warning that the warnings happen because this is a Boolean and not a boolean, so I tried to add this: android:checked="@={safeUnbox(viewModel.value)}" instead of android:checked="@={viewModel.value}" but then I got an error saying I can't invert the safeUnbox() method.

****/ data binding error ****msg:The expression android.databinding.DynamicUtil.safeUnbox(viewModelValue) cannot be inverted: There is no inverse for method safeUnbox, you must add an @InverseMethod annotation to the method to indicate which method should be used when using it in two-way binding expressions

I understand correctly the 2 separated issues, but do I have to live with the warning to avoid the error or is their a solution to avoid both the warning and the error? What about the @InverseMethod it is talking about? I didn't manage to add this annotation because the method comes from the android package.

MHogge
  • 5,408
  • 15
  • 61
  • 104

4 Answers4

11

I haven't worked with Android Architecture Components or with the Data Binding libraries in this particular way, but I think I can still help.

Within your XML, you've got this:

android:checked="@={viewModel.value}"

The system is giving you a warning because it wants you to know that in the case where viewModel.value is null, it's going to do something special (behave as though it were false instead, presumably). It does this via the safeUnbox() method.

To solve the warning, it's suggesting making the safeUnbox() call explicit. You can't do that because there's no "inverse" of safeUnbox() to go back from boolean to Boolean.

But it doesn't sound like you have to use safeUnbox(); you could create your own method that converts Boolean to boolean, and then you could use the suggested annotation to declare which method will convert back from boolean to Boolean.

public class MyConversions {

    @InverseMethod("myBox")
    public static boolean myUnbox(Boolean b) {
        return (b != null) && b.booleanValue();
    }

    public static Boolean myBox(boolean b) {
        return b ? Boolean.TRUE : Boolean.FALSE;
    }
}

Now you can change your XML to:

android:checked="@={com.example.stackoverflow.MyConversions.myUnbox(viewModel.value)}"

I hope this helps. If it turns out that I'm way off-base, let me know; I'd love to learn more about this topic.

Most of what I have in this answer I learned from https://medium.com/google-developers/android-data-binding-inverse-functions-95aab4b11873

Ben P.
  • 52,661
  • 6
  • 95
  • 123
  • You did well understood the problem ! Nice way of resolving it. Just to be cleaner I imported the "com.example..." into an `` tag. – MHogge Nov 17 '17 at 09:11
  • Thanks for this! I created a class containing mappings for all Java primitive types. https://gist.github.com/pasmat/f1f8f04eeab4dde7dc9b534a51f67d15 – Pasi Matalamäki Aug 22 '18 at 14:25
  • I tried this but getting null pointer exception when doing livedata.getValue() in my ViewModel after changing my checkbox. I can see that it is running the boxing methods if I'm putting breakpoints and the warnings disappeared. Any idea what could be wrong? – nilsi Oct 18 '18 at 16:06
4

Came across this issue and found an easier solution. You can avoid this warning by creating a custom BindingAdapter for the boxed type like this:

@BindingAdapter("android:checked")
public static void setChecked(CompoundButton checkableView, Boolean isChecked) {
    checkableView.setChecked(isChecked != null ? isChecked : false);
}

This solution can be replicated to any property like visibility, enabled etc. and to any boxed primitive like Integer, Float etc.

You can also provide the value to be used in case your LiveData value is null like this:

@BindingAdapter(value={"android:checked", "nullValue"}, requireAll=false)
public static void setChecked(CompoundButton checkableView, Boolean isChecked, boolean nullValue) {
    checkableView.setChecked(isChecked != null ? isChecked : nullValue);
}

And call it like this:

<CheckBox
    ...
    android:checked='@{viewModel.value}'
    app:nullValue="@{false}"
    />
Sir Codesalot
  • 7,045
  • 2
  • 50
  • 56
0

do like this in xml. Last line of code is the most important.

<layout>

  <data>
    <import type="android.view.View"/>
    <variable name="entryViewModel"
        type="com.craiovadata.groupmap.viewmodel.EntryViewModel" />
  </data>

  <Button 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="@{safeUnbox(entryViewModel.showBtnMyGroups) ? View.VISIBLE : View.GONE, default = gone}" />
Dan Alboteanu
  • 9,404
  • 1
  • 52
  • 40
0

I find a code ,this this safeUnbox implementation。

/** @hide */
protected static int safeUnbox(java.lang.Integer boxed) {
    return boxed == null ? 0 : (int)boxed;
}

/** @hide */
protected static long safeUnbox(java.lang.Long boxed) {
    return boxed == null ? 0L : (long)boxed;
}

/** @hide */
protected static short safeUnbox(java.lang.Short boxed) {
    return boxed == null ? 0 : (short)boxed;
}

/** @hide */
protected static byte safeUnbox(java.lang.Byte boxed) {
    return boxed == null ? 0 : (byte)boxed;
}

/** @hide */
protected static char safeUnbox(java.lang.Character boxed) {
    return boxed == null ? '\u0000' : (char)boxed;
}

/** @hide */
protected static double safeUnbox(java.lang.Double boxed) {
    return boxed == null ? 0.0 : (double)boxed;
}

/** @hide */
protected static float safeUnbox(java.lang.Float boxed) {
    return boxed == null ? 0f : (float)boxed;
}

/** @hide */
protected static boolean safeUnbox(java.lang.Boolean boxed) {
    return boxed == null ? false : (boolean)boxed;
}

so ,if you value is not base type,you can not direct use safeUnbox,you should define a static function to safe unbox by youself.

Mr.jie
  • 42
  • 6