Android Назначить LiveData<> для ListView

У меня есть представление списка для отображения пользователей и адаптера, который хорошо работает вместе с обычными списками пользователей ArrayLists. Однако я пытаюсь реализовать ViewModels и не могу заставить свой адаптер ListView работать с LiveData. Я не могу заставить любой из подобных вопросов/ответов работать на меня. Но это кажется довольно фундаментальным и, как будто это должно быть легко.

public class MainActivity extends AppCompatActivity {

MainActivityViewModel mainActivityViewModel;
ListView usersLV;
UsersAdapter userAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mainActivityViewModel = ViewModelProviders.of(this).get(MainActivityViewModel.class);
    usersLV = findViewById(R.id.usersLV);

    ArrayList<User> users = (ArrayList<User>)mainActivityViewModel.getUsers().getValue();
    if (users != null){
        userAdapter = new UsersAdapter(this,users);
        usersLV.setAdapter(userAdapter);
    }else{
        Log.d("gwyd","user list was null");
        userAdapter = new UsersAdapter(this,new ArrayList<User>());
        usersLV.setAdapter(userAdapter);
    }


    mainActivityViewModel.getUsers().observe(this, new Observer<List<User>>() {
        @Override
        public void onChanged(@Nullable List<User> users) {
            if (users != null) {
                userAdapter.setUsers(users);
            }else {
                Log.d("gwyd","no users found in db");
                userAdapter.setUsers(new ArrayList<User>());
            }
        }
    });
}}

ViewModel:

public class MainActivityViewModel extends AndroidViewModel {
private DaoRepository daoRepository;
private LiveData<List<User>> users;


public LiveData<List<User>> getUsers() {
    if (users == null){
        updateUsersList();
    }
    return users;
}
public MainActivityViewModel(@NonNull Application application) {
    super(application);
    daoRepository = new DaoRepository(application);
    users = updateUsersList();
}
public LiveData<List<User>> updateUsersList(){
    return daoRepository.getAllUsers();
}}

и пользовательский адаптер:

public class UsersAdapter extends ArrayAdapter<User> {
private List<User> users;

public void setUsers(List<User> users) {
    this.users = users;
    notifyDataSetChanged();
}

public UsersAdapter(Context context, List<User> users) {
    super(context, 0, users);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // Get the data item for this position
    User user = getItem(position);
    // Check if an existing view is being reused, otherwise inflate the view
    if (convertView == null) {
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.user_list_item, parent, false);
    }
    // Lookup view for data population
    TextView tvName = (TextView) convertView.findViewById(R.id.userLvItemTV);
    // Populate the data into the template view using the data object
    tvName.setText(user.getUserName());
    // Return the completed view to render on screen
    return convertView;
}}

И файл макета List_item

<LinearLayout
xmlns:android = "http://schemas.android.com/apk/res/android" android:layout_width = "match_parent"
android:layout_height = "match_parent"
android:orientation = "vertical">
    <TextView
        android:id = "@+id/userLvItemTV"
        android:layout_width = "match_parent"
        android:layout_height = "wrap_content" />

3
0
8 980
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Изменять:

mainActivityViewModel.getUsers().observe(this, new Observer<List<User>>() {
        @Override
        public void onChanged(@Nullable List<User> users) {
            playerAdapter.notifyDataSetChanged();
        }
    });

К:

mainActivityViewModel.getUsers().observe(this, new Observer<List<User>>() {
        @Override
        public void onChanged(@Nullable List<User> users) {
            playerAdapter.setUsers(users);
        }
    });

И в вашем адаптере создайте сеттер, например:

public void setUsers(List<User> users) {
    this.users = users;
    notifyDataSetChanged();
}

Мой метод getUsers каким-то образом возвращает null, так что мне придется немного разобраться в этом. Но это имеет смысл. дать адаптеру массива ссылку на список пользователей

bodovix 25.02.2019 13:56

Я внес предложенные вами изменения, мне также пришлось добавить некоторые другие изменения, чтобы предотвратить сбой, в основном проверка нулевого возврата. Теперь он находится в точке, когда я создаю адаптер, он не может найти пользователей, поэтому использует часть else в статусе. Затем, когда onChange получает удар, он может найти пользователя в списке, но все равно не выводит его, как ожидалось.

bodovix 25.02.2019 15:23

(ArrayList<User>)mainActivityViewModel.getUsers().getValue() Кажется, всегда возвращает значение null, если только оно не находится в onChange... но тогда я не могу установить его на адаптер и получить эту ошибку: (UsersAdapter (android.content .Context, List<User>) в UsersAdapter нельзя применить к (anonymous android.arch.lifecycle.Observer<java.util.List<com.example.c‌​oursework.model.User‌​>>, List<User>) ) Также мы дали адаптеру список пользователей, но не указали, как заставить его использовать этот список, а не исходный / установить исходный список на этот. сбит с толку

bodovix 25.02.2019 17:21

Я думаю, что ваша проблема заключается в этом: public LiveData<List<User>> getUsers() { if (users == null){ updateUsersList(); } return users; изменить так, чтобы: users = updateUsersList();

Almyk 26.02.2019 00:06
Ответ принят как подходящий

После нескольких строгих слов с самим собой я придумал это как решение: В основном то же самое, что и я, но в методе onChange ваших наблюдателей используйте getApplicationContext() вместо this для инициализации ArrayAdapter. Кажется, что контекст действий не может быть доступен из внутреннего класса наблюдателей и выдает расплывчатую ошибку, которая привела к путанице...

Мне нужно больше читать о том, как различные контексты влияют на вещи. но если никто не придумает slam dunk, вот как это сделать, я, вероятно, отмечу это как ответ через пару дней, потому что, похоже, это решило проблему.

Основная деятельность:

public class MainActivity extends AppCompatActivity {

MainActivityViewModel mainActivityViewModel;
ListView usersLV;
UsersAdapter userAdapter;

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

    mainActivityViewModel = ViewModelProviders.of(this).get(MainActivityViewModel.class);
    usersLV = findViewById(R.id.usersLV);

    mainActivityViewModel.getUsers().observe(this, new Observer<List<User>>() {
        @Override
        public void onChanged(@Nullable List<User> users) {
            if (users != null) {
                userAdapter =new UsersAdapter(getApplicationContext(), users);
                usersLV.setAdapter(userAdapter);
            }
            userAdapter.notifyDataSetChanged();

        }
    });
}}

Просмотр модели:

public class MainActivityViewModel extends AndroidViewModel {
private DaoRepository daoRepository;
private LiveData<List<User>> users;


public LiveData<List<User>> getUsers() {
    if (users == null){
       users = updateUsersList();
    }
    return users;
}
public MainActivityViewModel(@NonNull Application application) {
    super(application);
    daoRepository = new DaoRepository(application);
    users = updateUsersList();
}
public LiveData<List<User>> updateUsersList(){
    return daoRepository.getAllUsers();
}}

Адаптер массива:

public class UsersAdapter extends ArrayAdapter<User> {

public UsersAdapter(Context context, List<User> users) {
    super(context, 0, users);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // Get the data item for this position
    User user = getItem(position);
    // Check if an existing view is being reused, otherwise inflate the view
    if (convertView == null) {
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.user_list_item, parent, false);
    }
    // Lookup view for data population
    TextView tvName = (TextView) convertView.findViewById(R.id.userLvItemTV);
    // Populate the data into the template view using the data object
    tvName.setText(user.getUserName());
    // Return the completed view to render on screen
    return convertView;
}

}

Элемент списка Xml:

<?xml version = "1.0" encoding = "utf-8"?>
<LinearLayout
xmlns:android = "http://schemas.android.com/apk/res/android" android:layout_width = "match_parent"
android:layout_height = "match_parent"
android:orientation = "vertical">
    <TextView
        android:id = "@+id/userLvItemTV"
        android:layout_width = "match_parent"
        android:textSize = "20sp"
        android:height = "20dp"
        android:fontFamily = "sans-serif-medium"
        android:gravity = "center"
        android:layout_height = "60dp" />
</LinearLayout>

Но теперь вы воссоздаете адаптер каждый раз, когда ваш список пользователей меняется. Это не кажется правильным. Ответ Алмыка ниже выглядит более подходящим.

Paul-Etienne 13.08.2020 15:50

Другие вопросы по теме