-1

我正在研究架构组件,现在我正在学习 LiveData 和数据绑定。我可以将 LiveData 与整数值的布局绑定,但我无法找到用户列表。

这是带有数据绑定的 LiveData 的一个工作示例

<?xml version="1.0" encoding="utf-8"?>
<layout>

    <data>
        <variable
            name="viewModel"
            type="com.example.tutorial3livedataanddatabinding.viewmodel.CounterViewModel" />
    </data>

    <android.support.constraint.ConstraintLayout 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"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(viewModel.counter)}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </android.support.constraint.ConstraintLayout>
</layout>

ViewModel 类和活动

public class CounterViewModel extends ViewModel {
    private MutableLiveData<Integer> counter = new MutableLiveData<>();

    public MutableLiveData<Integer> getCounter() {
        return counter;
    }

    public void setCounter(MutableLiveData<Integer> counter) {
        this.counter = counter;
    }

    public void counterValue(int val) {
        this.counter.setValue(val);
    }
}


public class MainActivity extends AppCompatActivity {

    private int mCounter = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        /*
            LifeCycleOwner should be set for LiveData changes to be propagated to UI for this binding
         */

        binding.setLifecycleOwner(this);

        final CounterViewModel counterViewModel = ViewModelProviders.of(this).get(CounterViewModel.class);

        binding.setViewModel(counterViewModel);
        increaseCounter(counterViewModel);
    }

    private void increaseCounter(final CounterViewModel counterViewModel) {
        final Handler handler = new Handler();

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mCounter ++;
                counterViewModel.counterValue(mCounter);
                handler.postDelayed(this, 1000);
            }
        }, 1000);
    }
}

对于上面的示例,数据绑定数据工作正常。计数器更新,但与文档状态不同,它不会在应用程序停止或 LiveData 时停止计数

注意: LiveData 对象仅在活动或 LifecycleOwner 处于活动状态时发送更新。如果您导航到其他应用程序,日志消息会暂停,直到您返回。LiveData 对象仅在其各自的生命周期所有者为 STARTED 或 RESUMED 时才将订阅视为活动的。

我真正的问题是如何用一个LiveData<List<User>>类来实现数据绑定。

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <variable
            name="viewModel"
            type="com.example.tutorial3livedataanddatabinding2.viewmodel.MyViewModel"/>
    </data>

    <android.support.constraint.ConstraintLayout 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"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.tutorial3livedataanddatabinding2.MainActivity">

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.getUserRecords()}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.349" />

        <Button
            android:id="@+id/button_shufle"
            android:layout_width="88dp"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="20dp"
            android:onClick="@{() -> viewModel.shuffleUsers()}"
            android:text="Shuffle"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/textView" />

        <Button
            android:id="@+id/button_add"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:onClick="@{() -> viewModel.addUser()}"
            android:text="Add"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/button_shufle" />


    </android.support.constraint.ConstraintLayout>
</layout>

android:text="@{viewModel.getUserRecords()在开始时将用户数据绑定为字符串,但当新用户添加或列表被打乱时它不会更新,除非设备旋转。

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;

    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // do async operation to fetch users
        List<User> userList = new ArrayList<>();

        User user = new User();
        user.setFirstName("Jack");
        user.setLastName("Daniels");
        userList.add(user);

        user = new User();
        user.setFirstName("Johnny");
        user.setLastName("Walker");
        userList.add(user);

        user = new User();
        user.setFirstName("James");
        user.setLastName("Jameson");
        userList.add(user);

        user = new User();
        user.setFirstName("Arthur");
        user.setLastName("Guinness");
        userList.add(user);

        users.setValue(userList);

    }

    public void shuffleUsers() {
        if (users != null && users.getValue() != null && users.getValue().size() > 0) {
            Collections.shuffle(users.getValue());
            // Needed to update Live Data observers
            users.setValue(users.getValue());
        }
    }

    public void addUser() {
        if (users != null && users.getValue() != null) {
            User user = new User();
            user.setFirstName("Winston");
            user.setLastName("Whiskey");

            users.getValue().add(user);
            // Needed to update Live Data observers
            users.setValue(users.getValue());

        }
    }


    public String getUserRecords() {

        if (users != null && users.getValue() != null && users.getValue().size() > 0) {
            StringBuilder sb = new StringBuilder();
            for (User user : users.getValue()) {
                sb.append( user.getFirstName() + " " + user.getLastName() + "\n");
            }

            return sb.toString();
        }

        return "empty list";
    }
}

活动

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        MyViewModel myViewModel = ViewModelProviders.of(MainActivity.this).get(MyViewModel.class);
        myViewModel.getUsers();

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setViewModel(myViewModel);
        // Required for  binding with LiveData
        binding.setLifecycleOwner(this);

    }
}

编辑:我在 Blackbelt 的回答之后编辑了 MyViewModel 类现在我进入 Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.arch.lifecycle.LiveData.observeForever(android.arch.lifecycle.Observer)' on a null object reference System.out.println()里面没有被调用,其他 2 被调用并且apply()不返回nullusersusers.getValue()

import android.arch.core.util.Function;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Transformations;
import android.arch.lifecycle.ViewModel;

import com.example.tutorial3livedataanddatabinding2.model.User;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class MyViewModel extends ViewModel {

    private MutableLiveData<List<User>> users;

    public LiveData<String> userStringLiveData = Transformations.map(users, new Function<List<User>, String>() {
        @Override
        public String apply(List<User> input) {
            System.out.println("userStringLiveData users: " + users);
            return getUserRecords();
        }
    });

    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // do async operation to fetch users
        List<User> userList = new ArrayList<>();

        User user = new User();
        user.setFirstName("Jack");
        user.setLastName("Daniels");
        userList.add(user);

        user = new User();
        user.setFirstName("Johnny");
        user.setLastName("Walker");
        userList.add(user);

        user = new User();
        user.setFirstName("James");
        user.setLastName("Jameson");
        userList.add(user);

        user = new User();
        user.setFirstName("Arthur");
        user.setLastName("Guinness");
        userList.add(user);

        users.setValue(userList);

        System.out.println("MyViewModel users " + users);
        System.out.println("MyViewModel users List" + users.getValue());

    }

    public void shuffleUsers() {
        if (users != null && users.getValue() != null && users.getValue().size() > 0) {
            Collections.shuffle(users.getValue());
            // Needed to update Live Data observers
            users.setValue(users.getValue());
        }
    }

    public void addUser() {
        if (users != null && users.getValue() != null) {

            User user = new User();
            user.setFirstName("Winston");
            user.setLastName("Whiskey");

            users.getValue().add(user);
            // Needed to update Live Data observers
            users.setValue(users.getValue());

        }
    }


    public String getUserRecords() {

        if (users != null && users.getValue() != null && users.getValue().size() > 0) {
            StringBuilder sb = new StringBuilder();
            for (User user : users.getValue()) {
                sb.append(user.getFirstName() + " " + user.getLastName() + "\n");
            }

            return sb.toString();
        }

        return "empty list";
    }
}
4

2 回答 2

1

inandroid:text="@{viewModel.getUserRecords()} getUserRecords()返回 a String,而不是LiveData对象。您应该改为绑定到LiveData<String>对象。

例如

 public LiveData<String> userStringLiveData = Transformations.map(getUsers(), new Function<List<User>, String>() {
        @Override
        public String apply(List<User> input) {
            return getUserRecords();
        }
  });

并在您的布局android:text="@{viewModel.userStringLiveData}中。这样,当您在用户上发布新值时,将调用 map 并更新 UI。

于 2018-06-22T09:01:26.587 回答
1

看起来用户在创建对象时很像为空,也就是第一次评估 userStringLiveData 时。所以 Transformations.map 将传递一个 null 作为它的第一个参数,而不是你希望它稍后操作的 LiveData。

这是我的建议:

public class r_RealTulipCoin {

  private LiveData<List<User>> users;

  public LiveData<String> userStringLiveData;

  public void onCreate() {
    getUsersLiveDataFromRoomOrSomething();
    prepareUserStringLiveData();
  }

  private void prepareUserStringLiveData() {
    userStringLiveData = Transformations.map(users, new android.arch.core.util.Function<List<User>, String>() {
          @Override
          public String apply(List<User> input) {
            System.out.println("userStringLiveData users: " + users);
            return getUserRecordsAsSingleStringFromThisParticularList(input);
          }
        });
  }

  private String getUserRecordsAsSingleStringFromThisParticularList(List<User> a_list_of_users) {
    return "i concatenated them or something";
  }

  private class User {
    String a_name_or_something;
  }

}

这是一个模型,但我希望它能说明我的观点。你会注意到我已经做了几件事......在我尝试将用户传递到 Transformations.map 之前,我确保它实际上是非空的。我使用 map 提供的输入参数。我很感激您已经知道它指的是什么,这就是您的代码依赖于使用 users 字段的原因,但是考虑它的来源以及 Transformations 类试图做什么可能会对您有所帮助。

在你的情况下,我建议你这样做:

public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<>();
            // this is the first time the users pointer is not null
            loadUsers();
            prepareUserStringLiveData();
        }
        return users;
    }

最后一个替代方法是返回原始代码并在第一次构造转换时使用 getUsers() 而不是 users:

public LiveData<String> userStringLiveData = Transformations.map(getUsers(), blah...)

这不是最简洁的解决方案,但它再次说明了为什么我一直坚持认为您实际上是在将 null 传递给 Transformations.map。

于 2018-06-22T19:54:45.343 回答