web-dev-qa-db-fra.com

Créer une liaison bidirectionnelle avec Android Data Binding

J'ai implémenté la nouvelle liaison de données Android et, après cette implémentation, j'ai réalisé qu'elle ne prend pas en charge la liaison bidirectionnelle. J'ai essayé de résoudre ce problème manuellement, mais j'ai du mal à trouver la bonne solution à utiliser lors de la liaison à un EditText. Dans ma mise en page, j'ai cette vue:

<EditText
Android:id="@+id/firstname"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:inputType="textCapWords|textNoSuggestions"
Android:text="@{statement.firstName}"/>

Une autre vue montre également les résultats:

<TextView
style="@style/Text.Large"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:text="@{statement.firstName}"/>

Dans mon fragment, je crée la liaison comme ceci: 

FragmentStatementPersonaliaBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_statement_personalia, container, false);
binding.setStatement(mCurrentStatement);

Cela fonctionne et met la valeur actuelle de firstName dans le EditText. Le problème est de savoir comment mettre à jour le modèle lorsque le texte change. J'ai essayé de mettre un auditeur OnTextChanged sur le editText et de mettre à jour le modèle. Cela a créé une boucle qui tue mon application (model-update met à jour l'interface graphique, qui appelle textChanged times infinity). Ensuite, j'ai essayé d'avertir uniquement lorsque de vrais changements se produisaient, comme ceci: 

@Bindable
public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
        boolean changed = !TextUtils.equals(this.firstName, firstName);
        this.firstName = firstName;
        if(changed) {
            notifyPropertyChanged(BR.firstName);
        }
    }

Cela fonctionnait mieux, mais à chaque fois que j'écris une lettre, l'interface graphique est mise à jour et, pour une raison quelconque, le curseur d'édition est déplacé au premier plan. 

Toute suggestion serait la bienvenue

42
Gober

EDIT 04.05.16: La liaison de données Android prend désormais automatiquement en charge la liaison bidirectionnelle! Remplacez simplement:

Android:text="@{viewModel.address}"

avec:

Android:text="@={viewModel.address}"

dans un EditText par exemple et vous obtenez une liaison bidirectionnelle. Assurez-vous de mettre à jour la dernière version d'Android Studio/gradle/build-tools pour l'activer.

(RÉPONSE PRÉCÉDENTE): 

J'ai essayé la solution de Bhavdip Pathar, mais cela n'a pas permis de mettre à jour d'autres vues que j'avais liées à la même variable. J'ai résolu ceci d'une manière différente, en créant mon propre EditText:

public class BindableEditText extends EditText{

public BindableEditText(Context context) {
    super(context);
}

public BindableEditText(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public BindableEditText(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

private boolean isInititalized = false;

@Override
public void setText(CharSequence text, BufferType type) {
    //Initialization
    if(!isInititalized){
        super.setText(text, type);
        if(type == BufferType.EDITABLE){
            isInititalized = true;
        }
        return;
    }

    //No change
    if(TextUtils.equals(getText(), text)){
        return;
    }

    //Change
    int prevCaretPosition = getSelectionEnd();
    super.setText(text, type);
    setSelection(prevCaretPosition);
}}

Avec cette solution, vous pouvez mettre à jour le modèle comme vous le souhaitez (TextWatcher, OnTextChangedListener, etc.) et il s’occupe de la boucle de mise à jour infinie. Avec cette solution, le modélisateur peut être implémenté simplement comme suit:

public void setFirstName(String firstName) {
    this.firstName = firstName;
    notifyPropertyChanged(BR.firstName);
}

Cela met moins de code dans la classe de modèle (vous pouvez garder les écouteurs dans votre fragment). 

J'apprécierais tout commentaire, amélioration ou autre/meilleure solution à mon problème

78
Gober

Ceci est maintenant pris en charge dans Android Studio 2.1+ avec le plugin Gradle 2.1+

Changez simplement l'attribut text d'EditText de @{} à @={} comme ceci:

<EditText
Android:id="@+id/firstname"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:inputType="textCapWords|textNoSuggestions"
Android:text="@={statement.firstName}"/>

pour plus d'informations, voir: https://halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-Android/

20
Jeremy Dowdall

@Gober La liaison de données Android prend en charge la liaison bidirectionnelle. Par conséquent, vous n'avez pas besoin de le faire manuellement. Comme vous avez essayé en mettant le OnTextChanged-listener sur le editText. Il convient de mettre à jour le modèle. 

J'ai essayé de mettre un auditeur OnTextChanged sur le editText et de mettre à jour le modèle. Cela a créé une boucle qui tue mon application (les mises à jour du modèle L'interface graphique, qui appelle textChanged fois infini).

Il convient de noter que les cadres de liaison qui implémentent une liaison bidirectionnelle effectueraient normalement cette vérification pour vous…

Voici l'exemple du modèle de vue modifié, qui ne génère pas de notification de liaison de données si la modification a été effectuée dans l'observateur:

Créons un SimpleTextWatcher qui n’a besoin que d’une méthode à remplacer:

public abstract class SimpleTextWatcher implements TextWatcher {

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
    }

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

    @Override
    public void afterTextChanged(Editable s) {
        onTextChanged(s.toString());
    }

    public abstract void onTextChanged(String newValue);
}

Ensuite, dans le modèle de vue, nous pouvons créer une méthode qui expose l'observateur. L'observateur sera configuré pour transmettre la valeur modifiée du contrôle au modèle de vue:

@Bindable
public TextWatcher getOnUsernameChanged() {

    return new SimpleTextWatcher() {
        @Override
        public void onTextChanged(String newValue) {
            setUsername(newValue);
        }
    };
}

Enfin, dans la vue, nous pouvons lier l'observateur à EditText à l'aide de addTextChangeListener:

<!-- most attributes removed -->
<EditText
    Android:id="@+id/input_username"
    Android:addTextChangedListener="@{viewModel.onUsernameChanged}"/>

Voici l'implémentation de la vue Modèle qui résout l'infini de la notification.

public class LoginViewModel extends BaseObservable {

    private String username;
    private String password;
    private boolean isInNotification = false;

    private Command loginCommand;

    public LoginViewModel(){
        loginCommand = new Command() {
            @Override
            public void onExecute() {
                Log.d("db", String.format("username=%s;password=%s", username, password));
            }
        };
    }

    @Bindable
    public String getUsername() {
        return this.username;
    }

    @Bindable
    public String getPassword() {
        return this.password;
    }

    public Command getLoginCommand() { return loginCommand; }

    public void setUsername(String username) {
        this.username = username;

        if (!isInNotification)
            notifyPropertyChanged(com.petermajor.databinding.BR.username);
    }

    public void setPassword(String password) {
        this.password = password;

        if (!isInNotification)
            notifyPropertyChanged(com.petermajor.databinding.BR.password);
    }

    @Bindable
    public TextWatcher getOnUsernameChanged() {

        return new SimpleTextWatcher() {
            @Override
            public void onTextChanged(String newValue) {
                isInNotification = true;
                setUsername(newValue);
                isInNotification = false;
            }
        };
    }

    @Bindable
    public TextWatcher getOnPasswordChanged() {

        return new SimpleTextWatcher() {
            @Override
            public void onTextChanged(String newValue) {
                isInNotification = true;
                setPassword(newValue);
                isInNotification = false;
            }
        };
    }
}

J'espère que c'est ce que vous cherchez et que cela peut vous aider. Merci

7
Bhavdip Sagar

Il y a une solution plus simple. Évitez simplement de mettre à jour le champ s'il n'avait pas vraiment changé.

@Bindable
public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
     if(this.firstName.equals(firstName))
        return;

     this.firstName = firstName;
     notifyPropertyChanged(BR.firstName);
}
2
rpattabi

POJO:

public class User {
    public final ObservableField<String> firstName =
            new ObservableField<>();
    public final ObservableField<String> lastName =
            new ObservableField<>();

    public User(String firstName, String lastName) {
        this.firstName.set(firstName);
        this.lastName.set(lastName);

    }


    public TextWatcherAdapter firstNameWatcher = new TextWatcherAdapter(firstName);
    public TextWatcherAdapter lastNameWatcher = new TextWatcherAdapter(lastName);

}

Disposition:

 <TextView Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="@{user.firstName,  default=First_NAME}"/>
        <TextView Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="@{user.lastName, default=LAST_NAME}"/>

        <EditText
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:id="@+id/editFirstName"
            Android:text="@{user.firstNameWatcher.value}"
            Android:addTextChangedListener="@{user.firstNameWatcher}"/>
        <EditText
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:id="@+id/editLastName"
            Android:text="@{user.lastNameWatcher.value}"
            Android:addTextChangedListener="@{user.lastNameWatcher}"/>

Observateur:

public class TextWatcherAdapter implements TextWatcher {

    public final ObservableField<String> value =
            new ObservableField<>();
    private final ObservableField<String> field;

    private boolean isInEditMode = false;

    public TextWatcherAdapter(ObservableField<String> f) {
        this.field = f;

        field.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback(){
            @Override
            public void onPropertyChanged(Observable sender, int propertyId) {
                if (isInEditMode){
                    return;
                }
                value.set(field.get());
            }
        });
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        //
    }

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

    @Override public void afterTextChanged(Editable s) {
        if (!Objects.equals(field.get(), s.toString())) {
            isInEditMode = true;
            field.set(s.toString());
            isInEditMode = false;
        }
    }

}
1
Eugene Mankovski

J'ai eu du mal à trouver un exemple complet de liaison de données bidirectionnelle. J'espère que cela vous aidera . La documentation complète est disponible: https://developer.Android.com/topic/libraries/data-binding/index.html }

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <data>
        <variable
            name="item"
            type="com.example.abc.twowaydatabinding.Item" />
    </data>

    <LinearLayout
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:orientation="vertical">

        <TextView
            Android:id="@+id/tv_title"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="@={item.name}"
            Android:textSize="20sp" />


        <Switch
            Android:id="@+id/switch_test"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:checked="@={item.checked}" />

        <Button
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="change"
            Android:onClick="button_onClick"/>

    </LinearLayout>
</layout>

Item.Java:

import Android.databinding.BaseObservable;
import Android.databinding.Bindable;

public class Item extends BaseObservable {
    private String name;
    private Boolean checked;
    @Bindable
    public String getName() {
        return this.name;
    }
    @Bindable
    public Boolean getChecked() {
        return this.checked;
    }
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }
    public void setChecked(Boolean checked) {
        this.checked = checked;
        notifyPropertyChanged(BR.checked);
    }
}

MainActivity.Java:

public class MainActivity extends AppCompatActivity {

    public Item item;


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

        item = new Item();
        item.setChecked(true);
        item.setName("a");

        /* By default, a Binding class will be generated based on the name of the layout file,
        converting it to Pascal case and suffixing “Binding” to it.
        The above layout file was activity_main.xml so the generate class was ActivityMainBinding */

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setItem(item);
    }

    public void button_onClick(View v) {
        item.setChecked(!item.getChecked());
        item.setName(item.getName() + "a");
    }
}

build.gradle:

Android {
...
    dataBinding{
        enabled=true
    }

}
0
live-love