MVVM. Set data in ViewModel - android

It is necessary to put the data in LiveData to send to the callback. In this method:
public void setData(List<Data> data) {
this.currentData.setValue((Data) data);
}
according to the documentation setValue is called by MutableLiveData, I replaced the LiveData in ViewModel with MutableLiveData, but anyway, when I open the required fragment, the application crashes
java.lang.ClassCastException: androidx.room.RoomTrackingLiveData cannot be cast to androidx.lifecycle.MutableLiveData
at avocado.droid.ptitsami.room.DataViewModel.<init>(DataViewModel.java:24)
at avocado.droid.ptitsami.room.DataViewModel$ModelFactory.create(DataViewModel.java:54)
at androidx.lifecycle.ViewModelProvider$FactoryWrapper.create(ViewModelProvider.java:268)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:179)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:147)
at avocado.droid.ptitsami.fragment.DataFragment.onCreateView(DataFragment.java:57)
How to fix it?
ViewModel
public class DataViewModel extends AndroidViewModel {
MutableLiveData<Data> currentData;
DataRepository repository;
public DataViewModel(#NonNull Application application, final int verseId) {
super(application);
int verseId1 = verseId;
repository = new DataRepository(application);
currentData = (MutableLiveData<Data>) repository.getById(verseId);
}
public LiveData<Data> getById() {
return currentData;
}
public void setData(List<Data> data) {
this.currentData.setValue((Data) data);
}
public static class ModelFactory extends ViewModelProvider.NewInstanceFactory {
#NonNull
private final Application application;
private final int dataId;
private final DataRepository repository;
public ModelFactory(#NonNull Application application, int id) {
super();
this.application = application;
this.dataId = id;
repository = new DataRepository(application);
}
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
if (modelClass == DataViewModel.class) {
return (T) new DataViewModel(application, dataId);
}
return null;
}
}
Fragment
public class DataFragment extends Fragment {
private int dataId;
private static final String KEY_DATA_ID = "KEY_DATA_ID";
public TextView tvTitle;
public DataFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootViewRead = inflater.inflate(R.layout.fragment_data, container, false);
Toolbar toolbar = rootViewRead.findViewById(R.id.toolbar);
AppCompatActivity activity = (AppCompatActivity) getActivity();
if (activity != null) {
activity.setSupportActionBar(toolbar);
}
setHasOptionsMenu(true);
tvTitle = (TextView) rootViewRead.findViewById(R.id.text);
DataViewModel.ModelFactory factory = new DataViewModel.ModelFactory(
getActivity().getApplication(), getArguments().getInt(KEY_DATA_ID));
final DataViewModel model = ViewModelProviders.of(this, factory)
.get(DataViewModel.class);
model.getById().observe(this, new Observer<Data>() {
#Override
public void onChanged(Data data) {
model.setData((List<Data>) data);
}
});
return rootViewRead;
}
public static DataFragment forData(int dataId) {
DataFragment fragment = new DataFragment();
Bundle args = new Bundle();
args.putInt(KEY_DATA_ID, dataId);
fragment.setArguments(args);
return fragment;
}
repository
public class DataRepository {
private DatabaseCopier db;
DataRepository(Application application) {
db = DatabaseCopier.getInstance(application);
}
LiveData<Data> getById(int id) {
return db.getDatabase().dataDao().getById(id);
}

Try adding source to currentData and change currentData from MutableLivaData to MediatorLiveData.
LiveData<Data> data = repository.getById(verseId);
currentData.addSource(data, observer);

There are multiple things odd here..
In your ViewModel you have a getter and a setter for the LiveData:
public LiveData<Data> getById() {
return currentData;
}
public void setData(List<Data> data) {
this.currentData.setValue((Data) data);
}
And in your observer of the LiveData you call the setter of the LiveData??
model.getById().observe(this, new Observer<Data>() {
#Override
public void onChanged(Data data) {
model.setData((List<Data>) data);
}
});
That does not make sense! When the observe method is called, the model already has this data set! So you do not need to call setData. Without the main issue, this will create an endless loop!
Now to your main Issue:
androidx.room.RoomTrackingLiveData cannot be cast to androidx.lifecycle.MutableLiveData
Room data can only be loaded to LiveData. The reason is because Room always keeps a link to it and automatically updates it, once the content of the database changed! But therefore YOU cannot change the content of the LiveData!
So please explain why
It is necessary to put the data in LiveData to send to the callback.
You have to change:
//MutableLiveData<Data> currentData;
LiveData<Data> currentData;
and
model.getById().observe(this, new Observer<Data>() {
#Override
public void onChanged(Data data) {
//model.setData((List<Data>) data); <- this creates an endless loop
// do here what you want to do with the content of the data
// if you need to pass it to the viewmodel, do it, but do not call `setValue`
}
});
For specific usecases a MediatorLiveData might be reasonable, but for this you would have to explain in detail why above doesn't do the job for you.

Related

Flowable<List<T>> changes to empty List on Fragment switching back

I'm trying to populate a RecyclerView by following the nexts steps:
Download data from server and getting a SoapObject (yah, old server)
Transform the data to Flowable<List<MyItem>> (in Repository) in order to subscribe to it (in ViewModel) through LiveDataStreams.fromPublisher(flowableObj)
Set the resulted list into a MediatorLiveData object.
Observe the MediatorLiveData object in the Fragment's onViewCreated method.
So, when I click an on item from the list, it navigates (through Navigation Component) to a new Fragment, but, once I go back through the phone's back button, the list becomes empty and consequently the observer is notified and updates the list (shows nothing cause is empty).
I don't know why, the list gets empty and therefore the RecyclerView. Any help? -- code below:
Generic Fragment
public abstract class ListFragment<T> extends Fragment {
protected ListViewModel mViewModel;
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mNavController = Navigation.findNavController(getDataBindingObject().getRoot());
showItemsList();
setUpFilters();
}
protected void showItemsList() {
mViewModel.getList().observe(getViewLifecycleOwner(), listObserver);
mViewModel.getItemSelected().observe(getViewLifecycleOwner(), onListItemSelected());
}
protected final Observer<List<T>> listObserver = new Observer<List<T>>() {
#Override
public void onChanged(List<T> list) {
mViewModel.setListAdapter(list);
}
};
MyItem Fragment's code:
#Override
public View onCreateView(#NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mViewModel = new ViewModelProvider(this, new ViewModelFactory()).get(MyItemViewModel.class);
mDataBinding = ...
mDataBinding.setLifecycleOwner(this); //geViewLifecycleOwner()
mDataBinding.setViewModel(mViewModel);
return mDataBinding.getRoot();
}
#Override
public void onViewCreated(#NonNull #NotNull View view, #Nullable #org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mNavController = NavHostFragment.findNavController(this);
}
Generic ViewModel:
public abstract class ListViewModel<T, K> extends MyViewModel {
protected ListRepository<T> mRepository;
protected MediatorLiveData<List<T>> list;
protected MutableLiveData<K> mListAdapter;
public ListViewModel() {
super();
}
public LiveData<List<T>> getList() {
if (list == null) {
LiveData<List<T>> lD = LiveDataReactiveStreams.fromPublisher(mRepository.getList());
list = new MediatorLiveData<>();
list.addSource(lD, li -> {
this.list.postValue(li);
list.remove(lD); //removing this line does not work either
});
}
return list;
}
public LiveData<K> getListAdapter() {
if (mListAdapter == null)
mListAdapter = new MutableLiveData<>();
return mListAdapter;
}
public abstract void setListAdapter(List<T> list);
MyItemViewModel:
public class MyItemViewModel extends ListViewModel<MyItem, MyItemAdapter> {
protected MyItemRepository mHistoryRepository;
public MyItemViewModel(MyItemRepository repository) {
super();
mRepository = repository;
}
#Override
public void setListAdapter(List<MyItem> list) {
if (getListAdapter().getValue() == null) {
MyItemAdapter adapter = new MyItemAdapter(list);
adapter.setListener(onListItemSelectedListener);
mListAdapter.setValue(adapter);
} else
mListAdapter.getValue().updateList(list);
}
Generic Repository
public abstract class ListRepository<T> {
protected Flowable<List<T>> list;
protected abstract Flowable<List<T>> getItemsList(int orderByField);
public Flowable<List<T>> getList() {
if (list == null)
list = getItemsList();
return list;
}
MyItemRepository:
public class MyItemRepository extends ListRepository<MyItem> {
protected static volatile MyItemRepository instance;
protected final MyItemLocalDS mLocalDataSource;
protected final MyItemRemoteDS mRemoteDataSource;
public MyItemRepository(MyItemRemoteDS remoteDataSource,
MyItemLocalDS localDataSource) {
this.mRemoteDataSource = remoteDataSource;
this.mLocalDataSource = localDataSource;
}
public static MyItemRepository getInstance(MyRemoteDS remoteDataSource,
MyLocalDS localDataSource) {
if (instance == null)
instance = new MyItemRepository(remoteDataSource, localDataSource);
return instance;
}
#Override
protected Flowable<List<MyItem>> getItemsList() {
list = mRemoteDataSource.download(...)
.map(soapObject -> parseItemsList(soapObject))
.map(wsResult -> transformItemsList(wsResult));
return list.subscribeOn(Schedulers.io());
}

Livedata doesn't update data in fragment correctly

I have tablayout and 2 fragments in separate tabs.
Fragment A have an overridden method that returns data when Activity (started from Fragment A) return data on it's destroy:
public class Fragment A extends Fragment {
...
#Override
public void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
if(resultCode != RESULT_CANCELED) {
assert data != null;
String accountTransaction = data.getStringExtra("Account");
String categoryTransaction = data.getStringExtra("Category");
Double getDouble = data.getDoubleExtra("Value", 0);
TransactionNewItem item = new TransactionNewItem(String.valueOf(getDouble),accountTransaction,categoryTransaction);
model.setSelected(item);
}
super.onActivityResult(requestCode, resultCode, data);
}
}
In this same method I use a call to ViewModel that should observe TransactionNewItem object :
public class TransactionViewModel extends ViewModel {
private final MutableLiveData<TransactionNewItem> selected = new MutableLiveData<>();
public void setSelected (TransactionNewItem item){
selected.setValue(item);
}
public LiveData<TransactionNewItem> getSelected() {
return selected;
}
}
After data that returns from Activity, with new values it creates a new POJO and sends data stored in this POJO to Fragment B, where based on data from Fragment A new item for RecyclerView will be created
public class Fragment B extends Fragment {
...
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
initObserve();
initRecView();
super.onViewCreated(view, savedInstanceState);
}
//init RecyclerView
private void initRecView(){
binding.transactionView.setLayoutManager(new LinearLayoutManager(requireContext()));
adapter = new TransactionRecViewAdapter(listContentArr);
adapter.setListContent(listContentArr);
binding.transactionView.setAdapter(adapter);
}
//observe data from Fragment A and create object based on it
private void initObserve(){
model = new ViewModelProvider(requireActivity()).get(TransactionViewModel.class);
model.getSelected().observe(getViewLifecycleOwner(), item -> {
TransactionItem newAccountItem = new TransactionItem() ;
newAccountItem.setTransactionValue(item.getTransactionValue());
newAccountItem.setTransactionCategory(item.getTransactionCategory());
newAccountItem.setTransactionAccount(item.getTransactionAccount());
listContentArr.add(0,newAccountItem);
adapter.notifyDataSetChanged();
});
}
}
However, it will add only 1 item into RecyclerView and will replace it with when Activity returns new data. This happens if the user didn’t switch to Fragment B at least one time, because onViewCreated isn't called till the user switches to Fragment B.
How to make ViewModel observe data from Fragment A, and create new TransActionItem in Fragment B Recyclerview every time the Activity returns new data if the user never switched to Fragment B before?
Thanks in advance
EDIT: I managed to do what I want in a next way:
STEP 1. I change ViewModel from POJO to Arraylist with POJO - ArrayList:
public class TransactionViewModel extends ViewModel {
private final MutableLiveData<ArrayList<TransactionItem>> selected = new MutableLiveData<>();
public void setSelected (ArrayList<TransactionItem> arrayList){
selected.setValue(arrayList);
}
public LiveData<ArrayList<TransactionItem>> getSelected() {
return selected;
}
}
STEP 2. In Fragment A I added ArrayList with the same POJO type, in onActivityResult. I changed the code now object will be created and added after Activity will return a result, not in Fragment B:
public class Fragment A extends Fragment {
ArrayList<TransactionItem> listTransactions = new ArrayList<>();
…
#Override
public void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
if(resultCode != RESULT_CANCELED) {
...
//Create TransactionItem and use setSelected method from ViewModel
TransactionItem item = new TransactionItem(accountTransaction,
String.valueOf(getDouble),categoryAccount),transactionID);
listTransactions.add(0,item);
model.setSelected(listTransactions);
}
super.onActivityResult(requestCode, resultCode, data);
}
}
Must notice that I added transactionID into the TransactionItem constructor, and that's why we need it.
STEP 3 I created next TransactionDiffUtilCallback class that extends DiffUtil.Callback :
public class TransactionDiffUtilCallback extends `DiffUtil.Callback` {
public TransactionDiffUtilCallback(ArrayList<TransactionItem> oldList, ArrayList<TransactionItem> newList) {
this.oldList = oldList;
this.newList = newList;
}
ArrayList<TransactionItem> newList;
#Override
public int getOldListSize() {
return oldList.size();
}
#Override
public int getNewListSize() {
return newList.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).getItemIID() == newList.get(newItemPosition).getItemIID();
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));
}
#Nullable
#Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return super.getChangePayload(oldItemPosition, newItemPosition);
}
}
I used getItemIID() from POJO to notify that the new item in ArrayList is different.
STEP 4 In recyclerview adapter I created updateItemList(list):
public void updateItemList(ArrayList<TransactionItem> items){
final TransactionDiffUtilCallback diffCallback = new TransactionDiffUtilCallback(this.pad_list, items);
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
this.pad_list.clear();
this.pad_list.addAll(items);
diffResult.dispatchUpdatesTo(this);
}
So this method uses DiffUtil.CallBack to compare items in ArrayList from Fragment A and ArrayList in Fragment B, then notify adapter that ArrayList from Fragment A is different, and this data should be put in ArrayList in Fragment B, and view should be updated.
STEP 5 In Fragment B OnViewCreated() code was rewritten to observe Arraylist forever :
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
initRecView();
model = new ViewModelProvider(requireActivity()).get(TransactionViewModel.class);
Observer<ArrayList<TransactionItem>> observer = (Observer<ArrayList<TransactionItem>>) this::initObserve;
model.getSelected().observeForever(observer);
super.onViewCreated(view, savedInstanceState);
}
And initObserve() now have next code:
private void initObserve(ArrayList<TransactionItem> list){
adapter.updateItemList(list);
}
For now, this solution is working, the user doesn’t need to switch to Fragment B to keep transaction recording. I will resume test this solution.

Only update RecyclerView when a change in the dataset occurs

I have a dataset of restaurants and a recycler view where they are displayed. Depending on a few options, they should or not be visible: opening time, food type, etc.
Right now every time the activity with the recycler view is opened I run adapter.updateDataset() which internally goes through the whole dataset, creates a subset based on all the possible filters, and then does notifyDataSetChanged().
How can I make it so that I only need to run adapter.updateDataset() when a change actually occurs? Since these changes occur in a different context from the RecyclerView activity, I can't just call the function there. What alternative do I have, to improve performance?
You should probably use a list of LiveData objects either from room or a network resource and bind it your viewmodel. Then you will be observing the changes in your fragment/activity. When the change occurs, update the adapters data list and do not forget to use DiffUtil in order to update only changed items. A good example is in google sample codes on room database usage.
In your Room Dao query it should be like:
#Query("SELECT * FROM products")
LiveData<List<ProductEntity>> loadProducts();
Then in your viewmodel:
public class ProductListViewModel extends AndroidViewModel {
// MediatorLiveData can observe other LiveData objects and react on their emissions.
private final MediatorLiveData<List<ProductEntity>> observableProducts;
public ProductListViewModel(#NonNull Application application) {
super(application);
observableProducts = new MediatorLiveData<>();
// set by default null, until we get data from the database.
observableProducts.setValue(null);
LiveData<List<ProductEntity>> products = ((YourBaseApp) application).getRepository()
.loadProducts();
observableProducts.addSource(products, observableProducts::setValue);
}
public static class Factory extends ViewModelProvider.NewInstanceFactory {
#NonNull
private final Application mApplication;
public Factory(#NonNull Application application) {
mApplication = application;
}
#Override
public <T extends ViewModel> T create(Class<T> modelClass) {
//noinspection unchecked
return (T) new ProductListViewModel(mApplication);
}
}
public LiveData<List<ProductEntity>> getProductList() {
return observableProducts;
}
}
Then in your activity/fragment onCreate you may call such a sample function and start observing your data:
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
// Binding is of type ProductListLayoutBinding
// you need to declare it on tope of your fragment
binding = DataBindingUtil.inflate(inflater, R.layout.product_list_layout, container, false);
// your other stuff if needed..
productAdapter = new ProductAdapter(/*...Your parameters if any*/);
binding.yourRecylerViewId.setAdapter(productAdapter);
return binding.getRoot();
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//...
//...
// do your normal stuff above
ProductListViewModel.Factory factory = new ProductListViewModel.Factory(
YourBaseApp.getInstance());
final ProductListViewModel viewModel =
new ViewModelProvider(this, factory).get(ProductListViewModel.class);
subscribeUi(viewModel);
}
private void subscribeUi(ProductListViewModel viewModel) {
// Update the list when the data changes
viewModel.getProductList().observe(this, new Observer<List<ProductEntity>>() {
#Override
public void onChanged(#Nullable List<ProductEntity> myProducts) {
if (myProducts != null) {
if (myProducts.size() == 0) {
binding.setIsLoading(true);
} else {
binding.setIsLoading(false);
productAdapter.setProductList(myProducts);
}
} else {
binding.setIsLoading(true);
}
binding.executePendingBindings();
}
});
}
Finally on your adapter:
public void setProductList(final List<? extends Product> inProductList) {
if (productList == null) {
productList = inproductList;
notifyItemRangeInserted(0, productList.size());
} else {
DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
#Override
public int getOldListSize() {
return productList.size();
}
#Override
public int getNewListSize() {
return inproductList.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return productList.get(oldItemPosition).getId() == inproductList.get(newItemPosition).getId();
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
productList newProduct = inproductList.get(newItemPosition);
productList oldProduct = productList.get(oldItemPosition);
return newProduct.getId() == oldProduct.getId()
&& Objects.equals(newProduct.getDefinition(), oldProduct.getDefinition())
//... compare other properties
//...
;
}
});
productList = inproductList;
result.dispatchUpdatesTo(this);
}
}
Hope , this helps.

Passing variable to paging library class

I am creating an app with Android Paging Library. I'm using retrofit with it.
Retrofit code is in ItemDataSource and there i can't pass variable to it. I have some variable coming with intent. How can i set my variable in Retrofit Post method.
ItemDataSource
public class ItemDataSource extends PageKeyedDataSource<Integer, Item> {
//we will start from the first page which is 1
private static final int PAGE_NUMBER = 1;
//this will be called once to load the initial data
String table
ItemDataSource(String table){
this.table = table;
}
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull
final LoadInitialCallback<Integer, Item> callback) {
RetrofitClient.getInstance()
// I want to pass table variable here.
.getApi().getAnswers("table","","","",PAGE_NUMBER,"")
.enqueue(new Callback<StackApiResponse>() {
#Override
public void onResponse(Call<StackApiResponse> call,
Response<StackApiResponse> response) {
if (response.body() != null) {
callback.onResult(response.body().images, null,
PAGE_NUMBER + 1);
}
}
#Override
public void onFailure(Call<StackApiResponse> call,
Throwable
t) {
}
});
}
}
Main Activity
public class Detail extends AppCompatActivity {
ArrayList<Item> items;
Api api;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
// I'm getting intent here.
final RecyclerView recyclerView =
findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
ItemViewModel itemViewModel =
ViewModelProviders.of(this).get(ItemViewModel.class);
//creating the Adapter
//observing the itemPagedList from view model
itemViewModel.itemPagedList.observe(this, new
Observer<PagedList<Item>>() {
#Override
public void onChanged(#Nullable PagedList<Item> items) {
//in case of any changes
//submitting the items to adapter
adapter.submitList(items);
}
});
//setting the adapter
recyclerView.setAdapter(adapter);
}
}
Item View Model
public class ItemViewModel extends ViewModel {
//creating livedata for PagedList and PagedKeyedDataSource
LiveData<PagedList<Item>> itemPagedList;
LiveData<PageKeyedDataSource<Integer, Item>> liveDataSource;
//constructor
public ItemViewModel() {
//getting our data source factory
ItemDataSourceFactory itemDataSourceFactory = new
ItemDataSourceFactory();
//getting the live data source from data source factory
liveDataSource = itemDataSourceFactory.getItemLiveDataSource();
//Getting PagedList config
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder())
.setEnablePlaceholders(false)
.setPageSize(10).build();
//Building the paged list
itemPagedList = (new LivePagedListBuilder(itemDataSourceFactory,
pagedListConfig))
.build();
}
}
BTW i'm following this https://www.simplifiedcoding.net/android-paging-library-tutorial/
for doing this, you have to create a constructor in ItemDataSource class which you did, a new object of that class is created in ItemDataSourceFactory so you have to create a constructor there to get the value and pass it to ItemDataSource. and you have to pass the value to ItemDataSourceFactory from your viewModel. this is how it should look (based on the link that you posted)
public class ItemViewModel extends ViewModel {
LiveData<PagedList<Item>> itemPagedList;
LiveData<PageKeyedDataSource<Integer, Item>> liveDataSource;
public ItemViewModel(String table) {
ItemDataSourceFactory itemDataSourceFactory = new ItemDataSourceFactory(table);
liveDataSource = itemDataSourceFactory.getItemLiveDataSource();
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder())
.setEnablePlaceholders(false)
.setPageSize(ItemDataSource.PAGE_SIZE).build();
itemPagedList = (new LivePagedListBuilder(itemDataSourceFactory,pagedListConfig))
.build();
}}
then in your activity/fragment you should pass the value like this:
ItemViewModel itemViewModel = ViewModelProviders.of(this, new ViewModelProvider.Factory() {
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
return (T)new ItemViewModel ("your table name");
}
}).get(ItemViewModel.class);

Android Live data observer exception

I am trying to implement the new android architecture components and have used live data in the fragment and view model but when I add an observer to the live data the app crashes throwing this exception.
Process: com.nrs.nsnik.architecturecomponents, PID: 3071
java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.nrs.nsnik.architecturecomponents/com.nrs.nsnik.architecturec
omponents.view.MainActivity}: java.lang.ClassCastException: android.arch.lifecycle.LiveData_LifecycleBoundObserver_LifecycleAdapter cannot be cast to android.arch.lifecycle.GeneratedAdapter
.
.
.
.
Caused by: java.lang.ClassCastException: android.arch.lifecycle.LiveData_LifecycleBoundObserver_LifecycleAdapter cannot be cast to android.arch.lifecycle.GeneratedAdapter
List Fragment :
public class ListFragment extends Fragment {
#BindView(R.id.listFragmentRecyclerView)
RecyclerView mRecyclerView;
#BindView(R.id.listFragmentAddItem)
FloatingActionButton mFloatingActionButton;
private Unbinder mUnbinder;
private CompositeDisposable mCompositeDisposable;
private ListViewModel mListViewModel;
private List<NoteEntity> mNoteEntityList;
private ListAdapter mListAdapter;
private NoteDatabase mNoteDatabase;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_list, container, false);
mUnbinder = ButterKnife.bind(this, v);
mListViewModel = ViewModelProviders.of(this).get(ListViewModel.class);
mNoteDatabase = ((MyApplication)getActivity().getApplication()).getNoteDatabaseInstance();
initialize();
listeners();
return v;
}
private void initialize() {
mCompositeDisposable = new CompositeDisposable();
mNoteEntityList = new ArrayList<>();
mListAdapter = new ListAdapter(getActivity(), mNoteEntityList);
mListViewModel.getNoteList().observe(this, noteEntityList -> {
mListAdapter.swapList(noteEntityList);
mListAdapter.notifyDataSetChanged();
});
}
private void cleanUp() {
if (mUnbinder != null) {
mUnbinder.unbind();
}
if (mCompositeDisposable != null) {
mCompositeDisposable.dispose();
}
}
private void listeners() {
RxView.clicks(mFloatingActionButton).subscribe(o -> {
AlertDialog.Builder newNoteDialog = new AlertDialog.Builder(getActivity());
View v = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_add_note_dialog, null);
newNoteDialog.setView(v);
EditText editText = v.findViewById(R.id.addNoteEditText);
newNoteDialog.setNegativeButton(getActivity().getResources().getString(R.string.cancel), (dialogInterface, i) -> {
}).setPositiveButton(getActivity().getResources().getString(R.string.add), (dialogInterface, i) -> {
if (isValid(editText)) {
NoteEntity entity = new NoteEntity();
entity.setNote(editText.getText().toString());
entity.setDate(getCurrentDate());
mNoteDatabase.getNoteDao().insertNote(entity);
}
});
newNoteDialog.create().show();
});
}
private Date getCurrentDate() {
Date date = new Date(Calendar.getInstance().getTimeInMillis());
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
simpleDateFormat.format(date);
return date;
}
private boolean isValid(EditText editText) {
return !(editText.getText().toString().length() <= 0 || editText.getText().toString().isEmpty());
}
#Override
public void onDestroy() {
super.onDestroy();
cleanUp();
if (BuildConfig.DEBUG) {
RefWatcher refWatcher = MyApplication.getRefWatcher(getActivity());
refWatcher.watch(this);
}
}
}
ViewModel :
public class ListViewModel extends AndroidViewModel {
private LiveData<List<NoteEntity>> mNoteList;
private final NoteDatabase mNoteDatabase;
ListViewModel(Application application) {
super(application);
mNoteDatabase = ((MyApplication)application).getNoteDatabaseInstance();
mNoteList = mNoteDatabase.getNoteDao().getNoteList();
}
public LiveData<List<NoteEntity>> getNoteList() {
return mNoteList;
}
}
NoteDatabase :
#Database(entities = {NoteEntity.class}, version = 1)
public abstract class NoteDatabase extends RoomDatabase {
public abstract NoteDao getNoteDao();
}
App crashes if a add the obverse on the live data.
I am building a single instance of the database in my application class using "Room.databaseBuilder(....)" function and using it everywhere and my NoteEntity class has three fields one is id which is a primary key that auto-generates.
I had similar error, in my case was caused by this dependency in gradle.build file:
implementation "android.arch.lifecycle:common-java8:1.0.0-beta2"
The FirebaseUI has not yet updated android.arch.lifecycle to 1.0.0-beta2.
Use 1.0.0-beta1 instead of 1.0.0-beta2.
Wait until they update the lifecycle library.

Categories

Resources