J'ai utilisé MVVM
et ROOM
et databindig
dans mon application.Selon le guide de l'architecture de l'application , je souhaite encaisser des données en utilisant room.Dans la mise en page xml
de l'élément RecyclerView
, j'utilise la variable CategoryViewModel
liste des catégories de la base de données de la Room
avec LiveData
type. Je souhaite changer le type LiveData<list<CategoryItem>>
type MutableLiveData<ArrayList<CategoryViewModel>>
. Parce que finalement mon adaptateur consomme le type de données ArrayList<CategoryViewModel>
Comment obtenir la valeur de LiveData
? Lorsque j'appelle la méthode getValue()
, renvoie null. c'est le modèle CategoryItem
:
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); } }); }
il s'agit de la classe CategoryViewModel
:
@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);
Il s'agit de la classe CategoryRepository
:
@Dao
Il s'agit de la mise en page xml
de l'élément de 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>
C'est la classe CategoryDao
:
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(); } }
interface publique CategoryDao {
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; } }
}
Et enfin j'observe MutableLiveData
dans mon Fragment:
@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; } }
4 Réponses :
Vos items=categoryDao.loadCategoryItem().getValue()
n'auront aucune valeur à moins que vous items=categoryDao.loadCategoryItem().getValue()
observer dessus.
Mon ami, dans le CategoryRepository
je lui attribue une valeur: arrayListMutableLiveData.setValue (arrayList); Vous ne comprenez pas mon problème.
Je suis désolé de m'être trompé. J'ai changé ma réponse.
Vous essayez de charger des données à partir de la mauvaise table course_table
@Query ("SELECT * FROM course_table") LiveData> loadCategoryItem ();
Cela devrait être category_table
Votre problème est ici, non?
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); ...
Cela est dû au fait que votre méthode categoryDao.loadCategoryItem () renvoie un objet LiveData. Cela signifie que l'appel de méthode sera exécuté dans le thread d'arrière-plan. Ainsi, lorsque vous appelez la méthode getValue (), la valeur est encore nulle à ce moment-là.
Pour échapper à cela, vous pouvez faire deux mauvaises choses.
1. Appelez loadCategoryItem () plus tôt, pour avoir des valeurs plus tard lors de l'appel de getValue ();
Votre classe Repository
<data class="CategoryDataBinding"> <variable name="category" type="com.struct.red.alltolearn.///.CategoryItem "/> </data>
Votre classe ViewModel
public LiveData<List<CategoryItem>> getAllCategories(){ if (items == null) { items = categoryDao.loadCategoryItem() } return items; }
Cela peut fonctionner mais nous avons 2 problèmes. Tout d'abord, rien ne garantit que les valeurs ne seront pas nulles. Le deuxième problème est que vous ne pouvez pas observer les modifications apportées à votre article. Même si vous retournez un objet arrayListMutableLiveData, qui est vécu, vous définissez sa valeur manuellement une fois, et sa valeur ne sera pas modifiée à moins que vous n'appeliez à nouveau getCategory ().
2. Le deuxième hack consiste à charger les éléments de catégorie de manière synchrone
private String title; private String imagePath;
Dans ce cas, vos méthodes getAllCategories () et getCategory () devraient également fonctionner de manière synchrone.
Quelque chose comme ça
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); } }
Dans ce cas également, nous avons le deuxième problème -> vous ne pouvez pas observer les changements de votre article.
J'ai écrit ceci pour mieux clarifier le problème. *
Le vrai problème est que vous essayez d'utiliser CategoryViewModel pour la liaison de données.
Veuillez utiliser CategoryItem à la place
Je suggère de supprimer ces deux lignes de viewModel
public interface CategoryDao { @Query("SELECT * FROM category_table") LiveData<List<CategoryItem>>loadCategoryItem(); @Query("SELECT * FROM category_table") List<CategoryItem> loadCategoryItemsSync();
Essayez de résoudre votre problème sans analyser les données de List à ArrayList.
public class CategoryViewModel extends AndroidViewModel { public void init(CategoryItem categoryItem){ repository.init(); // added this.title=categoryItem.getTitle(); this.imagePath=categoryItem.getImagePath(); }
puis essayez d'utiliser CategoryItem comme objet de données
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; } }
et essayez de changer votre adaptateur pour rendre cela possible
public MutableLiveData<ArrayList<CategoryViewModel>> getCategory(){ ... items=categoryDao.loadCategoryItem().getValue(); // returns null ... }
Merci beaucoup, j'ai moi-même essayé la dernière solution, mais j'ai eu une petite erreur et je reçois une erreur NullPointException
. Comme @Kishore Jethava l'a souligné, j'essaie de charger des données à partir de la mauvaise table course_table
et cela devrait être category_table
.
Le nom de la table a également changé dans ma réponse. Heureux d'aider ))
Peut-être pouvez-vous utiliser une transformation?
//this is returned to the observer in setupCategoryRecycler() return Transformations.switchMap(repository.getCategory()) { result -> //do any other stuff you need here allCategories.setValue(result) }
Une transformation peut être utilisée pour convertir un liveData en un autre. Vérifiez: https://developer.android.com/topic/libraries/architecture/livedata#transform_livedata