【问题标题】:How to get value of LiveData from repository that don't access to lifeCycleOwner for observing it?如何从无法访问 lifeCycleOwner 的存储库中获取 LiveData 的价值以进行观察?
【发布时间】:2019-06-20 11:49:10
【问题描述】:

我在我的应用程序中使用了MVVMROOMdatabindig。根据 Guide to app architecture,我想使用房间兑现数据。在RecyclerView项目的xml布局中,我使用@ 987654327@ 变量。我从Room 数据库中使用LiveData 类型获取类别列表。我想将LiveData<list<CategoryItem>> 类型更改为MutableLiveData<ArrayList<CategoryViewModel>> 类型。因为最后我的适配器消耗了ArrayList<CategoryViewModel> 数据类型。如何获得LiveData 的值?当我调用 getValue() 方法时,返回 null。 这是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();
    }
   }

这是xmlrecyclerView项目的布局:

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

}

最后我在 Fragment 中观察到 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);
        }
    });
}

【问题讨论】:

    标签: android mvvm android-room android-viewmodel android-livedata


    【解决方案1】:

    你的问题就在这里,对吧?

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

    这是因为您的 categoryDao.loadCategoryItem() 方法返回 LiveData 对象。这意味着方法调用将在后台线程中执行。因此,当您调用 getValue() 方法时,该值仍为 null。

    要摆脱这种情况,你可以做两件坏事。

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

    这可行,但我们有两个问题。首先是仍然不能保证值不会为空。第二个问题是您无法观察到您的项目变化。即使您返回 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;
    

    尝试在不将数据从 List 解析到 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
    • 在我的回答中也更改了表名。乐于助人))
    【解决方案2】:

    也许你可以使用转换?

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

    【讨论】:

      【解决方案3】:

      您正在尝试从错误的表course_table加载数据

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

      应该是category_table

      【讨论】:

        【解决方案4】:

        您的items=categoryDao.loadCategoryItem().getValue() 将没有任何价值,除非您对其进行观察。

        【讨论】:

        • 我的朋友,在CategoryRepository 我为它设置了值:arrayListMutableLiveData.setValue(arrayList);你不明白我的问题。
        • 对不起,我弄错了。我改变了答案。
        猜你喜欢
        • 2023-03-03
        • 1970-01-01
        • 2020-03-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多