1

I'm having an issue with databinding. I'm trying to include an XML layout setting the tag of the included TextView. However, it resolves to the the included layout's name prefixed by layout and suffixed by _0 i.e.layout/common_helpinfo_0

In the main layout I have :-

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:orientation="vertical"
        ....
        <LinearLayout
            .....
            >
            <TextView
                .... />
            <include layout="@layout/common_helpinfo"
                android:id="@+id/hi_tag_world1"
                app:tagstr="@{@string/hi_tag_world1}"
                >
            </include>
        </LinearLayout>
        ....
    </LinearLayout>
</layout>

The included layout common_helpinfo is :-

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="tagstr" type="String"/>
    </data>
    <TextView
        ....
        android:tag="@{tagstr}"
        ''''
        android:onClick="clickHelpInfo"
        >
    </TextView>
</layout>

To test this I have the following in my MainActivity :-

     public void clickHelpInfo(View v) {
        String hicaller = v.getTag().toString();
        int vid = v.getId();

        Toast.makeText(this,"You Clicked Help from button=" + hicaller + " ID+" + Integer.toString(vid)
                , Toast.LENGTH_SHORT).show();
    }

In the main layout I also have, a hard-coded equivalent of the included TextView:-

        <TextView
            ....
            android:tag="@string/hi_tag_world1"
            ''''
            android:onClick="clickHelpInfo"
            >
        </TextView>

And this works as expected, with the Toast displaying the contents of the String Resource hi_tag_world1.

In ActivitMainBinding, after a successful compile, I have the following which appears to show where the incorrect data is being set (note I've actually included the common_helpinfo twice both act the same) :-

private static final android.util.SparseIntArray sViewsWithIds;
static {
    sIncludes = new android.databinding.ViewDataBinding.IncludedLayouts(12);
    sIncludes.setIncludes(1, 
        new String[] {"common_helpinfo"}, <<<<<
        new int[] {3},
        new int[] {R.layout.common_helpinfo}); <<<<<
    sIncludes.setIncludes(2, 
        new String[] {"common_helpinfo"}, <<<<<
        new int[] {4},
        new int[] {R.layout.common_helpinfo}); <<<<<
    sViewsWithIds = new android.util.SparseIntArray();
    sViewsWithIds.put(R.id.vtext01, 5);
    sViewsWithIds.put(R.id.vtext02, 6);
    sViewsWithIds.put(R.id.vtext03, 7);
    sViewsWithIds.put(R.id.tvhi03, 8);
    sViewsWithIds.put(R.id.etext01, 9);
    sViewsWithIds.put(R.id.actvemail, 10);
    sViewsWithIds.put(R.id.lvemail, 11);
}

However later in ActivityMainBinding I get the following which appears to try to get the correct data :-

@Override
protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;
        mDirtyFlags = 0;
    }
    // batch finished
    if ((dirtyFlags & 0x4L) != 0) {
        // api target 1

        this.hiTagWorld1.setTagstr(getRoot().getResources().getString(R.string.hi_tag_world1));
        this.hiTagWorld2.setTagstr(getRoot().getResources().getString(R.string.hi_tag_world2));
    }
    executeBindingsOn(hiTagWorld1);
    executeBindingsOn(hiTagWorld2);
}

I've looked at and based my code on How do I use databinding to combine a string from resources with a dynamic variable in XML?

I've read through Data Binding Library

I've turned on DataBinding and have build.gradle as :-

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "mjt.testvcsuse"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    dataBinding {
        enabled = true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.1.0'
    testCompile 'junit:junit:4.12'
}
MikeT
  • 51,415
  • 16
  • 49
  • 68

1 Answers1

0

After a fair bit of playing around, I appear to have found a solution and that is to use the dataBindingUtil's setContentView method to replace the setContentView method.

e.g. final ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);

i.e. Instead of :-

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ....
    }

The binding worked using :-

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        ....
    }

I found this by reading this article Android Authority - Data Binding in Android, written by Obaro Ogbo. The relevant extract being :-

Data Binding Activity

At the moment, we have a layout file that is data binding capable. However, to utilize its data binding ability, we have to load it in a different way.

Previously, you would load your layout like this:

setContentView(R.layout.activity_main);
final Button button1 = (Button)findViewById(R.id.button1);
button.setOnClickListener(...);

With data binding, a Binding class is auto generated from your layout file. The class is named using your layout file name by default. The default name is generated by capitalizing the first letter of each word after an underscore, removing all underscores, and adding ‘Binding’ to the name. As such, activity_main.xml will result in a class called ActivityMainBinding. To associate this auto generated binding class in your code, you invoke DataBindingUtil’s setContentView

final ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(
    this, R.layout.activity_main);
activityMainBinding.updateButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        activityMainBinding.textView1.setText(R.string.text1b);
    }
});

In the code snippet above, you will notice that we can access the updateButton Button directly. All views with an ‘@+id’ in a data binding layout are automatically assigned to a final field of the correct type. So Button updateButton is created for the layout Button with ‘@+id/updateButton’, and TextView textView1 is created for the id/text_view1 TextView.

It doesn't appear to interfere with existing Views e.g. the non-data bound/hard coded TextView onClick handling still works with no other code changes.

MikeT
  • 51,415
  • 16
  • 49
  • 68