web-dev-qa-db-ja.com

ViewModelとDataBindingを使用してUIを更新する

私はView-modelを学ぶことを試みていますAndroid、学習の私の最初の段階で、私はview-modelとdata-bindingを使用してUI(Textview)を更新しようとしています。コールバックとそれはREST 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);
    }
};

}

およびメインクラス

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

次の基本原則に従うことをお勧めします。

  • ビジネスまたはプレゼンテーションロジックによってデータオブジェクトをオーバーロードしないでください
  • プレゼンテーション層でデータを取得するために必要なビューモデルのみ
  • ビューモデルは使用準備完了データのみをプレゼンテーションレイヤーに公開する必要があります
  • (オプション)バックグラウンドタスクは、データを配信するために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>

注:1つのレイアウトでいくつかのビューモデルを使用できますが、生データは使用できません

ビューモデルは、すぐに使用できるデータのみをプレゼンテーションに公開する必要があります

つまり、複雑なデータオブジェクト(この場合はUserData)をビューモデルから直接公開するべきではありません。ビューがas-isを使用できるプライベートタイプを公開することをお勧めします。以下の例では、UserDataオブジェクトを保持する必要はありません。これは、grouped dataのロードにのみ使用されるためです。おそらく、保存するには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:AsyncTaskの代わりにRxJavaライブラリを使用してバックグラウンド作業を実行してください。

13
XIII-th

このような値を設定する場合、オブザーバーに通知する必要があります。

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

バインディングレイアウトを機能させるには、ビューをバインディング方法で設定する必要があります。また、バインディングクラスにデータを設定します。

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