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);
}
});
}
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);
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With