使用 ViewModel 和 DataBinding 更新 UI

2024-06-20

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

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

和我的 MainActivity 类:

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

和布局文件

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

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

  • 不要通过业务或表示逻辑使数据对象过载
  • 表示层只需要视图模型来获取数据
  • 视图模型应该只公开可以使用数据到表示层
  • (可选)后台任务应该公开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在我们的例子中)直接来自视图模型。最好公开视图可以使用的私有类型as-is。在下面的例子中我们不需要持有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执行后台工作。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 ViewModel 和 DataBinding 更新 UI 的相关文章

随机推荐