wake-up-neo.com

Aktualisieren der Benutzeroberfläche mit ViewModel und DataBinding

Ich versuche, View-model Android zu lernen. In meiner ersten Lernphase versuche ich, die Benutzeroberfläche (Textview) mithilfe von View-Model und Datenbindung zu aktualisieren. In View-Modell habe ich einen Aynctask-Callback, und es wird REST Api-Aufruf aufgerufen, und ich bekomme eine Ausgabe für, aber ich aktualisiere den Wert in Textview nicht.

meine Viewmodel-Klasse

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);
    }
};

}

und Hauptklasse 

    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;
    }
}

und meine Datenklasse

   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);
    }
}

und XML-Datei 

    <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

Ich empfehle folgende Grundprinzipien zu befolgen:

  • Überladen Sie Datenobjekte nicht durch Geschäfts- oder Präsentationslogik
  • nur Ansichtsmodell, das zum Abrufen von Daten in der Präsentationsschicht erforderlich ist
  • das Ansichtsmodell sollte nur einsatzbereit data für die Präsentationsschicht verfügbar machen
  • (optional) Die Hintergrundaufgabe sollte LiveData zur Lieferung von Daten verfügbar machen

Implementierungshinweise:

  • firstName kann nur gelesen werden
  • lastName kann bearbeitet werden
  • loadUser() ist nicht threadsicher 
  • wir haben eine Fehlermeldung, wenn die Methode save() aufgerufen wird, bis die Daten nicht geladen sind

Überladen Sie Datenobjekte nicht nach Geschäfts- oder Präsentationslogik

Angenommen, wir haben ein UserData-Objekt mit Vor- und Nachnamen. Getter sind also (normalerweise) alles, was wir brauchen:

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;
    }
}

Nur Ansichtsmodell zum Abrufen von Daten in der Präsentation erforderlich

Um diesem Vorschlag zu folgen, sollten wir nur das Ansichtsmodell im Datenbindungslayout verwenden: 

<?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>

Hinweis: Sie können einige Ansichtsmodelle in einem Layout verwenden, jedoch keine Rohdaten.

Das Ansichtsmodell sollte nur fertige Daten für die Präsentation verfügbar machen

Dies bedeutet, dass Sie komplexe Datenobjekte (in unserem Fall UserData) nicht direkt aus dem Ansichtsmodell ausstellen sollten. Es ist vorzuziehen, private Typen freizulegen, deren Ansicht wie sie ist verwenden kann. Im folgenden Beispiel brauchen wir kein UserData-Objekt zu halten, da dieses nur zum Laden von grouped data verwendet wurde. Wir müssen wahrscheinlich UserData erstellen, um es zu speichern, aber es hängt von Ihrer Repository-Implementierung ab.

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;
    }
}

Die Hintergrundaufgabe sollte LiveData zur Lieferung von Daten verfügbar machen

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]);
    }
}

Hauptaktivität.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: Verwenden Sie die RxJava-Bibliothek anstelle von AsyncTask, um Hintergrundarbeit auszuführen.

4
XIII-th

Sie müssen den Beobachter benachrichtigen, wenn Sie den Wert wie folgt einstellen:

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

Wenn Sie möchten, dass das verbindliche Layout funktioniert, müssen Sie Ihre Ansicht verbindlich festlegen. Setzen Sie auch Daten in der Bindungsklasse.

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