Обновление пользовательского интерфейса с использованием ViewModel и DataBinding - PullRequest
0 голосов
/ 19 октября 2018

Я пытаюсь выучить View-модель Android, на первом этапе обучения я пытаюсь обновить UI (Textview), используя view-модель и привязку данных.В модели View у меня есть обратный вызов aynctask, и он будет вызывать API-вызов REST, и я получаю вывод, но я не обновляю значение в textview.

мой класс модели представления

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

}

и Основной класс

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

и мой класс данных

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

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

Ответы [ 3 ]

0 голосов
/ 22 октября 2018

Если вы хотите, чтобы верстка работала, вы должны настроить вид в обязательном порядке.Также установите данные в классе привязки.

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 голосов
/ 25 октября 2018

Я предлагаю придерживаться следующих основных принципов:

  • не перегружать объекты данных бизнес-логикой или логикой представления
  • только просматривать модель, необходимую для получения данных на уровне представления
  • модель представления должна представлять только готовые к использованию данные для уровня представления
  • (необязательно) фоновая задача должна предоставлять LiveData для доставки данных

Реализацияпримечания:

  • firstName только для чтения при просмотре
  • lastName редактируется при просмотре
  • loadUser() не является поточно-ориентированным
  • у нас появляется сообщение об ошибке при вызове метода save() до тех пор, пока данные не загружены

Не перегружайте объекты данных логикой бизнеса или представления

Предположим, у нас есть UserData объект симя и фамилия.Итак, геттеры - это (обычно) все, что нам нужно:

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

Только представление модели требуется для получения данных в представлении

Чтобы следовать этому предложению, мы должны использовать только модель представления в привязке данныхмакет:

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

Примечание. Можно использовать несколько моделей представлений в одном макете, но не необработанные данные

Модель представления должна отображать только готовые данные дляpresentation

Это означает, что вам не следует выставлять сложные объекты данных (* в нашем случае UserData) непосредственно из модели представления.Предпочтительно выставлять приватные типы, для просмотра которых можно использовать как есть .В приведенном ниже примере нам не нужно хранить объект UserData, поскольку он используется только для загрузки сгруппированных данных.Возможно, нам нужно создать UserData, чтобы сохранить его, но это зависит от реализации вашего репозитория.

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 для доставки данных

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: попробуйте использовать библиотеку RxJava вместо AsyncTask для выполнения фоновой работы.

0 голосов
/ 19 октября 2018

Вам потребуется уведомить наблюдателя, когда вы установите значение следующим образом:

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