Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get value of LiveData from repository that don't access to lifeCycleOwner for observing it?

I have used of MVVM and ROOM and databindig in my app.According Guide to app architecture ,I want to cash data using room.In the xml layout of RecyclerView item, I use CategoryViewModel variable.I get list of categories from Room database withLiveData type. I want to change LiveData<list<CategoryItem>> type to MutableLiveData<ArrayList<CategoryViewModel>> type. Because finally my adapter consume ArrayList<CategoryViewModel> data type.How to get value of LiveData? When I call getValue() method, returns null. this is CategoryItem model:

    @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;
    }

}

this is CategoryViewModel class:

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

This is CategoryRepository class:

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

This is xml layout of item of 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>

This is CategoryDao class:

@Dao

public interface 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);

}

And finally I observe MutableLiveData in my Fragment:

    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);
        }
    });
}
like image 699
maryam Avatar asked Dec 03 '22 18:12

maryam


1 Answers

Your problem is here, right?

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

This is because your categoryDao.loadCategoryItem() method returns LiveData object. It means that method call will be executed in background thread. So when you call getValue() method the value yet null that moment.

To escape from this you can do two bad things.

1. Call loadCategoryItem() earlier, to have values later when calling getValue();

Your Repository class

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

Your ViewModel class

  public class CategoryViewModel extends AndroidViewModel {

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

This can work but we have 2 problems. First is that still there is no guarantee that values will not be null. Second problem is that you cannot observe your item changes. Even tho you are returning arrayListMutableLiveData object, which is livedata, you are setting its value manually once, and its value will not be changed unless you call getCategory() again.

2. Second hack is load category items synchronously

    public interface CategoryDao {

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

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

In this case your getAllCategories () and getCategory() methods also should work synchronously.

Something like this

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

In this case also we have the second problem -> you cannot observe your item changes.

I wrote this to better clarify the problem. *

The real problem is that you trying use CategoryViewModel for data binding.

Please use CategoryItem instead

I suggest to remove this two rows from viewModel

private String title;
private String imagePath;

Try to solve your problem without parsing data from List to ArrayList.

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

then try to use CategoryItem as data object

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

and try to change your adapter to make possible doing this

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);
...
 
like image 170
snersesyan Avatar answered Dec 06 '22 10:12

snersesyan