Я использовал 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);
}
});
}
Ваш items=categoryDao.loadCategoryItem().getValue() не будет иметь никакого значения, если вы не вызовете для него наблюдение.
Мне жаль, что я ошибся. Я изменил свой ответ.
Вы пытаетесь загрузить данные не из той таблицы 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.
В моем ответе также изменено имя таблицы. Рад помочь ))
Может быть, вы можете использовать трансформацию?
//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
Друг мой, в
CategoryRepositoryя установил для него значение: arrayListMutableLiveData.setValue(arrayList); Вы не понимаете мою проблему.