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>
Je suggère de suivre les principes de base suivants:
LiveData
pour fournir des donnéesNotes d'implémentation:
firstName
est en lecture seulelastName
est éditable à l'affichageloadUser()
n'est pas threadsafe save()
jusqu'à ce que les données ne soient pas chargéesSupposons 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;
}
}
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
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;
}
}
LiveData
pour fournir des donnéespublic 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]);
}
}
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.
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
}
}
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());
}
}