Android Live data observer exception - android

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.

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

Android Fragment LiveData observer is not triggered when update is done on a record data

I am trying to figure out why the LiveData observer for getAllGoals() does not trigger immediately in the fragment when I update a record. However, the observer is called only after switching to another fragment using the bottom tab navigation and then coming back to the original fragment.
The fragment in question:
MyGoalsFragment.java
public class MyGoalsFragment extends Fragment implements MyGoalsAdapter.MyGoalsCallback {
FragmentMyGoalsBinding myGoalsBinding;
private MyGoalsViewModel myGoalsViewModel;
MyGoalsAdapter myGoalsAdapter;
ConstraintSet smallConstraintSet = new ConstraintSet();
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
myGoalsViewModel = new ViewModelProvider(getActivity(), ViewModelProvider.AndroidViewModelFactory.getInstance(getActivity().getApplication())).get(MyGoalsViewModel.class);
myGoalsBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_my_goals, container, false);
myGoalsBinding.recyclerView2.setLayoutManager(new LinearLayoutManager(getActivity()));
DrawerLayout drawerLayout = (DrawerLayout) getActivity().findViewById(R.id.drawer_layout);
myGoalsBinding.menu.setOnClickListener(v -> {
drawerLayout.openDrawer(GravityCompat.START);
});
TransitionManager.beginDelayedTransition(myGoalsBinding.recyclerView2);
myGoalsAdapter = new MyGoalsAdapter();
myGoalsAdapter.setCallback(this);
myGoalsAdapter.setContext(getActivity());
myGoalsAdapter.setRecyclerView(myGoalsBinding.recyclerView2);
myGoalsBinding.recyclerView2.setAdapter(myGoalsAdapter);
myGoalsBinding.floatingActionButton.setOnClickListener(v -> {
startActivity(new Intent(getActivity(), CreateGoalActivity.class));
getActivity().finish();
});
enableSwipeToDeleteAndUndo();
myGoalsBinding.recyclerView2.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0 && myGoalsBinding.floatingActionButton.getVisibility() == View.VISIBLE) {
myGoalsBinding.floatingActionButton.hide();
} else if (dy < 0 && myGoalsBinding.floatingActionButton.getVisibility() != View.VISIBLE) {
myGoalsBinding.floatingActionButton.show();
}
}
});
myGoalsViewModel.getAllGoals().observe(getViewLifecycleOwner(), new Observer<List<Goal>>() {
#Override
public void onChanged(List<Goal> goals) {
myGoalsAdapter.submitList(goals); // This observer is not called even after updating a record
}
});
return myGoalsBinding.getRoot();
}
#Override
public void editGoalCallback(Goal goal) {
Intent intent = new Intent(getActivity(), CreateGoalActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable("goal", goal);
intent.putExtras(bundle);
startActivity(intent);
}
#Override
public void goalCheckBoxCallback(Goal goal) {
myGoalsViewModel.updateGoal(goal);
}
private void enableSwipeToDeleteAndUndo() {
SwipeToDeleteCallback swipeToDeleteCallback = new SwipeToDeleteCallback(getActivity()) {
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int i) {
if(i==ItemTouchHelper.LEFT) {
Goal tempGoal = myGoalsAdapter.getGoalAt(viewHolder.getAdapterPosition());
myGoalsViewModel.deleteGoal(tempGoal);
Snackbar.make(myGoalsBinding.rootConstraintLayout, "Goal Deleted", Snackbar.LENGTH_LONG)
.setAction("Undo", v -> {
myGoalsViewModel.insertGoal(tempGoal);
})
.setActionTextColor(getActivity().getResources().getColor(R.color.arcticLimeGreen))
.show();
}else if(i==ItemTouchHelper.RIGHT){
Goal tempGoal = myGoalsAdapter.getGoalAt(viewHolder.getAdapterPosition());
if(tempGoal.isCompleted())
tempGoal.setCompleted(false);
else
tempGoal.setCompleted(true);
TransitionManager.beginDelayedTransition(myGoalsBinding.recyclerView2);
myGoalsViewModel.updateGoal(tempGoal); // This is where the update is called
}
}
};
ItemTouchHelper itemTouchhelper = new ItemTouchHelper(swipeToDeleteCallback);
itemTouchhelper.attachToRecyclerView(myGoalsBinding.recyclerView2);
}
}
The MyGoals ViewModel:
public class MyGoalsViewModel extends AndroidViewModel {
private NoteRepository repository;
private LiveData<List<Goal>> allGoals;
public MyGoalsViewModel(#NonNull Application application) {
super(application);
repository = new NoteRepository(application);
allGoals = repository.getAllGoals();
}
public LiveData<List<Goal>> getAllGoals(){
return allGoals;
}
public void deleteGoal(Goal goal){repository.deleteGoal(goal);}
public void insertGoal(Goal goal){repository.insertGoal(goal);}
public void updateGoal(Goal goal){repository.updateGoal(goal);}
}
The Repository:
public class NoteRepository {
private String DB_NAME = "db_task";
Context context;
private GoalDao goalDao;
private LiveData<List<Goal>> allGoals;
private NoteDatabase noteDatabase;
public NoteRepository(Context context) {
noteDatabase = NoteDatabase.getInstance(context);
goalDao = noteDatabase.goalDao();
allGoals = goalDao.getAllGoals();
this.context = context;
}
public void insertGoal(Goal goal){
new InsertGoalAsyncTask(goalDao).execute(goal);
}
public void deleteGoal(Goal goal){
new DeleteGoalAsyncTask(goalDao).execute(goal);
}
public void updateGoal(Goal goal){
new UpdateGoalAsyncTask(goalDao).execute(goal);
}
public void deleteAllGoals(){
new DeleteAllGoalAsyncTask(goalDao).execute();
}
public LiveData<List<Goal>> getAllGoals(){
return allGoals;
}
private static class InsertGoalAsyncTask extends AsyncTask<Goal,Void,Void>{
private GoalDao goalDao;
private InsertGoalAsyncTask(GoalDao goalDao){
this.goalDao = goalDao;
}
#Override
protected Void doInBackground(Goal... goals) {
goalDao.insert(goals[0]);
return null;
}
}
private static class DeleteGoalAsyncTask extends AsyncTask<Goal,Void,Void>{
private GoalDao goalDao;
private DeleteGoalAsyncTask(GoalDao goalDao){
this.goalDao = goalDao;
}
#Override
protected Void doInBackground(Goal... goals) {
goalDao.delete(goals[0]);
return null;
}
}
private static class UpdateGoalAsyncTask extends AsyncTask<Goal,Void,Void>{
private GoalDao goalDao;
private UpdateGoalAsyncTask(GoalDao goalDao){
this.goalDao = goalDao;
}
#Override
protected Void doInBackground(Goal... goals) {
goalDao.update(goals[0]);
return null;
}
}
private static class DeleteAllGoalAsyncTask extends AsyncTask<Void,Void,Void>{
private GoalDao goalDao;
private DeleteAllGoalAsyncTask(GoalDao goalDao){
this.goalDao = goalDao;
}
#Override
protected Void doInBackground(Void... voids) {
goalDao.deleteAllGoals();
return null;
}
}
}
The DAO class:
#Dao
public interface GoalDao {
#Insert
void insert(Goal goal);
#Update
void update(Goal goal);
#Delete
void delete(Goal goal);
#Query("DELETE from goal_table")
void deleteAllGoals();
#Query("Select * from goal_table order by end_date")
LiveData<List<Goal>> getAllGoals();
}
I have this issue in 2 fragments and there are 2 other fragments that do not have this issue with the exact same implementation. Why is the observer not being called as soon as I update a record in MyGoals fragment?
I found the solution, the problem was not in the LiveData code, but in the Recyclerview ListAdapter & DiffUtil Implementation which stopped from triggering LiveData change.
In MyGoalsAdapter I have used DiffUtil & ListAdapter to have smooth animations and increase performance. For it to work properly we need to compare the new list with the old list. The Problem is where the contents of an object were being marked as equal when they were actually different. I solved this by adding a date field in my Model class modifiedAt
and updated the field before that Object was updated. Here is the snippet of code to explain it better.
MyGoalsAdapter:
public class MyGoalsAdapter extends ListAdapter<Goal, MyGoalsAdapter.MyGoalsViewHolder> {
private Context context;
public MyGoalsAdapter() {
super(DIFF_CALLBACK);
}
private static final DiffUtil.ItemCallback<Goal> DIFF_CALLBACK = new DiffUtil.ItemCallback<Goal>() {
#Override
public boolean areItemsTheSame(#NonNull Goal oldItem, #NonNull Goal newItem) {
return oldItem.getId() == newItem.getId();
}
#Override
public boolean areContentsTheSame(#NonNull Goal oldItem, #NonNull Goal newItem) { //Here we check if the objects in the list have changed fields.
boolean id,desc,iscomp,edate,etime,sdate,stime,title, naya, purana, createdAt, modifiedAt;
id = oldItem.getId() == newItem.getId();
desc = oldItem.getDescription().equals(newItem.getDescription());
purana = oldItem.isCompleted();
naya = newItem.isCompleted();
iscomp = purana && naya;
edate = oldItem.getEnd_date().equals(newItem.getEnd_date());
etime = oldItem.getEnd_time().equals(newItem.getEnd_time());
sdate = oldItem.getStart_date().equals(newItem.getStart_date());
stime = oldItem.getStart_time().equals(newItem.getStart_time());
title = oldItem.getTitle().equals(newItem.getTitle());
createdAt = oldItem.getCreatedAt().equals(newItem.getCreatedAt());
modifiedAt = oldItem.getModifiedAt().equals(newItem.getModifiedAt()); //This will return false for the object that is changed
return id &&
desc &&
iscomp &&
edate &&
etime &&
sdate &&
stime &&
title &&
createdAt &&
modifiedAt
;
}
};
}
When I am updating I set the Object modifiedAt field with the current Date and Time.
Goal tempGoal = myGoalsAdapter.getGoalAt(viewHolder.getAdapterPosition()); //Get the object to make change to it
//make change to the object's field
tempGoal.setModifiedAt(Calendar.getInstance().getTime()); //set the modified date with Current date
myGoalsViewModel.updateGoal(tempGoal); //Update the object to the database
Changing the modifiedAt field will tell the Adapter when there is an object that is updated, triggering the animation and showing the updated object in the List, instantly.
I hope this will help someone.

Room - SELECT doesn't return any data

Can't get where is my mistake - it seems data is inserted (I checked the database file through Device File Explorer) but it doesn't returns.
I wonder whether it's in Adapter or ViewHolder or anywhere else.
Any help is granted!
This the activity where I perform my queries
public class ShowDatabaseActivity extends AppCompatActivity {
private List <Contact> contactsList = new ArrayList<>()
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_show_database);
setupToolbar();
initRecyclerView();
Intent intent = getIntent();
unpack(intent);
}
private void setupToolbar() {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
toolbar.setNavigationOnClickListener(v -> onBackPressed());
}
}
private void initRecyclerView() {
RecyclerView recyclerView = findViewById(R.id.recycler_view);
final ContactsListAdapter adapter = new ContactsListAdapter(contactsList);
adapter.notifyDataSetChanged();
recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
recyclerView.setAdapter(adapter);
}
private void unpack(Intent intent) {
final Handler handler = new Handler();
Thread backgroundThread = new Thread(() -> {
Bundle extras = intent.getExtras();
String lastName = extras.getString(Constants.LAST_NAME_KEY);
String firstName = extras.getString(Constants.FIRST_NAME_KEY);
String middleName = extras.getString(Constants.MIDDLE_NAME_KEY);
int age = extras.getInt(Constants.AGE_KEY);
Contact contact = new Contact(lastName, firstName, middleName, age);
AppDatabase.getINSTANCE(ShowDatabaseActivity.this).contactDao().insert(contact);
AppDatabase.getINSTANCE(ShowDatabaseActivity.this).contactDao().getAll();
handler.post(() -> {
});
});
backgroundThread.start();
}
}
If I debug this line shows data sucessfully -
(ShowDatabaseActivity.this).contactDao().insert(contact);
My adapter
public class ContactsListAdapter extends RecyclerView.Adapter<ContactsListAdapter.ContactViewHolder> {
private Context context;
private List<Contact> contacts;
public ContactsListAdapter(#NonNull List<Contact> contacts) {
this.contacts = contacts;
}
#Override
public ContactViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(context);
View itemView = inflater.inflate(R.layout.recycler_view, parent, false);
return new ContactViewHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull ContactViewHolder holder, int position) {
Contact currentContact = contacts.get(position);
if (currentContact!=null) {
holder.contactLastNameView.setText(currentContact.getLastName());
holder.contactFirstNameView.setText(currentContact.getFirstName());
holder.contactMiddleNameView.setText(currentContact.getMiddleName());
holder.contactAgeView.setText(Integer.toString(currentContact.getAge()));
}
else {
holder.contactLastNameView.setText("No information");
holder.contactFirstNameView.setText("No information");
holder.contactMiddleNameView.setText("No information");
holder.contactAgeView.setText("No information");
}
}
#Override
public int getItemCount() {
return contacts.size();
}
class ContactViewHolder extends RecyclerView.ViewHolder {
private final TextView contactLastNameView;
private final TextView contactFirstNameView;
private final TextView contactMiddleNameView;
private final TextView contactAgeView;
private ContactViewHolder(View itemView) {
super(itemView);
contactLastNameView = itemView.findViewById(R.id.last_name_text_view);
contactFirstNameView = itemView.findViewById(R.id.first_name_text_view);
contactMiddleNameView = itemView.findViewById(R.id.middle_name_text_view);
contactAgeView = itemView.findViewById(R.id.age_text_view);
}
}
#Override
public int getItemViewType(final int position) {
return R.layout.recycler_view;
}
}
My DataBase
#Database(entities = {Contact.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
public abstract ContactDao contactDao();
private List<Contact> allContacts;
List<Contact> getAllContacts() {
return allContacts;
}
private static AppDatabase INSTANCE;
public synchronized static AppDatabase getINSTANCE(Context context) {
INSTANCE = getDatabase(context);
return INSTANCE;
}
private static AppDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "table_contacts")
.build();
Log.d("LOG", "Getting the database instance");
}
}
return INSTANCE;
}
}
There is no doubt in my Entity and Dao classes as it worked perfectly with other variant of database so I don't attach them.
Will be very grateful for any help!
Your code is incomplete
You are setting only empty Array inside your adapter, to rectify this make your Adapter's object global
Your handler.post have nothing inside it
you need to create a function inside you adapter like below
public void addItem(List<Contacts> list) {
mList.add(list)
notifyItemInserted(mList.size - 1)
}
Now you need to call addItem inside your handler.post by using
adapter.addItem(contact)
This will add the content inside your adapter's list and notify the chnages also
AppDatabase.getINSTANCE(ShowDatabaseActivity.this).contactDao().getAll();
This code does not assign data to anything/ variable to hold your data. Assign data to a reference variable and pass that variable to the adapter so it can show data from it.

MVVM. Set data in ViewModel

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.

passing Data between FragmentA and FragmentB

How do you pass data between Fragments.
I've created a InstructionActivity, InstructionsFragment, StepsFragment and SharedViewModel. I'm trying to pass String from InstructionsFragment to the StepsFragment
this the result in my logCat:
D/StepsFragment:THIS com.shawn.nichol.bakingapp.Fragments.SharedViewModel#23e39c0
I have also put the whole code up on GitHub.
SharedViewModel:
public class SharedViewModel extends ViewModel {
private final MutableLiveData<String> stepPosition = new MutableLiveData<>();
public void setStepPosition(String position) {
stepPosition.setValue(position);
}
public MutableLiveData getStepPosition() {
return stepPosition;
}
}
InstructionsFragment:
public class InstructionsFragment extends Fragment {
private static final String LOGTAG = "InstructionsFragment";
private RecyclerView mRecyclerView;
private InstructionsAdapter mAdapter;
private String mAllIngredients;
private SharedViewModel model;
// Empty constructor
public InstructionsFragment() {
}
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle onSavedInstanceState) {
View view = inflater.inflate(R.layout.fragment_instructions, container, false);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
mRecyclerView = (RecyclerView) view.findViewById(R.id.ingredients_instructions_rv_fragments);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mRecyclerView.addOnItemTouchListener(new RecyclerTouchListener(getActivity().getApplicationContext(),
mRecyclerView, new RecyclerTouchListener.ClickListener() {
#Override
public void onClick(View view, int position) {
Log.d(LOGTAG, "Step " + (position + 1) + " " +
InstructionsExtractSteps.stepsShortDescriptionList.get(position));
model.setStepPosition("HELP");
FragmentManager mFragmentManager = getFragmentManager();
StepsFragment mStepsFragment = new StepsFragment();
FragmentTransaction mFragmentTransaction = mFragmentManager.beginTransaction();
mFragmentTransaction
.replace(R.id.instructions_place_holder, mStepsFragment)
// Puts InstructionsFragment on back stack, when back button is press it will
// reload that fragment instead of going back to the RecipeActivity.
.addToBackStack(null)
.commit();
}
#Override
public void onLongClick(View view, int position) {
}
}));
mAdapter = new InstructionsAdapter();
mRecyclerView.setAdapter(mAdapter);
return view;
}
/**
* onViewCreated: I found this to be the only way to update the TextView in fragment_instructions.xml
*
* #param view
* #param savedInstanceState
*/
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState){
TextView mLoadIngredients = (TextView)getView().findViewById(R.id.ingredients_tv_fragment);
// Set ingredients to screen
for(int i = 0; i < InstructionsExtractIngredients.ingredientList.size(); i++) {
if(i == 0) {
mAllIngredients = "- " + InstructionsExtractIngredients.ingredientList.get(i);
} else {
mAllIngredients = mAllIngredients + "\n - " + InstructionsExtractIngredients.ingredientList.get(i);
}
}
Log.d(LOGTAG, mAllIngredients);
mLoadIngredients.setText(mAllIngredients);
}
}
StepsFragment:
public class StepsFragment extends Fragment {
private static final String LOGTAG = "StepsFragment";
// Requires an empty constructor
public StepsFragment() {
}
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_step, container, false);
final SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getStepPosition();
Log.d(LOGTAG, "THIS " + model);
return view;
}
}
you can use interfaces to allow communications between two fragment and its not deprecated
however a better alternative option is to use Architecture Components
just add the following libraries to your app gradle file
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'android.arch.lifecycle:runtime:1.1.1'
and then create the following class as shown below
import android.arch.lifecycle.ViewModel;
import android.support.annotation.NonNull;
public class TestViewModel extends ViewModel {
private MutableLiveData<String> fragmentAClicked;
public LiveData<String> getFragmentAClicked() {
if (fragmentAClicked == null) {
fragmentAClicked = new MutableLiveData<String>();
}
return fragmentAClicked;
}
public void fragmentAIsClicked(){
fragmentAClicked.setValue("I am clicked")
}
}
in your activity class add the following code
public class MyActivity extends AppCompatActivity {
TestViewModel model;
public void onCreate(Bundle savedInstanceState) {
model = ViewModelProviders.of(this).get(TestViewModel.class);
}
}
suppose you have two fragments A and B
public class FragmentB extends Fragment {
private TestViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model =ViewModelProviders.of(getActivity()).get(TestViewModel.class);
//communicating with fragment
model.fragmentAIsClicked();
}
}
public class FragmentA extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TestViewModel model = ViewModelProviders.of(getActivity()).get(TestViewModel.class);
model.getFragmentAClicked().observe(this, { msg ->
Log.d("test","message from fragmentA is " + msg)
});
}
}
so basically the idea is that you a viewmodel class based on mvvm design pattern which handles all communication between activities ,fragments using events
for more info check this viewmodel
I don't know what is your purpose but you can use a POJO class to pass between fragment.
public class Test {
public static class FragmentScanIDGInfo {
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
String lastName = "";
String firstName = "";
public static FragmentScanIDGInfo shared;
public static FragmentScanIDGInfo sharedSingleton() {
if (shared == null) {
shared = new FragmentScanIDGInfo();
}
return shared;
}
FragmentScanIDGInfo() {
this.setLastName("");
this.setFirstName("");
}
}
}
And you can set and get in your own purpose like :
Test.FragmentScanIDGInfo scanDataFragment = Test.FragmentScanIDGInfo.sharedSingleton();
scanDataFragment.setFirstName(""); /* set data first name */
scanDataFragment.getFirstName(); /* get data first name */

Categories

Resources