Updating UI using ViewModel and DataBinding

后端 未结 3 534
清酒与你
清酒与你 2021-01-31 20:09

I am trying learn View-model android ,in my first phase of learning i am trying to update UI (Textview) by using view-model and data-binding. In View model i am having aynctask

相关标签:
3条回答
  • 2021-01-31 20:28

    You'll require to notify observer when you set value like this :

    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 讨论(0)
  • 2021-01-31 20:41

    I suggest to follow next basic principles:

    • don't overload data objects by business or presentation logic
    • only view model required to obtain data in presentation layer
    • view model should expose only ready to use data to presentation layer
    • (optional) background task should expose LiveData to deliver data

    Implementation notes:

    • firstName is read only on view
    • lastName is editable on view
    • loadUser() is not threadsafe
    • we have error message when call save() method until data is not loaded

    Don't overload data objects by business or presentation logic

    Suppose, we have UserData object with first and last name. So, getters it's (usually) all what we need:

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

    Only view model required to obtain data in presentation

    To follow this suggestion we should to use only view model in data binding layout:

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

    Note: you can use a few view models in one layout, but not raw data

    View model should expose only ready to use data to presentation

    This mean, you shouldn't to expose complex data objects (UserData in our case) directly from view model. Preferable to expose privative types which view can use as-is. In example below we don't need to hold UserData object because it used only to loading grouped data. We, probably, need to create UserData to save it but it depends on your repository implementation.

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

    Background task should expose LiveData to deliver data

    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: try to use RxJava library instead of AsyncTask to perform background work.

    0 讨论(0)
  • 2021-01-31 20:43

    If you want binding layout to work then you have to set your view in binding way. Also set data in binding 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());
        }
    }
    
    0 讨论(0)
提交回复
热议问题