使用 ViewModel 和 DataBinding 更新 UI

Updating UI using ViewModel and DataBinding

我正在尝试在 android 中学习 ViewModel,在我的第一阶段学习中,我正在尝试使用 ViewModel 和 DataBinding 更新 UI (TextView)。在 ViewModel 中,我有一个 AsyncTask 回调,它将调用 REST API 调用。我收到来自 API 调用的响应,但 textview 中的值没有得到更新。

我的 ViewModel class:

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

和我的 MainActivity class:

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

和我的数据 class:

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

和布局文件

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

当您设置这样的值时,您需要通知观察者:

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

如果你想让绑定布局起作用,那么你必须以绑定方式设置你的视图。还要在绑定 class.

中设置数据
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());
    }
}

我建议遵循以下基本原则:

  • 不要通过业务或表示逻辑使数据对象过载
  • 只有视图模型需要在表示层获取数据
  • 视图模型应该只向表示层公开准备使用数据
  • (可选)后台任务应公开 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>

注意:您可以在一个布局中使用几个视图模型,但不能使用原始数据

视图模型应仅将准备好使用的数据公开给演示文稿

这意味着,您不应该直接从视图模型公开复杂数据对象(在我们的例子中是UserData)。最好公开视图可以使用 原样 的私有类型。在下面的示例中,我们不需要保存 UserData 对象,因为它仅用于加载 grouped 数据。我们可能需要创建 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 来执行后台工作。