Как получить значение LiveData из репозитория, у которого нет доступа к lifeCycleOwner для его наблюдения?

Я использовал MVVM, ROOM и databindig в своем приложении. Согласно Руководство по архитектуре приложения, я хочу обналичивать данные, используя комнату. В макете xml элемента RecyclerView я использую переменную CategoryViewModel. Я получаю список категорий из базы данных Room с типом LiveData. Я хочу изменить тип LiveData<list<CategoryItem>> на тип MutableLiveData<ArrayList<CategoryViewModel>>. Потому что, наконец, мой адаптер использует тип данных ArrayList<CategoryViewModel>. Как получить значение LiveData? Когда я вызываю метод getValue(), возвращает ноль. это модель CategoryItem:

    @Entity(tableName = "category_table")
public class CategoryItem implements Serializable {

    @PrimaryKey
    private int id;
    private String title;
    private String imagePath;
    @TypeConverters({SubCategoryConverter.class})
    private ArrayList<String> subCategory;
    @TypeConverters({DateConverter.class})
    private Date lastRefresh;

    public CategoryItem(int id, String title, String imagePath, ArrayList<String> subCategory, Date lastRefresh) {
        this.id = id;
        this.title = title;
        this.imagePath = imagePath;
        this.subCategory = subCategory;
        this.lastRefresh=lastRefresh;
    }

    public CategoryItem(int id, String title, String imagePath) {
        this.id = id;
        this.title = title;
        this.imagePath = imagePath;
    }

    public CategoryItem() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getImagePath() {
        return imagePath;
    }

    public void setImagePath(String imagePath) {
        this.imagePath = imagePath;
    }

    public ArrayList<String> getSubCategory() {
        return subCategory;
    }

    public void setSubCategory(ArrayList<String> subCategory) {
        this.subCategory = subCategory;
    }

    public Date getLastRefresh() {
        return lastRefresh;
    }

    public void setLastRefresh(Date lastRefresh) {
        this.lastRefresh = lastRefresh;
    }

}

это класс CategoryViewModel:

     public class CategoryViewModel extends AndroidViewModel {

        private String title;
        private String imagePath;
        private MutableLiveData<ArrayList<CategoryViewModel>> allCategories=new MutableLiveData<>();
        private CategoryRepository repository;

        public CategoryViewModel(@NonNull Application application) {
            super(application);
            repository=new CategoryRepository(application, Executors.newSingleThreadExecutor());
        }

        public void init(CategoryItem categoryItem){
            this.title=categoryItem.getTitle();
            this.imagePath=categoryItem.getImagePath();
        }

        public MutableLiveData<ArrayList<CategoryViewModel>> getAllCategories(){

            allCategories=repository.getCategory();
            return allCategories;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getImagePath() {
            return imagePath;
        }
    }

Это класс CategoryRepository:

    public class CategoryRepository {

    private static final String TAG = "CategoryRepository";
    private static int FRESH_TIMEOUT_IN_MINUTES = 1;

    private final Executor executor;

    private APIInterface apiInterface;
    public MutableLiveData<ArrayList<CategoryViewModel>> arrayListMutableLiveData=new MutableLiveData<>();


    private CategoryDao categoryDao;
    private Application application;

    public CategoryRepository(Application application,Executor executor) {
        this.executor = executor;
        this.application = application;
        apiInterface= APIClient.getClient().create(APIInterface.class);
        LearnDatabase database= LearnDatabase.getInstance(application);
        categoryDao=database.categoryDao();
    }

    public MutableLiveData<ArrayList<CategoryViewModel>>  getCategory(){

        refreshCategory();

        List<CategoryItem> items;
        categoryDao.loadCategoryItem();

        items=categoryDao.loadCategoryItem().getValue(); // return null
        CategoryItem category;
        ArrayList<CategoryViewModel> arrayList=new ArrayList<>();

        for(int i=0;i<items.size();i++){

            category=items.get(i);
            CategoryViewModel categoryViewModel=new CategoryViewModel(application);
            categoryViewModel.init(category);
            arrayList.add(categoryViewModel);
        }


        arrayListMutableLiveData.setValue(arrayList);

        return arrayListMutableLiveData;
    }

    private void refreshCategory(){

        executor.execute(() -> {
            String lastRefresh=getMaxRefreshTime(new Date()).toString();
            boolean sliderExists =(!(categoryDao.hasCategory(lastRefresh)).isEmpty());
            Log.e(TAG,"sliderExist: "+sliderExists);
            Log.e(TAG,"lastrefresh: "+lastRefresh);
            Log.e(TAG,"hasSlider: "+categoryDao.hasCategory(lastRefresh).toString());
            // If user have to be updated
            if (!sliderExists) {
                Log.e(TAG,"in if");
                apiInterface.getCategory().enqueue(new Callback<List<CategoryItem>>() {
                    @Override
                    public void onResponse(Call<List<CategoryItem>> call, Response<List<CategoryItem>> response) {

                        executor.execute(() -> {
                            List<CategoryItem> categories=response.body();
                            for (int i=0;i<categories.size();i++){
                                categories.get(i).setLastRefresh(new Date());
                                categoryDao.saveCategory(categories.get(i));
                            }

                        });
                    }

                    @Override
                    public void onFailure(Call<List<CategoryItem>> call, Throwable t) {

                        Log.e(TAG,"onFailure "+t.toString());
                    }
                });
            }

        });
    }

    private Date getMaxRefreshTime(Date currentDate){
        Calendar cal = Calendar.getInstance();
        cal.setTime(currentDate);
        cal.add(Calendar.MINUTE, -FRESH_TIMEOUT_IN_MINUTES);
        return cal.getTime();
    }
   }

Это макет xml элемента recyclerView:

 <?xml version = "1.0" encoding = "utf-8"?>
<layout>
    <data class = "CategoryDataBinding">
        <variable
            name = "category"
            type = "com.struct.red.alltolearn.viewmodel.CategoryViewModel"/>
    </data>

    <android.support.v7.widget.CardView xmlns:android = "http://schemas.android.com/apk/res/android"
        xmlns:app = "http://schemas.android.com/apk/res-auto"
        android:layout_width = "200dp"
        android:layout_height = "150dp"
        app:cardCornerRadius = "15dp">

        <RelativeLayout
            android:layout_width = "match_parent"
            android:layout_height = "match_parent">

            <ImageView
                android:id = "@+id/imgItemCategory"
                android:layout_width = "match_parent"
                android:layout_height = "match_parent"
                android:scaleType = "centerCrop"
                app:imageUrl = "@{category.imagePath}" />

            <TextView
                android:id = "@+id/txtTitleItemCategory"
                android:layout_width = "wrap_content"
                android:layout_height = "wrap_content"
                android:layout_centerInParent = "true"
                android:text = "@{category.title}"
                android:textColor = "#FFFFFF"
                android:textSize = "20sp"
                android:textStyle = "bold" />
        </RelativeLayout>

    </android.support.v7.widget.CardView>
</layout>

Это класс CategoryDao:

@Dao

общедоступный интерфейс CategoryDao {

@Query("SELECT * FROM course_table")
LiveData<List<CategoryItem>> loadCategoryItem();


@Insert(onConflict = OnConflictStrategy.REPLACE)
void saveCategory(CategoryItem category);

@Query("SELECT * FROM category_table WHERE lastRefresh > Date(:lastRefreshMax)")
List<CategoryItem> hasCategory(String lastRefreshMax);

}

И, наконец, я наблюдаю MutableLiveData в своем Фрагменте:

    private void setupCategoryRecycler() {
    categoryViewModel = ViewModelProviders.of(this).get(CategoryViewModel.class);
    categoryViewModel.getAllCategories().observe(this, new Observer<ArrayList<CategoryViewModel>>() {
        @Override
        public void onChanged(@Nullable ArrayList<CategoryViewModel> categoryViewModels) {
            Log.e(TAG, "categoryitem: " + categoryViewModels.toString());
            categoryAdapter = new CategoryAdapter(getContext(), categoryViewModels);
            LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, true);
            linearLayoutManager.setReverseLayout(true);
            CategoryRecy.setLayoutManager(linearLayoutManager);
            CategoryRecy.setAdapter(categoryAdapter);
        }
    });
}
4
0
8 062
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ваш items=categoryDao.loadCategoryItem().getValue() не будет иметь никакого значения, если вы не вызовете для него наблюдение.

Друг мой, в CategoryRepository я установил для него значение: arrayListMutableLiveData.setValue(arrayList); Вы не понимаете мою проблему.

maryam 30.01.2019 08:50

Мне жаль, что я ошибся. Я изменил свой ответ.

Archie G. Quiñones 30.01.2019 09:45

Вы пытаетесь загрузить данные не из той таблицы course_table

@Query("SELECT * FROM course_table") LiveData> loadCategoryItem();

Должно быть category_table

Ответ принят как подходящий

Твоя проблема здесь, верно?

public MutableLiveData<ArrayList<CategoryViewModel>>  getCategory(){
    ...
    items=categoryDao.loadCategoryItem().getValue(); // returns null
    ...
}

Это связано с тем, что ваш метод categoryDao.loadCategoryItem() возвращает объект LiveData. Это означает, что вызов метода будет выполняться в фоновом потоке. Поэтому, когда вы вызываете метод getValue(), значение в этот момент еще не равно нулю.

Чтобы избежать этого, вы можете сделать две плохие вещи.

1. Вызовите loadCategoryItem() раньше, чтобы получить значения позже при вызове getValue();

Ваш класс репозитория

public class CategoryRepository {
Livedata<List<CategoryItem>> items; // moved here
...

public void init () { 
     items=categoryDao.loadCategoryItem(); 
}

public MutableLiveData<ArrayList<CategoryViewModel>>  getCategory(){

    ArrayList<CategoryViewModel> arrayList=new ArrayList<>();
    List<CategoryItem> currentList = items.getValue(); 
    for(int i=0;i<currentList.size();i++){
          ...  
    }
    arrayListMutableLiveData.setValue(arrayList);
    return arrayListMutableLiveData;
}
}

Ваш класс ViewModel

  public class CategoryViewModel extends AndroidViewModel {

    public void init(CategoryItem categoryItem){
        repository.init();                          // added
        this.title=categoryItem.getTitle();
        this.imagePath=categoryItem.getImagePath();
    }

Это может работать, но у нас есть 2 проблемы. Во-первых, все еще нет гарантии, что значения не будут нулевыми. Вторая проблема заключается в том, что вы не можете наблюдать за изменениями вашего предмета. Даже если вы возвращаете объект arrayListMutableLiveData, который является livedata, вы устанавливаете его значение вручную один раз, и его значение не изменится, если вы снова не вызовете getCategory().

2. Второй хак - синхронная загрузка элементов категории

    public interface CategoryDao {

    @Query("SELECT * FROM category_table") LiveData<List<CategoryItem>>loadCategoryItem();

    @Query("SELECT * FROM category_table") List<CategoryItem> loadCategoryItemsSync();

В этом случае ваши методы getAllCategories() и getCategory() также должны работать синхронно.

Что-то вроде этого

public void  getCategory(Listener listener){
executor.execute(() -> {
    ArrayList<CategoryViewModel> arrayList=new ArrayList<>();
    List<CategoryItem> currentList = items.getValue(); 
    for(int i=0;i<currentList.size();i++){
          ...  
    }
    arrayListMutableLiveData.setValue(arrayList);
    listener.onItemsLoaded(arrayListMutableLiveData);
}
}

В этом случае у нас также есть вторая проблема -> вы не можете наблюдать за изменениями вашего элемента.

Я написал это, чтобы лучше прояснить проблему. *

Настоящая проблема заключается в том, что вы пытаетесь использовать CategoryViewModel для привязки данных.

Пожалуйста, используйте вместо этого CategoryItem

Я предлагаю удалить эти две строки из viewModel

private String title;
private String imagePath;

Попробуйте решить вашу проблему без разбора данных из списка в ArrayList.

public LiveData<List<CategoryItem>> getAllCategories(){
        if (items == null) {
            items = categoryDao.loadCategoryItem()
        }
        return items;
    }

затем попробуйте использовать CategoryItem как объект данных

<data class = "CategoryDataBinding">
    <variable
        name = "category"
        type = "com.struct.red.alltolearn.///.CategoryItem "/>
</data>

и попробуйте изменить свой адаптер, чтобы сделать это возможным

categoryViewModel = ViewModelProviders.of(this).get(CategoryViewModel.class);
categoryViewModel.getAllCategories().observe(this, new Observer<List<CategoryItem >>() {
        @Override
        public void onChanged(@Nullable List<CategoryItem > categoryItems) {
            categoryAdapter = new CategoryAdapter(getContext(), categoryItems);
...
 

Большое спасибо. Я сам пробовал последнее решение. Но у меня была небольшая ошибка, и я получаю ошибку NullPointException. Как указал @Kishore Jethava, я пытаюсь загрузить данные из неправильной таблицы course_table, и это должен быть category_table.

maryam 06.02.2019 06:40

В моем ответе также изменено имя таблицы. Рад помочь ))

snersesyan 06.02.2019 12:07

Может быть, вы можете использовать трансформацию?

//this is returned to the observer in setupCategoryRecycler()
return Transformations.switchMap(repository.getCategory()) { result -> 
    //do any other stuff you need here           
    allCategories.setValue(result)
}

Преобразование можно использовать для преобразования одних liveData в другие. Проверить: https://developer.android.com/topic/libraries/architecture/livedata#transform_livedata

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