web-dev-qa-db-fra.com

Mise à jour de l'interface utilisateur à l'aide de ViewModel et DataBinding

J'essaie d'apprendre View-model Android, dans ma première phase d'apprentissage, j'essaie de mettre à jour l'interface utilisateur (Textview) à l'aide de view-model et de la liaison de données. Dans View model, je vais avoir un rappel aynctask et il invoquera REST appel api et j'obtiens une sortie mais je ne mets pas à jour la valeur dans textview.

ma classe viewmodel

public class ViewModelData extends ViewModel {

private MutableLiveData<UserData> users;

public LiveData<UserData> getUsers() {
    if (users == null) {
        users = new MutableLiveData<UserData>();
        loadUsers();
    }

    return users;
}

public void loadUsers() {
    ListTask listTask =new ListTask (taskHandler);
    listTask .execute();

}

public Handler taskHandler= new Handler() {
    @Override
    public void handleMessage(Message msg) {


        UserData  userData = (UserData) msg.obj;

        users.setValue(userData);
    }
};

}

et classe principale 

    public class MainActivity extends AppCompatActivity implements LifecycleOwner {
    private LifecycleRegistry mLifecycleRegistry;
    private TextView fName;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        fName = (TextView)findViewById(R.id.text_name);
        mLifecycleRegistry = new LifecycleRegistry(this);
        mLifecycleRegistry.markState(Lifecycle.State.CREATED);
        ViewModelData model = ViewModelProviders.of(this).get(ViewModelData.class);
        model.getUsers().observe(this, new Observer<UserData>() {
            @Override
            public void onChanged(@Nullable UserData userData) {
                Log.d("data"," =  - - - - ="+userData.getFirstName());

            }
        });

    }

    @Override
    public Lifecycle getLifecycle() {
        return mLifecycleRegistry;
    }
}

et ma classe de données

   public class UserData extends BaseObservable{
    private String firstName ;
@Bindable
    public String getFirstName() {
        return firstName;
    }

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

et fichier XML 

    <layout xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <data>
        <import type="Android.view.View" />
        <variable name="data" type="com.cgi.viewmodelexample.UserData"/>
    </data>
<RelativeLayout
    xmlns:tools="http://schemas.Android.com/tools"
    Android:id="@+id/activity_main"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:paddingBottom="@dimen/activity_vertical_margin"
    Android:paddingLeft="@dimen/activity_horizontal_margin"
    Android:paddingRight="@dimen/activity_horizontal_margin"
    Android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.cgi.viewmodelexample.MainActivity">

    <TextView
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:text="@{data.firstName}"
        Android:id="@+id/text_name"/>
</RelativeLayout>
</layout>
6
karthik selvaraj

Je suggère de suivre les principes de base suivants:

  • ne surchargez pas les objets de données par la logique métier ou de présentation
  • afficher uniquement le modèle requis pour obtenir des données dans la couche de présentation
  • le modèle de vue ne doit exposer que prêt à utiliser les données dans la couche de présentation
  • (facultatif) la tâche en arrière-plan doit exposer LiveData pour fournir des données

Notes d'implémentation:

  • firstName est en lecture seule
  • lastName est éditable à l'affichage
  • loadUser() n'est pas threadsafe 
  • nous avons un message d'erreur quand appelez la méthode save() jusqu'à ce que les données ne soient pas chargées

Ne surchargez pas les objets de données par une logique métier ou de présentation

Supposons que nous ayons un objet UserData avec le prénom et le nom. Donc, c'est tout ce dont nous avons besoin:

public class UserData {

    private String firstName;
    private String lastName;

    public UserData(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }
}

Afficher uniquement le modèle requis pour obtenir des données dans la présentation

Pour suivre cette suggestion, nous devrions utiliser uniquement le modèle d'affichage dans la présentation de liaison de données: 

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    xmlns:tools="http://schemas.Android.com/tools"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.example.vmtestapplication.MainActivity">

    <data>

        <import type="Android.view.View" />

        <!-- Only view model required -->
        <variable
            name="vm"
            type="com.example.vmtestapplication.UserDataViewModel" />
    </data>

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

        <!-- Primitive error message -->
        <TextView
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="@{vm.error}"
            Android:visibility="@{vm.error == null ? View.GONE : View.VISIBLE}"/>

        <!-- Read only field (only `@`) -->
        <TextView
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="@{vm.firstName}" />

        <!-- Two-way data binding (`@=`) -->
        <EditText
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="@={vm.lastName}" />

    </LinearLayout>
</layout>

Remarque: vous pouvez utiliser quelques modèles de vue dans une même présentation, mais pas les données brutes

Le modèle de vue ne doit exposer que les données prêtes à l'emploi à la présentation

Cela signifie que vous ne devez pas exposer objets de données complexes (UserData dans notre cas) directement à partir du modèle d'affichage. Préférable d'exposer les types privatifs que view peut utiliser as-is. Dans l'exemple ci-dessous, il n'est pas nécessaire de conserver l'objet UserData car il ne sert qu'au chargement des données grouped. Nous avons probablement besoin de créer UserData pour l’enregistrer, mais cela dépend de l’implémentation de votre référentiel.

public class UserDataViewModel extends ViewModel {

    private ListTask loadTask;

    private final MutableLiveData<String> firstName = new MediatorLiveData<>();
    private final MutableLiveData<String> lastName = new MediatorLiveData<>();
    private final MutableLiveData<String> error = new MutableLiveData<>();

    /**
     * Expose LiveData if you do not use two-way data binding
     */
    public LiveData<String> getFirstName() {
        return firstName;
    }

    /**
     * Expose MutableLiveData to use two-way data binding
     */
    public MutableLiveData<String> getLastName() {
        return lastName;
    }

    public LiveData<String> getError() {
        return error;
    }

    @MainThread
    public void loadUser(String userId) {
        // cancel previous running task
        cancelLoadTask();
        loadTask = new ListTask();
        Observer<UserData> observer = new Observer<UserData>() {
            @Override
            public void onChanged(@Nullable UserData userData) {
                // transform and deliver data to observers
                firstName.setValue(userData == null? null : userData.getFirstName());
                lastName.setValue(userData == null? null : userData.getLastName());
                // remove subscription on complete
                loadTask.getUserData().removeObserver(this);
            }
        };
        // it can be replaced to observe() if LifeCycleOwner is passed as argument
        loadTask.getUserData().observeForever(observer);
        // start loading task
        loadTask.execute(userId);
    }

    public void save() {
        // clear previous error message
        error.setValue(null);
        String fName = firstName.getValue(), lName = lastName.getValue();
        // validate data (in background)
        if (fName == null || lName == null) {
            error.setValue("Opps! Data is invalid");
            return;
        }
        // create and save object
        UserData newData = new UserData(fName, lName);
        // ...
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        cancelLoadTask();
    }

    private void cancelLoadTask() {
        if (loadTask != null)
            loadTask.cancel(true);
        loadTask = null;
    }
}

La tâche en arrière-plan doit exposer LiveData pour fournir des données

public class ListTask extends AsyncTask<String, Void, UserData> {

    private final MutableLiveData<UserData> data= new MediatorLiveData<>();

    public LiveData<UserData> getUserData() {
        return data;
    }

    @Override
    protected void onPostExecute(UserData userData) {
        data.setValue(userData);
    }

    @Override
    protected UserData doInBackground(String[] userId) {
        // some id validations
        return loadRemoiteUser(userId[0]);
    }
}

MainActivity.Java

public class MainActivity extends AppCompatActivity {

    private UserDataViewModel viewModel;

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

        // get view model
        viewModel = ViewModelProviders.of(this).get(UserDataViewModel.class);
        // create binding
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        // set view model to data binding
        binding.setVm(viewModel);
        // don't forget to set LifecycleOwner to data binding
        binding.setLifecycleOwner(this);

        // start user loading (if necessary)
        viewModel.loadUser("user_id");
        // ...
    }
}

PS: essayez d'utiliser la bibliothèque RxJava au lieu de AsyncTask pour effectuer un travail en arrière-plan.

4
XIII-th

Vous devrez notifier l'observateur lorsque vous définissez une valeur comme celle-ci:

public class UserData extends BaseObservable{
private String firstName ;
@Bindable
public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
    notifyPropertyChanged(BR.firstName) // call like this
}
}
0
Jeel Vankhede

Si vous voulez que la mise en page de reliure fonctionne, vous devez définir votre vue de manière liée. Définissez également les données dans la classe de liaison.

public class MainActivity extends AppCompatActivity implements LifecycleOwner {
    ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        ...
        ViewModelData model = ViewModelProviders.of(this).get(ViewModelData.class);
        ...
        binding.setData(model.getUsers());
    }
}
0
Khemraj