araraujo araraujo - 3 months ago 13
Android Question

The android data binding is not working properly

I would like to help to fix a problem.

First, following the details of my code:

build.gradle (Project: android)

buildscript {
repositories {
jcenter()
mavenCentral()
maven { url '/home/melti/java/repository' }
}
dependencies {
classpath "com.android.tools.build:gradle:1.3.0-beta4"
classpath "com.android.databinding:dataBinder:1.0-rc0"

}
}

allprojects {
repositories {
jcenter()
mavenCentral()
maven { url '/home/melti/java/repository' }

}
}


build.gradle (Module: app)

apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'

android {
compileSdkVersion 22
buildToolsVersion "23.0.0 rc2"

defaultConfig {
applicationId "br.com.soma"
minSdkVersion 16
targetSdkVersion 21
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.0.0'
compile 'com.android.support:recyclerview-v7:21.0.3'
compile 'com.android.support:cardview-v7:21.0.3'
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp:okhttp:2.4.0'
compile 'com.google.code.gson:gson:2.3.1'
compile 'org.springframework:spring-core:4.1.7.RELEASE'
compile 'org.apache.commons:commons-io:1.3.2'


}


AmanteEditModel

package br.com.soma.amante.edit;

import android.databinding.BaseObservable;
import android.databinding.Bindable;

import br.com.soma.BR;

/**
* Created by spassu on 09/07/15.
*/
public class AmanteEditModel extends BaseObservable {

private String senhaConfirm;



@Bindable
public String getSenhaConfirm() {
return senhaConfirm;
}

public void setSenhaConfirm(String senhaConfirm) {
this.senhaConfirm = senhaConfirm;
notifyPropertyChanged(BR.senhaConfirm);
}
}


fragment_amante_edit

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto">

<data>
<variable name="model" type="br.com.soma.amante.edit.AmanteEditModel"/>
</data>

<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent" >

<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/card_edit_style"
android:layout_gravity="top"
card_view:cardCornerRadius="0dp">


<EditText
android:id="@+id/amante_edit_senha_confirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:hint="Confirme a senha"
android:inputType="textPassword"
android:maxLines="1"
android:text="@{model.senhaConfirm}"
/>

</android.support.v7.widget.CardView>

</ScrollView>
</layout>


AmanteEditFragment

package br.com.soma.amante.edit;

import android.app.Fragment;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import br.com.soma.R;
import br.com.soma.amante.view.AmanteViewActivity;
import br.com.soma.databinding.FragmentAmanteEditBinding;

/**
* Created by spassu on 27/05/15.
*/
public class AmanteEditFragment extends Fragment {

private AmanteEditModel model;

private static final String AMANTE_ID = "amanteId";
public static final String DEFAULT_FRAGMENT_TAG = "amanteEditFragment";

// Views
private long amanteId;

public AmanteEditFragment() {
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
model = new AmanteEditModel();
FragmentAmanteEditBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_amante_edit, container, false);
binding.setModel(model);
return binding.getRoot();
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_amante_edit, menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
// The Save button was pressed
case R.id.menu_amante_edit_save:
save();
getActivity().finish();
return true;
}
return super.onOptionsItemSelected(item);
}


public static AmanteEditFragment newInstance(long id) {
AmanteEditFragment fragment = new AmanteEditFragment();
Bundle args = new Bundle();
args.putLong(AmanteViewActivity.EXTRA_AMANTEID, id);
fragment.setArguments(args);
return fragment;
}

private void save() {
Toast.makeText(getActivity(), model.getSenhaConfirm(), Toast.LENGTH_SHORT).show();
}
}


After enter data in
senhaConfirm
and clicking
save(
), I found that the binding does not work, that is, the
senhaConfirm
into
AmanteEditModel
is null yet. Can anyone help me?

Answer

EDIT: According to Remi David Android Studio now features two way databinding. The solution I describe below should no longer be nessesary.

Thanks Remi for pointing that out.


With respect to user input Android Databinding is only one way. The GUI will automagically reflect any changes in the model, but not vice versa.

You need to add a TextChangeListener to your model class which sets the model property when the user changes anything.

Example:

public class AmanteEditModel extends BaseObservable {

    private String senhaConfirm;

    @Bindable
    public String getSenhaConfirm() {
        return senhaConfirm;
    }

    public void setSenhaConfirm(String senhaConfirm) {
        this.senhaConfirm = senhaConfirm;
        notifyPropertyChanged(BR.senhaConfirm);
    }

    // Textwatcher Reference: http://developer.android.com/reference/android/text/TextWatcher.html
    public TextWatcher getMyEditTextWatcher() {
        return new TextWatcher() {

            public void afterTextChanged(Editable s) {
            }

            public void beforeTextChanged(CharSequence s, int start,
                                          int count, int after) {
            }

            public void onTextChanged(CharSequence s, int start,
                                  int before, int count) {
                // Important! Use the property setter, otherwhise the model won't be informed about the change.
                setSenhaConfirm(s);
            }
        };
    }

}

In your layout xml change EditText to this:

<EditText
    android:id="@+id/amante_edit_senha_confirm"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center_vertical"
    android:hint="Confirme a senha"
    android:inputType="textPassword"
    android:maxLines="1"
    android:text="@{model.senhaConfirm}"
    app:addTextChangeListener="@{model.myEditTextWatcher}"
    />

Watch for the namespace of addTextChangeListener. This method might not be available through the android: namespace, so I'm using app: here. You may also use bind: to make the binding more clear.

So don't miss to add

xmlns:app="http://schemas.android.com/apk/res-auto"

or

xmlns:bind="http://schemas.android.com/apk/res-auto"

to your XML namespaces.

This solution works for all input controls, custom included, given you provide the correct Listeners in your model.

TextWatcher Reference

Comments