How to make Livedata not update whole list - android

So I've got an adapter, which is holding a list of objects that I'm displaying. I'm using the MVVP approach with LiveData and ViewModel that come with the android architecture framework.
In my fragment, I connect the livedata to the adapter:
viewModel.getAlarms().observe(this, alarms -> {
Timber.d("Updating alarm list");
alarmAdapter.updateAlarms(alarms);
});
And in my adapter, I update the list...
void updateAlarms(List<Alarm> alarms){
this.alarms = alarms;
notifyDataSetChanged();
}
So even if I make a small change on a single item from the list (item update, item create, item delete..), the whole list will update. That messes up all of my animations. Is there a way to prevent that?
I don't want to copy everything, but as requested here's the bigger picture:
Fragment:
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
viewModel = ViewModelProviders.of(this, factory).get(HomeViewModel.class);
// Get everything..
viewModel.getAlarms().observe(this, alarms -> {
Timber.d("Updating alarm list");
alarmAdapter.updateAlarms(alarms);
});
}
#OnClick(R.id.home_fbtn_add_alarm)
void addAlarm(){
viewModel.createAlarm(new Alarm(13,39));
}
private void onAlarmStatusChanged(int alarmId, boolean isActive){
// TODO Make it so it doesn't update the whole list...
viewModel.setAlarmStatus(alarmId, isActive);
}
private void onAlarmDeleted(int alarmId){
this.showSnackbar(String.format("Alarm %s deleted", alarmId), clContainer);
viewModel.deleteAlarm(alarmId);
}
Adapter:
class AlarmsAdapter extends RecyclerView.Adapter<AlarmsAdapter.AlarmHolder> {
private List<Alarm> alarms;
private BiConsumer<Integer, Boolean> onStatusChange;
private Consumer<Integer> onDelete;
AlarmsAdapter(BiConsumer<Integer, Boolean> onStatusChange, Consumer<Integer> onDelete) {
this.alarms = new ArrayList<>();
this.onStatusChange = onStatusChange;
this.onDelete = onDelete;
}
void updateAlarms(List<Alarm> alarms){
this.alarms = alarms;
notifyDataSetChanged();
}
#NonNull
#Override
public AlarmHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
Context parentContext = parent.getContext();
int alarmLayoutId = R.layout.item_alarm;
View view = LayoutInflater.from(parentContext).inflate(alarmLayoutId, parent, false);
return new AlarmHolder(view);
}
#Override
public void onBindViewHolder(#NonNull AlarmHolder alarmViewHolder, int position) {
Alarm alarm = alarms.get(position);
alarmViewHolder.setAlarm(alarm);
}
#Override
public int getItemCount() {
return alarms == null ? 0 : alarms.size();
}
class AlarmHolder extends RecyclerView.ViewHolder {
#BindView(R.id.item_alarm_tv_time)
TextView tvTime;
#BindView(R.id.item_alarm_tv_repeat)
TextView tvRepeat;
#BindView(R.id.item_alarm_tv_punishments)
TextView tvPunishment;
#BindView(R.id.item_alarm_swt_active)
Switch swtActive;
#BindView(R.id.item_alarm_img_delete)
ImageView imgDelete;
#BindView(R.id.item_alarm_foreground)
ConstraintLayout foreground;
#BindView(R.id.item_alarm_background)
RelativeLayout background;
AlarmHolder(#NonNull View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
void setAlarm(Alarm alarm){
Timber.i("Setting alarm: %s", this.getAdapterPosition());
boolean isActive = alarm.getActive();
tvTime.setText(alarm.getTime());
tvRepeat.setText(alarm.getRepetitionDays());
tvPunishment.setText(alarm.getPunishments());
swtActive.setChecked(isActive);
}
private void setStatus(boolean isActive) {
AlphaAnimation animation;
if(!isActive){
animation = new AlphaAnimation(1.0f, 0.3f);
} else {
animation = new AlphaAnimation(0.3f, 1f);
}
animation.setDuration(300);
animation.setFillAfter(true);
this.itemView.startAnimation(animation);
// TODO Make it so it doesn't update the whole list...
}
#OnCheckedChanged(R.id.item_alarm_swt_active)
void onStatusClick(boolean checked) {
onStatusChange.accept(getAdapterPosition(), checked);
setStatus(checked);
}
#OnClick(R.id.item_alarm_img_delete)
void onDeleteClick() {
onDelete.accept(getAdapterPosition());
}
}}
And the LiveData:
public class HomeViewModel extends ViewModel {
private final AlarmRepository alarmRepository;
private LiveData<List<Alarm>> alarms;
public HomeViewModel(AlarmRepository alarmRepository) {
this.alarmRepository = alarmRepository;
}
/**
* Gets the Alarms' Observable...
* #return Alarms' observable
*/
LiveData<List<Alarm>> getAlarms() {
Timber.d("Fetching alarms..");
if(alarms == null) {
Timber.i("No alarms are cached. Going to DB!");
alarms = alarmRepository.getAllAlarms();
}
return alarms;
}
/**
* Deletes the selected
* #param alarmPosition alarm to be deleted
*/
void deleteAlarm(int alarmPosition) {
Timber.d("Deleting alarm %d", alarmPosition);
getAlarmAtPosition(alarmPosition)
.ifPresent(alarmRepository::deleteAlarm);
}
/**
* Changes the status of the selected alarm
* #param alarmPosition The selected alarm
* #param status The new status
*/
void setAlarmStatus(int alarmPosition, boolean status){
Timber.d("Alarm: %d is changing active status to %s", alarmPosition, status);
getAlarmAtPosition(alarmPosition)
.ifPresent(alarm -> alarmRepository.updateStatus(alarm, status));
}
/**
* Gets the alarm at the selected position.
* #param position The position of the alarm
* #return The alarm of the selected position. Else returns empty.
*/
private Optional<Alarm> getAlarmAtPosition(int position){
Optional<List<Alarm>> alarms =
Optional.ofNullable(this.alarms)
.map(LiveData::getValue);
if(!alarms.isPresent()) {
return Optional.empty();
}
try {
return Optional.of(alarms.get().get(position));
} catch (Exception e){
Timber.e(e, "Could not get alarm at position: %d", position);
return Optional.empty();
}
}
/**
* Creates a new alarm. If null, does nothing.
* #param alarm The alarm to be saved in the DB
*/
void createAlarm(Alarm alarm) {
Timber.d("Adding new alarm.");
if(alarm == null) {
Timber.w("Cannot save null alarm");
return;
}
alarmRepository.createAlarm(alarm);
}
}

I would suggest using ListAdapter (part of recyclerView library)
class AlarmsAdapter extends ListAdapter<Alarm , AlarmsAdapter.AlarmHolder> {
public AlarmsAdapter(
#NonNull ItemCallback<Feed> diffCallback) {
super(diffCallback);
}.....
}
pass this to AlarmsAdapter
private final ItemCallback<Alarm > diffCallback = new ItemCallback<Feed>() {
#Override
public boolean areItemsTheSame(#NonNull Alarm oldItem, #NonNull Alarm newItem) {
return oldItem==newItem;
}
#Override
public boolean areContentsTheSame(#NonNull Alarm oldItem, #NonNull Alarm newItem) {
return oldItem==newItem;
}
};

Related

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.

How to reset recycler when recreate fragment

The problem is that in my tablayout when im switching between tabs my list duplicating. So i need to remove list on onStop() to recreate it then. Or might be other better solution.
I have tried the following solutions
https://code-examples.net/en/q/1c97047
How to reset recyclerView position item views to original state after refreshing adapter
Remove all items from RecyclerView
My code of adapter
public class OnlineUsersAdapter extends RecyclerView.Adapter<OnlineUsersAdapter.OnlineUserViewHolder> {
private List<OnlineUser> onlineUsers = new ArrayList<>();
private OnItemClickListener.OnItemClickCallback onItemClickCallback;
private OnItemClickListener.OnItemClickCallback onChatClickCallback;
private OnItemClickListener.OnItemClickCallback onLikeClickCallback;
private Context context;
public OnlineUsersAdapter(Context context) {
this.onlineUsers = new ArrayList<>();
this.context = context;
}
#NonNull
#Override
public OnlineUsersAdapter.OnlineUserViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
return new OnlineUsersAdapter.OnlineUserViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull OnlineUsersAdapter.OnlineUserViewHolder holder, int position) {
OnlineUser user = onlineUsers.get(position);
Log.d("testList", "rating " + user.getRating() + " uid " + user.getUid());
holder.bind(user, position);
}
#Override
public int getItemCount() {
return onlineUsers.size();
}
class OnlineUserViewHolder extends RecyclerView.ViewHolder {
RelativeLayout container;
ImageView imageView, likeBtn, chatBtn;
TextView name, country;
private LottieAnimationView animationView;
OnlineUserViewHolder(View itemView) {
super(itemView);
context = itemView.getContext();
container = itemView.findViewById(R.id.item_user_container);
imageView = itemView.findViewById(R.id.user_img);
likeBtn = itemView.findViewById(R.id.search_btn_like);
chatBtn = itemView.findViewById(R.id.search_btn_chat);
name = itemView.findViewById(R.id.user_name);
country = itemView.findViewById(R.id.user_country);
animationView = itemView.findViewById(R.id.lottieAnimationView);
}
void bind(OnlineUser user, int position) {
ViewCompat.setTransitionName(imageView, user.getName());
if (FirebaseUtils.isUserExist() && user.getUid() != null) {
new FriendRepository().isLiked(user.getUid(), flag -> {
if (flag) {
likeBtn.setBackground(ContextCompat.getDrawable(context, R.drawable.ic_favorite));
animationView.setVisibility(View.VISIBLE);
} else {
likeBtn.setBackground(ContextCompat.getDrawable(context, R.drawable.heart_outline));
animationView.setVisibility(View.GONE);
}
});
}
if (user.getUid() != null) {
chatBtn.setOnClickListener(new OnItemClickListener(position, onChatClickCallback));
likeBtn.setOnClickListener(new OnItemClickListener(position, onLikeClickCallback));
}
imageView.setOnClickListener(new OnItemClickListener(position, onItemClickCallback));
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
if (user.getImage().equals(Consts.DEFAULT)) {
Glide.with(context).load(context.getResources().getDrawable(R.drawable.default_avatar)).into(imageView);
} else {
Glide.with(context).load(user.getImage()).thumbnail(0.5f).into(imageView);
}
country.setText(user.getCountry());
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(500);
animator.addUpdateListener(valueAnimator ->
animationView.setProgress((Float) valueAnimator.getAnimatedValue()));
if (animationView.getProgress() == 0f) {
animator.start();
} else {
animationView.setProgress(0f);
}
}
}
public OnlineUsersAdapter(OnItemClickListener.OnItemClickCallback onItemClickCallback,
OnItemClickListener.OnItemClickCallback onChatClickCallback,
OnItemClickListener.OnItemClickCallback onLikeClickCallback) {
this.onItemClickCallback = onItemClickCallback;
this.onChatClickCallback = onChatClickCallback;
this.onLikeClickCallback = onLikeClickCallback;
}
public void addUsers(List<OnlineUser> userList) {
int initSize = userList.size();
onlineUsers.addAll(userList);
// notifyItemRangeInserted(onlineUsers.size() - userList.size(), onlineUsers.size());
}
public String getLastItemId() {
return onlineUsers.get(onlineUsers.size() - 1).getUid();
}
public void clearData() {
List<OnlineUser> data = new ArrayList<>();
addUsers(data);
notifyDataSetChanged();
}
My code in fragment
#Override
public void onStop() {
super.onStop();
firstUid = "";
stopDownloadList = false;
List<OnlineUser> list = new ArrayList<>();
mAdapter.addUsers(list);
mAdapter.notifyDataSetChanged();
}
`users are added after callback
#Override
public void addUsers(List<OnlineUser> onlineUsers) {
if (firstUid.equals("")){
firstUid = onlineUsers.get(0).getUid();
}
if (!firstUid.equals("") && onlineUsers.contains(firstUid)){
stopDownloadList = true;
}
if (!stopDownloadList){
mAdapter.addUsers(onlineUsers);
}
setRefreshProgress(false);
isLoading = false;
isMaxData = true;
}
The line mAdapter.addUsers(onlineUsers); from addUsers method gets called twice. Looks like your asynchronous operation gets triggered twice (e. g. from repeating lifecycle methods like onCreate/onCreateView/onViewCreated).
Solution #1: request users a single time
Move your user requesting machinery to onCreate or onAttach. This will save network traffic but could lead to showing outdated data.
Solution #2: replaceUsers
Your clearData calls mAdapter.addUsers(new ArrayList<>()); (btw, take a look at Collections.emptyList()). Looks like you're trying to replace adapter data but appending instead. Replacement method could look like
public void replaceUsers(List<OnlineUser> userList) {
int oldSize = userList.size();
onlineUsers = userList;
notifyItemRangeRemoved(0, oldSize);
notifyItemRangeInserted(0, userList.size);
}
This version still requeses users every time your fragment gets focused but shows fresher data.

Lag in displaying data from Oberver on LiveData

I'm using a ViewModel that populates a RecyclerView.Adapter, and loads data from LiveData from my Room database. The problem is that my display is always blank, and checking with the Dao (for debug, on the main thread) shows me that the data is retreived just fine. (ergo there is data in the DB).
The problem is that the Observer on my LiveData always returns a null (or no data) and I end up having to refresh the fragment at least once (by moving away and moving back) to see anything - even the meagre one record I put in the Database for testing.
Restarting the app or fragment means a blank screen and a few refreshes before I see anything which is strange because, well, the data is already there.
I'm out of ideas on how to get this to show me data in more or less real time. Can anyone help?
Sharing the DAO, ViewModel and Fragment code here.
Fragment
... import libs and set up variables ...
private HouseCallAdapter houseCallAdapter;
private RecyclerView recyclerView;
private TextView emptyView;
RevivDatabase revivDatabase;
private LiveData<List<HouseCall>> liveHousecalls;
private List<HouseCall> houseCalls;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_reviv_housecall_request_list, container, false);
Bundle arguments = getArguments();
String action = arguments.getString("data");
revivDatabase = RevivDatabase.getDatabase(getActivity().getApplicationContext());
emptyView = view.findViewById(R.id.txtNoData);
recyclerView = view.findViewById(R.id.hcrecyclerView);
viewModel = ((Reviv) getActivity()).getViewModel();
if(liveHousecalls == null) {
liveHousecalls = new MutableLiveData<List<HouseCall>>();
}
houseCallAdapter = new HouseCallAdapter(getContext(), apikey, false, false);
liveHousecalls = viewModel.getOpenHousecalls();
// this is to test if there is actually any data retreived
// calling on main thread. Lose this code later.
houseCalls = revivDatabase.revivDao().getHousecallsByStatus(action);
break;
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, false));
houseCallAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
#Override
public void onChanged() {
super.onChanged();
checkEmpty();
}
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
checkEmpty();
}
#Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
checkEmpty();
}
void checkEmpty() {
//emptyView.setText (R.string.no_data_available);
emptyView.setVisibility(houseCallAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
recyclerView.setVisibility (houseCallAdapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
}
});
houseCallAdapter.setData(houseCalls);
houseCallAdapter.notifyDataSetChanged();
liveHousecalls.observe(getActivity(), new Observer<List<HouseCall>>() {
#Override
public void onChanged(#Nullable List<HouseCall> houseCalls) {
if(houseCalls != null) {
houseCallAdapter.setData(houseCalls);
houseCallAdapter.notifyDataSetChanged();
}
}
});
recyclerView.setItemAnimator (new DefaultItemAnimator());
recyclerView.setAdapter(houseCallAdapter);
emptyView.setVisibility(houseCallAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
return view;
}
ViewModel
private LiveData<List<HouseCall>> housecallList;
private LiveData<List<HouseCall>> openHousecalls, confirmedHousecalls, closedHousecalls, missedHousecalls, userCancelledHousecalls, respCancelledHousecalls;
private LiveData<List<Incident>> incidentList, openIncidents;
private LiveData<List<Incident>> closedIncidents, usercancelIncidents, respcancelIncidents;
private LiveData<Incident> liveIncident;
private RevivDatabase database;
Context context;
/////////////////////////////////////////////////////////
// CONSTRUCTOR
/////////////////////////////////////////////////////////
public RevivViewModel(Application application) {
super(application);
//////////////////////////////////////////////////////////////////////////
// //
// DANGER WILL ROBINSON //
// Storing context in ViewModel is Not A Good Idea (TM) //
context = application.getApplicationContext(); //
// //
//////////////////////////////////////////////////////////////////////////
database = RevivDatabase.getDatabase(application);
}
/////////////////////////////////////////////////////////
// GETTERS AND SETTERS
/////////////////////////////////////////////////////////
// Housecalls
public LiveData<List<HouseCall>> getHousecallList() {
if (housecallList == null) {
housecallList = new MutableLiveData<>();
loadHousecalls();
}
return housecallList;
}
public LiveData<List<HouseCall>> getOpenHousecalls() {
if (openHousecalls == null) {
openHousecalls = new MutableLiveData<>();
loadOpenHousecalls();
}
return openHousecalls;
}
/////////////////////////////////////////////////////////
// TRIGGER REFRESH FROM VIEWMODEL
/////////////////////////////////////////////////////////
// TBD
/////////////////////////////////////////////////////////
// EXTERNAL CALLS - REFRESH FROM DB
/////////////////////////////////////////////////////////
// Methods to accept/cancel incidents and housecalls
public void loadHousecalls(){
class OneShotTask implements Runnable {
OneShotTask() {
}
public void run() {
housecallList = database.revivDao().getAllLiveHousecalls();
//housecallList.postValue(hc);
}
}
Thread t = new Thread(new OneShotTask());
t.start();
}
public void loadOpenHousecalls(){
class OneShotTask implements Runnable {
OneShotTask() {
}
public void run() {
openHousecalls = database.revivDao().getLiveHousecallsByStatus("open");
}
}
Thread t = new Thread(new OneShotTask());
t.start();
}
}
DAOInterface
public interface RevivDaoInterface {
// Housecalls
... numerous insert, delete and update calls ...
#Query("SELECT * FROM housecalls WHERE housecallid = :housecallid")
public HouseCall getHousecallById(String housecallid);
#Query("SELECT * FROM housecalls WHERE status = :status")
public List<HouseCall> getHousecallsByStatus(String status);
#Update(onConflict = OnConflictStrategy.IGNORE)
void updateHousecall(HouseCall houseCall);
#Query("SELECT * FROM housecalls WHERE status = \'open\'")
public LiveData<List<HouseCall>> getOpenHousecalls();
#Query("SELECT * FROM housecalls WHERE status = :status")
public LiveData<List<HouseCall>> getLiveHousecallsByStatus(String status);
#Query("SELECT * FROM housecalls")
public List<HouseCall> getAllHousecalls();
}
DAO
imports
#Dao
public abstract class RevivDao implements RevivDaoInterface {
#Transaction
public void upsert(HouseCall houseCall){
try {
this.insert(houseCall);
} catch (SQLiteConstraintException exception) {
this.update(houseCall);
Log.e(TAG, "upsert: ", exception);
}
}
#Transaction
public void upsert(List<HouseCall> houseCall){
for(HouseCall hc : houseCall) {
try {
this.insert(hc);
} catch (SQLiteConstraintException exception) {
this.update(hc);
Log.e(TAG, "upsert: ", exception);
}
}
}
}
Thanks to #pskink, I figured a way around the lag in updating the data into my ViewModel.
To solve this issue, I had to implement PagedListAdapter.
In the build.gradle (Module) file
implementation 'android.arch.paging:runtime:1.0.1'
In the DAO
#Query("SELECT * from housecalls where status = :status")
public abstract DataSource.Factory<Integer, HouseCall> getHousecallPagesByStatus(String status);
In the ViewModel
//declare a LiveData of PagedList
LiveData<PagedList<HouseCall>> openhousecallPages;
// Define Configuration for Paged List
Config pagedListConfig = (new PagedList.Config.Builder()).setEnablePlaceholders(true)
.setPrefetchDistance(10)
.setPageSize(20).build();
// Function to access the data
public LiveData<PagedList<HouseCall>> getOpenhousecallPages(){
openhousecallPages = new LivePagedListBuilder<>(database.revivDao().getHousecallPagesByStatus("open"),
pagedListConfig).build();
return openhousecallPages;
}
Set up the PagedListAdapter
package packagename;
import static android.content.ContentValues.TAG;
public class HouseCallPagedAdapter extends PagedListAdapter<HouseCall, HouseCallViewHolder>{
protected HouseCallPagedAdapter(#NonNull DiffUtil.ItemCallback<HouseCall> diffCallback) {
super(diffCallback);
}
public HouseCallPagedAdapter(#NonNull DiffUtil.ItemCallback diffcallback){
super(diffcallback);
}
#NonNull
#Override
public HouseCallViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int i) {
return new HouseCallViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.card_housecall_request, parent, false),parent.getContext());
}
#Override
public void onBindViewHolder(#NonNull in.portmanteau.reviv.Adapters.HouseCallViewHolder holder, int i) {
holder.bindTo(getItem(i));
}
}
Define a ViewHolder with the following structure
public class HouseCallViewHolder extends RecyclerView.ViewHolder{
// declare values, elements, etc
public HouseCallViewHolder(View itemView, Context mContext) {
super(itemView);
// set up UI elements
}
void bindTo(final HouseCall houseCall){
this.houseCall = houseCall;
//populate values, set onClickListeners, etc.
}
}
Finally, use the Adapter in your Activity/Fragment!
// Implement an DiffUtil.ItemCallback
private DiffUtil.ItemCallback<HouseCall> diffCallback = new DiffUtil.ItemCallback<HouseCall>() {
#Override
public boolean areItemsTheSame(#NonNull HouseCall houseCall, #NonNull HouseCall newhouseCall) {
return houseCall.getHousecallid().equalsIgnoreCase(newhouseCall.getHousecallid()) ;
}
#Override
public boolean areContentsTheSame(#NonNull HouseCall houseCall, #NonNull HouseCall newhouseCall) {
return houseCall.isTheSame(newhouseCall);
}
};
HouseCallPagedAdapter houseCallPagedAdapter = new HouseCallPagedAdapter(diffCallback);
viewModel.getOpenhousecallPages().observe(this, houseCallPagedAdapter::submitList);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, false));
recyclerView.setItemAnimator (new DefaultItemAnimator());
recyclerView.setAdapter(houseCallPagedAdapter);

Recyler View with multiple toggle button checking the button randomly on scroll

I have a recycler view with with multiple toggle buttons on click of which the state is changed and the newly updated state is sent to the server by calling a service on changing the state of the toggle button.
The problem i am facing is that whenever the recycler view is scrolled the toggle buttons are getting checked randomly because of the recycling of the views and the service is called multiple times at the same time, because of which an indeterminate progress bar is shown.
I have tried multiple ways to handle this by intially setting the adapter to null. and also storing the state of the checked/toggle state of the button.
But nothing seems to help.
Below is the code of the recycler adapter class
public class NotificationsIllnessAdapter extends RecyclerView.Adapter<NotificationsIllnessAdapter.NotificationIllnessViewHolder> {
Context context = null;
ArrayList<NotificationIllnessdata> notificationIllnessdatas;
ArrayList<NotificationIllnessdata> notificationIllnessArraylist = null;
NetworkStatus mNetworkStatus = null;
static AlertDialog mShowDialog = null;
Button mButton_alerts;
public NotificationsIllnessAdapter(Context context,ArrayList<NotificationIllnessdata> notificationIllnessdataArrayList,Button button_alerts) {
this.context = context;
this.notificationIllnessdatas=notificationIllnessdataArrayList;
this.mButton_alerts=button_alerts;
for(int i=0;i<this.notificationIllnessdatas.size();i++)
{
Log.e("nIllnessadapter","inside constructor"+this.notificationIllnessdatas.get(i).getIsNotification());
}
}
#Override
public NotificationIllnessViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
mNetworkStatus = new NetworkStatus(context);
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.notifications_inflater, parent, false);
notificationIllnessArraylist = new ArrayList<>();
NotificationIllnessViewHolder viewHolder = new NotificationIllnessViewHolder(context,v);
viewHolder.setClickListener(new MyClickListener() {
#Override
public void onClickListener(View v, int position, boolean isLongClick) {
Toast.makeText(context,"OnClick",Toast.LENGTH_SHORT).show();
}
});
return viewHolder;
}
#Override
public void onBindViewHolder(final NotificationIllnessViewHolder holder, final int position) {
holder.mTextView_symptom.setText(notificationIllnessdatas.get(position).getIllnessCategory());
if(notificationIllnessdatas.get(position).getIsNotification())
{
Log.e("nIllnessadapter","true"+position);
holder.mToggleButton_symptom.setChecked(true);
}
else
{
Log.e("nIllnessadapter","false"+position);
holder.mToggleButton_symptom.setChecked(false);
}
//in some cases, it will prevent unwanted situations
holder.mToggleButton_symptom.setOnCheckedChangeListener(null);
//if true the togglebutton will be selected else unselected.
holder.mToggleButton_symptom.setChecked(notificationIllnessdatas.get(position).getIsNotification());
holder.mToggleButton_symptom.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
ArrayList<UpdateNotificationRequestData> Updatenoti;
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked)
{
//toggle button enabled
UpdateNotificationsRequestModel requestModel = new UpdateNotificationsRequestModel();
requestModel.setUserID(AppPreferences.readString(context, AppPreferenceNames.sUserid,""));
requestModel.setAppVersion(CommonUtils.APP_VERSION);
requestModel.setDeviceInfo(CommonUtils.DeviceInfo);
requestModel.setDeviceTypeID(CommonUtils.DEVICE_TYPE_ID);
Updatenoti = new ArrayList<UpdateNotificationRequestData>();
UpdateNotificationRequestData requestData = new UpdateNotificationRequestData();
Log.e("illadapter","status-->"+notificationIllnessdatas.get(position).getIsNotification());
requestData.setIsNotification("1");
requestData.setNotificationSettingID(notificationIllnessdatas.get(position).getNotificationSettingID());
Updatenoti.add(requestData);
requestModel.setUpdateNotification(Updatenoti);
/**
* Call the Update Notifications service
*/
if (mNetworkStatus.isNetWorkAvailable(context) == true) {
update_notifications(requestModel);
} else {
CommonUtils.showAlertDialog(context,"No Network Available. Please connect to network");
}
//set the objects last status
holder.mToggleButton_symptom.setChecked(isChecked);
}
else
{
//toggle button disabled
UpdateNotificationsRequestModel requestModel = new UpdateNotificationsRequestModel();
requestModel.setUserID(AppPreferences.readString(context, AppPreferenceNames.sUserid,""));
requestModel.setAppVersion(CommonUtils.APP_VERSION);
requestModel.setDeviceInfo(CommonUtils.DeviceInfo);
requestModel.setDeviceTypeID(CommonUtils.DEVICE_TYPE_ID);
Updatenoti = new ArrayList<UpdateNotificationRequestData>();
UpdateNotificationRequestData requestData = new UpdateNotificationRequestData();
Log.e("illadapter","status 2-->"+notificationIllnessdatas.get(position).getIsNotification());
requestData.setIsNotification("0");
requestData.setNotificationSettingID(notificationIllnessdatas.get(position).getNotificationSettingID());
Updatenoti.add(requestData);
requestModel.setUpdateNotification(Updatenoti);
/**
* Call the UpdateNotifications service
*/
if (mNetworkStatus.isNetWorkAvailable(context) == true) {
update_notifications(requestModel);
} else {
CommonUtils.showAlertDialog(context,"No Network Available. Please connect to network");
}
//set the objects last status
holder.mToggleButton_symptom.setChecked(false);
}
}
});
}
#Override
public int getItemCount() {
return notificationIllnessdatas.size();
}
/**
* View Holder for Adapter
*/
class NotificationIllnessViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener
{
MyClickListener clickListener;
public TextView mTextView_symptom;
public ToggleButton mToggleButton_symptom;
public NotificationIllnessViewHolder(Context context,View itemView) {
super(itemView);
mTextView_symptom = (TextView)itemView.findViewById(R.id.TextView_Symptom);
mToggleButton_symptom = (ToggleButton) itemView.findViewById(R.id.ToggleButton_Symptoms);
}
#Override
public void onClick(View v) {
// If not long clicked, pass last variable as false.
clickListener.onClickListener(v, getPosition(), false);
}
public void setClickListener(MyClickListener clickListener) {
this.clickListener = clickListener;
}
}
/**
* Update the notification settings
*/
public void update_notifications(UpdateNotificationsRequestModel object) {
/**
* Start the progress Bar.
*/
CommonUtils.show_progressbar(context);
/**
* call api
*/
Call<UpdateNotificationsResponseModel> responsecall = VirusApplication.getRestClient().getAPIService().updateNotifications(object);
responsecall.enqueue(new Callback<UpdateNotificationsResponseModel>() {
#Override
public void onResponse(Response<UpdateNotificationsResponseModel> response, Retrofit retrofit) {
/**
* Stop the progress Bar
*/
CommonUtils.stop_progressbar();
if (response.isSuccess()) {
//Server Success
UpdateNotificationsResponseModel responseModel = response.body();
if (responseModel.getErrorCode().equalsIgnoreCase("0")) {
//Data Success
Log.e("nf", "data success");
//CommonUtils.showAlertDialog(context, responseModel.getMessage());
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(responseModel.getMessage())
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
/**
* Downloads mychildren details.
*/
if (mNetworkStatus.isNetWorkAvailable(context)) {
getNotificationSettings();
} else {
CommonUtils.showAlertDialog(context, context.getString(R.string.network_unavailable));
}
dialog.dismiss();
}
});
mShowDialog = builder.create();
mShowDialog.show();
} else {
CommonUtils.showAlertDialog(context, responseModel.getMessage());
}
} else {
CommonUtils.showAlertDialog(context, "Server Error");
}
}
#Override
public void onFailure(Throwable t) {
/**
* Stop the progress Bar
*/
CommonUtils.stop_progressbar();
}
});
}
/**
* Get Notification Settings
*/
public void getNotificationSettings(){
//hit the getnotifications API to fetch the notification details
CommonUtils.show_progressbar(context);
/**
* Calls WebAPI
*/
Call<NotificationsModel> notificationsModelCall = VirusApplication.getRestClient().getAPIService().notifications(getNotificationsrequest());
notificationsModelCall.enqueue(new Callback<NotificationsModel>() {
#Override
public void onResponse(Response<NotificationsModel> response, Retrofit retrofit) {
/**
* Stops the Progresss bar
*/
CommonUtils.stop_progressbar();
if(response.isSuccess()) {
NotificationsModel notificationsModel = response.body();
if (notificationsModel.getErrorCode().equalsIgnoreCase("0")) {// Data Success
Log.e("notificationsAdapter","data success");
int i = 1;
notificationIllnessArraylist.clear();
for (NotificationIllnessdata notificationIllnessdata : notificationsModel.getNotificationIllnessdata()) {
notificationIllnessArraylist.add(notificationIllnessdata);
Log.e("notificationsAdapter","getnotificationsmethod"+i +notificationIllnessdata.getIsNotification() );
i++;
}
Log.e("sonu", "Symptoms ArraySize-->" + notificationIllnessArraylist.size());
if (notificationIllnessArraylist.size() > 0) {
ArrayList<NotificationIllnessdata> arrayTrue = new ArrayList<NotificationIllnessdata>();
ArrayList<NotificationIllnessdata> arrayFalse = new ArrayList<NotificationIllnessdata>();
for(int j=0;j<notificationIllnessArraylist.size();j++)
{
if(notificationIllnessArraylist.get(j).getIsNotification()){
arrayTrue.add(notificationIllnessArraylist.get(j));
}
else
if(!notificationIllnessArraylist.get(j).getIsNotification())
{
arrayFalse.add(notificationIllnessArraylist.get(j));
}
}
if(notificationIllnessArraylist.size()==arrayTrue.size())
{
mButton_alerts.setText("DeSelect All");
mButton_alerts.setBackgroundResource(R.drawable.togglebutton_on);
}
else
if(notificationIllnessArraylist.size()==arrayFalse.size())
{
mButton_alerts.setText("Select All");
mButton_alerts.setBackgroundResource(R.drawable.togglebutton_off);
}
else {
mButton_alerts.setText("Select All");
mButton_alerts.setBackgroundResource(R.drawable.togglebutton_off);
}
}
}
}
}
#Override
public void onFailure(Throwable t) {
}
});
}
/**
* Request values to Get notifications.
*
* #return map object
*/
public Map<String, Object> getNotificationsrequest() {
Map<String, Object> getChildrenValues = new HashMap<>();
getChildrenValues.put("appVersion", CommonUtils.APP_VERSION);
getChildrenValues.put("deviceTypeID", CommonUtils.DEVICE_TYPE_ID);
getChildrenValues.put("deviceInfo", CommonUtils.DeviceInfo);
getChildrenValues.put("userID", AppPreferences.readString(context, AppPreferenceNames.sUserid, ""));
return getChildrenValues;
}
}
Please help me with this. Have been trying from many days but haven't found any solution even after following many answers on stack overflow.
Try out this :
public class NotificationsIllnessAdapter extends RecyclerView.Adapter<NotificationsIllnessAdapter.NotificationIllnessViewHolder> {
Context context = null;
ArrayList<NotificationIllnessdata> notificationIllnessdatas;
NetworkStatus mNetworkStatus = null;
static AlertDialog mShowDialog = null;
Button mButton_alerts;
public NotificationsIllnessAdapter(Context context,ArrayList<NotificationIllnessdata> notificationIllnessdataArrayList,Button button_alerts) {
this.context = context;
this.mNetworkStatus = new NetworkStatus(context);
this.notificationIllnessdatas=notificationIllnessdataArrayList;
this.mButton_alerts=button_alerts;
}
#Override
public NotificationIllnessViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.notifications_inflater, parent, false);
NotificationIllnessViewHolder viewHolder = new NotificationIllnessViewHolder(context,v);
return viewHolder;
}
#Override
public void onBindViewHolder(final NotificationIllnessViewHolder holder, final int position) {
holder.mTextView_symptom.setText(notificationIllnessdatas.get(position).getIllnessCategory());
holder.mToggleButton_symptom.setChecked(notificationIllnessdatas.get(position).getIsNotification());
holder.mToggleButton_symptom.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
ArrayList<UpdateNotificationRequestData> Updatenoti;
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int position = getAdapterPosition();
if( position == RecyclerView.NO_POSITION ) {
return;
}
if(isChecked)
{
notificationIllnessdatas.get(position).setNotification(true);
}
else
{
notificationIllnessdatas.get(position).setNotification(false);
}
}
});
}
The toggling of checkboxes / toggle buttons happens due to the fact that the states are not maintained in your model class. You already have a isNotification field in your model class, set it as soon as the toggle happens as I've illustrated in the code. I have other code clean up suggestions, but those can wait.
Let me know if you need more clarifications.
Update : Answer only applicable for retaining states of toggle
buttons.

Best practices to use realm with a recycler view?

Do you guys have any best practices regarding using realm with a recyclerview ?
I know it's generic question but I found nothing on it on the internet. For example I run into a lot of troubles trying to implement a simple color change on a row . For example consider this typical usage:
public class User extends RealmObject {
#PrimaryKey
String name;
boolean isSelected;
...
constructor, getter and setters
}
public class UserAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private RealmResults<User> users;
public UserAdapter(RealmResults<User> users) {
this.users = users;
}
...
public void markAsSelected(int position){
// get the old selected user and deselect it
notifyItemChanged(? how do i get the position given my User has no index ?);
// mark as selected the new user at position
}
I ran into a lot of issues since I couldn't find anything on the internet. I know this is because I don't know how to properly use realm. But finding the right way is a struggle in itself . I read all their documentation but to no avail.
EDIT : Since I was asked to --> Instead of saying "I have a bunch of issues with [that]", describe your issue(s) and we'll try to provide insights and answers to your incomprehensions.
So my problem is simple :
I have a RealmUser :
public class RealmUser extends RealmObject {
#PrimaryKey
private String key;
private String name;
private boolean isSelected;
private boolean editMode;
private RealmList<RealmItemList> lists;
public RealmUser() {}
public RealmUser(String name, RealmList<RealmItemList> lists, boolean isSelected , boolean editMode) {
this.key = UUID.randomUUID().toString();
this.name = name;
this.isSelected = isSelected;
this.editMode = editMode;
if (lists ==null){
this.lists = new RealmList<RealmItemList>();
}else{
this.lists = lists;
}
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean isSelected) {
this.isSelected = isSelected;
}
public boolean isEditMode() {
return editMode;
}
public void setEditMode(boolean editMode) {
this.editMode = editMode;
}
public RealmList<RealmItemList> getLists() {
return lists;
}
public void setLists(RealmList<RealmItemList> lists) {
this.lists = lists;
}
}
Which I put in a RealmResults array using :
RealmResults users = realm.where(RealmUser.class).findAll();
I pass my user array to my custom user adapter :
public class UserAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private RealmResults<RealmUser> users;
public UserAdapter(RealmResults<RealmUser> users) {
this.users = users;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if(viewType == 1){
View v = inflater.inflate(R.layout.detail_user, parent, false);
return new UserHolder(v);
}else if(viewType == 2){
View v = inflater.inflate(R.layout.edit_user, parent, false);
return new editUserHolder(v);
}else {
return null;
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
RealmUser user = users.get(position);
String userName = user.getName();
boolean isSelected = user.isSelected();
if (holder instanceof UserHolder ){
UserHolder uHolder = (UserHolder) holder;
uHolder.userText.setText(userName);
if (isSelected){
uHolder.userContainer.setBackgroundColor(Color.parseColor("#607D8B"));
}
}else if(holder instanceof editUserHolder){
editUserHolder eUserHolder = (editUserHolder) holder;
eUserHolder.userEditContainer.setBackgroundColor(Color.parseColor("#eeeeee"));
}
}
#Override
public int getItemViewType(int position) {
RealmUser user = users.get(position);
if (user.isEditMode()){
return 2;
}else {
return 1;
}
}
#Override
public int getItemCount() {
return users.size();
}
public void markAsSelected(int position, DrawerLayout mDrawerLayout , Toolbar toolbar, Realm realm){
// Here is my problem : How do I get the already selected user asuming there is one in my db and notify the UI that I changed that item.
}
That has a custom click Listener : that gets recyclerview item that was clicked using :
public class UserClickListener implements RecyclerView.OnItemTouchListener{
public static interface OnItemClickListener{
public void onItemClick(View v, int position);
}
private OnItemClickListener mListener;
private GestureDetector mGestureDetector;
public UserClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener)
{
mListener = listener;
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
if(childView != null && mListener != null)
{
mListener.onItemClick(childView, recyclerView.getChildPosition(childView));
return true;
}
return false;
}
});
}
#Override
public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
View childView = view.findChildViewUnder(e.getX(), e.getY());
if(childView != null && mListener != null && mGestureDetector.onTouchEvent(e))
{
mListener.onItemClick(childView, view.getChildPosition(childView));
}
return false;
}
#Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
}
Which I add to my recyclerView with addOnItemTouchListener :
mListRecycler.addOnItemTouchListener(new UserClickListener(getActivity(), mListRecycler, new UserClickListener.OnItemClickListener(){
#Override
public void onItemClick(View view, int position)
{
UserAdapter myadapter = (UserAdapter) mListRecycler.getAdapter();
myadapter.markAsSelected(position, mDrawerLayout , mToolbar, realm);
}
}));
ANSWER FOR 0.89.0 AND ABOVE
For the latest versions, you should use RealmRecyclerViewAdapter in the realm-android-adapters repository.
Versions:
Use 1.5.0 up to 2.X
Use 2.1.1 up to 4.X
Use 3.0.0 above 5.X
OLD ANSWER FOR OLD VERSIONS:
I made this RealmRecyclerViewAdapter based on the implementation of RealmBaseAdapter.
This is for v0.89.0 AND ABOVE
public abstract class RealmRecyclerViewAdapter<T extends RealmObject, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> { //put this in `io.realm`
protected LayoutInflater inflater;
protected OrderedRealmCollection<T> adapterData;
protected Context context;
private final RealmChangeListener listener;
public RealmRecyclerViewAdapter(Context context, OrderedRealmCollection<T> data) {
if (context == null) {
throw new IllegalArgumentException("Context cannot be null");
}
this.context = context;
this.adapterData = data;
this.inflater = LayoutInflater.from(context);
this.listener = new RealmChangeListener<RealmResults<T>>() {
#Override
public void onChange(RealmResults<T> results) {
notifyDataSetChanged();
}
};
if (data != null) {
addListener(data);
}
}
private void addListener(OrderedRealmCollection<T> data) {
if (data instanceof RealmResults) {
RealmResults realmResults = (RealmResults) data;
realmResults.addChangeListener(listener);
} else if (data instanceof RealmList) {
RealmList realmList = (RealmList) data;
realmList.realm.handlerController.addChangeListenerAsWeakReference(listener);
} else {
throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass());
}
}
private void removeListener(OrderedRealmCollection<T> data) {
if (data instanceof RealmResults) {
RealmResults realmResults = (RealmResults) data;
realmResults.removeChangeListener(listener);
} else if (data instanceof RealmList) {
RealmList realmList = (RealmList) data;
realmList.realm.handlerController.removeWeakChangeListener(listener);
} else {
throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass());
}
}
/**
* Returns how many items are in the data set.
*
* #return the number of items.
*/
#Override
public int getItemCount() {
if (adapterData == null) {
return 0;
}
return adapterData.size();
}
/**
* Get the data item associated with the specified position in the data set.
*
* #param position Position of the item whose data we want within the adapter's
* data set.
* #return The data at the specified position.
*/
public T getItem(int position) {
if (adapterData == null) {
return null;
}
return adapterData.get(position);
}
/**
* Get the row id associated with the specified position in the list. Note that item IDs are not stable so you
* cannot rely on the item ID being the same after {#link #notifyDataSetChanged()} or
* {#link #updateData(OrderedRealmCollection)} has been called.
*
* #param position The position of the item within the adapter's data set whose row id we want.
* #return The id of the item at the specified position.
*/
#Override
public long getItemId(int position) {
// TODO: find better solution once we have unique IDs
return position;
}
/**
* Updates the data associated with the Adapter.
*
* Note that RealmResults and RealmLists are "live" views, so they will automatically be updated to reflect the
* latest changes. This will also trigger {#code notifyDataSetChanged()} to be called on the adapter.
*
* This method is therefore only useful if you want to display data based on a new query without replacing the
* adapter.
*
* #param data the new {#link OrderedRealmCollection} to display.
*/
public void updateData(OrderedRealmCollection<T> data) {
if (listener != null) {
if (adapterData != null) {
removeListener(adapterData);
}
if (data != null) {
addListener(data);
}
}
this.adapterData = data;
notifyDataSetChanged();
}
}
This is for v0.84.0 AND ABOVE, BUT OLDER THAN v0.89.0 (updated for v0.87.5):
public abstract class RealmRecyclerViewAdapter<T extends RealmObject, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> { //put this in `io.realm`
protected LayoutInflater inflater;
protected RealmResults<T> realmResults;
protected Context context;
private final RealmChangeListener listener;
public RealmRecyclerViewAdapter(Context context, RealmResults<T> realmResults, boolean automaticUpdate) {
if (context == null) {
throw new IllegalArgumentException("Context cannot be null");
}
this.context = context;
this.realmResults = realmResults;
this.inflater = LayoutInflater.from(context);
this.listener = (!automaticUpdate) ? null : new RealmChangeListener() {
#Override
public void onChange() {
notifyDataSetChanged();
}
};
if (listener != null && realmResults != null) {
realmResults.realm.handlerController.addChangeListenerAsWeakReference(listener);
}
}
/**
* Returns how many items are in the data set.
*
* #return count of items.
*/
#Override
public int getItemCount() {
if (realmResults == null) {
return 0;
}
return realmResults.size();
}
/**
* Returns the item associated with the specified position.
*
* #param i index of item whose data we want.
* #return the item at the specified position.
*/
public T getItem(int i) {
if (realmResults == null) {
return null;
}
return realmResults.get(i);
}
/**
* Returns the current ID for an item. Note that item IDs are not stable so you cannot rely on the item ID being the
* same after {#link #notifyDataSetChanged()} or {#link #updateRealmResults(RealmResults)} has been called.
*
* #param i index of item in the adapter.
* #return current item ID.
*/
#Override
public long getItemId(int i) {
// TODO: find better solution once we have unique IDs
return i;
}
/**
* Updates the RealmResults associated to the Adapter. Useful when the query has been changed.
* If the query does not change you might consider using the automaticUpdate feature.
*
* #param queryResults the new RealmResults coming from the new query.
*/
public void updateRealmResults(RealmResults<T> queryResults) {
if (listener != null) {
// Making sure that Adapter is refreshed correctly if new RealmResults come from another Realm
if (this.realmResults != null) {
this.realmResults.realm.removeChangeListener(listener);
}
if (queryResults != null) {
queryResults.realm.addChangeListener(listener);
}
}
this.realmResults = queryResults;
notifyDataSetChanged();
}
public void addChangeListenerAsWeakReference(RealmChangeListener realmChangeListener) {
if(realmResults != null) {
realmResults.realm.handlerController.addChangeListenerAsWeakReference(realmChangeListener);
}
}
}
This is for OLDER THAN 0.84.0:
public abstract class RealmRecyclerViewAdapter<T extends RealmObject, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> { //put this in `io.realm`
protected LayoutInflater inflater;
protected RealmResults<T> realmResults;
protected Context context;
private final RealmChangeListener listener;
public RealmRecyclerViewAdapter(Context context, RealmResults<T> realmResults, boolean automaticUpdate) {
if(context == null) {
throw new IllegalArgumentException("Context cannot be null");
}
this.context = context;
this.realmResults = realmResults;
this.inflater = LayoutInflater.from(context);
this.listener = (!automaticUpdate) ? null : new RealmChangeListener() {
#Override
public void onChange() {
notifyDataSetChanged();
}
};
if(listener != null && realmResults != null) {
realmResults.getRealm()
.addChangeListener(listener);
}
}
#Override
public long getItemId(int i) {
// TODO: find better solution once we have unique IDs
return i;
}
public T getItem(int i) {
if(realmResults == null) {
return null;
}
return realmResults.get(i);
}
public void updateRealmResults(RealmResults<T> queryResults) {
if(listener != null) {
// Making sure that Adapter is refreshed correctly if new RealmResults come from another Realm
if(this.realmResults != null) {
realmResults.getRealm().removeChangeListener(listener);
}
if(queryResults != null) {
queryResults.getRealm().addChangeListener(listener);
}
}
this.realmResults = queryResults;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
if(realmResults == null) {
return 0;
}
return realmResults.size();
}
}
Some of the answers above include reflection, not to mention that a sectioned RecyclerView would cause complications. They also do not support adding and removing items. Here is my version of the RecyclerView Adapter that works with Realm, supports a sectioned RecyclerView, also adds and removes items at arbitrary positions if need be
Here is our AbstractRealmAdapter that takes care of all the low level stuff, displaying headers, footers, items, loading data inside RealmResults, managing item types
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmResults;
public abstract class AbstractRealmAdapter<T extends RealmObject, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
public static final int HEADER_COUNT = 1;
public static final int FOOTER_COUNT = 1;
//Our data source
protected RealmResults<T> mResults;
public AbstractRealmAdapter(Realm realm) {
//load data from subclasses
mResults = loadData(realm);
notifyDataSetChanged();
}
public int getHeaderCount() {
return hasHeader() ? HEADER_COUNT : 0;
}
public int getFooterCount() {
return hasFooter() ? FOOTER_COUNT : 0;
}
public boolean isHeader(int position) {
if (hasHeader()) {
return position < HEADER_COUNT;
} else {
return false;
}
}
public boolean isFooter(int position) {
if (hasFooter()) {
return position >= getCount() + getHeaderCount();
} else {
return false;
}
}
#Override
public long getItemId(int i) {
return i;
}
#Override
public final int getItemViewType(int position) {
if (isHeader(position)) {
return ItemType.HEADER.ordinal();
} else if (isFooter(position)) {
return ItemType.FOOTER.ordinal();
} else {
return ItemType.ITEM.ordinal();
}
}
/**
* #param position the position within our adapter inclusive of headers,items and footers
* #return an item only if it is not a header or a footer, otherwise returns null
*/
public T getItem(int position) {
if (!isHeader(position) && !isFooter(position) && !mResults.isEmpty()) {
return mResults.get(position - getHeaderCount());
}
return null;
}
#Override
public final int getItemCount() {
return getHeaderCount() + getCount() + getFooterCount();
}
public final int getCount() {
return mResults.size();
}
public abstract boolean hasHeader();
public abstract boolean hasFooter();
public void setData(RealmResults<T> results) {
mResults = results;
notifyDataSetChanged();
}
protected abstract RealmResults<T> loadData(Realm realm);
public enum ItemType {
HEADER, ITEM, FOOTER;
}
}
To add items by some method or remove items by swipe to delete, we have an extension in the form of AbstractMutableRealmAdapter that looks as shown below
import android.support.v7.widget.RecyclerView;
import io.realm.Realm;
import io.realm.RealmObject;
public abstract class AbstractMutableRealmAdapter<T extends RealmObject, VH extends RecyclerView.ViewHolder>
extends AbstractRealmAdapter<T, VH> implements OnSwipeListener {
private Realm realm;
public AbstractMutableRealmAdapter(Realm realm) {
//call the superclass constructor to load data from subclasses into realmresults
super(realm);
this.realm = realm;
}
public void add(T item, boolean update) {
realm.beginTransaction();
T phraseToWrite = (update == true) ? realm.copyToRealmOrUpdate(item) : realm.copyToRealm(item);
realm.commitTransaction();
notifyItemRangeChanged(0, mResults.size());
}
#Override
public final void onSwipe(int position) {
if (!isHeader(position) && !isFooter(position) && !mResults.isEmpty()) {
int itemPosition = position - getHeaderCount();
realm.beginTransaction();
T item = mResults.get(itemPosition);
item.removeFromRealm();
realm.commitTransaction();
notifyItemRemoved(position);
}
}
}
Notice the use of the interface OnSwipeListener which looks like this
public interface OnSwipeListener {
/**
* #param position the position of the item that was swiped within the RecyclerView
*/
void onSwipe(int position);
}
This SwipeListener is used to perform a Swipe to delete inside our TouchHelperCallback which in turn is used to delete the objects from Realm directly and looks as follows
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
public class TouchHelperCallback extends ItemTouchHelper.Callback {
private final OnSwipeListener mSwipeListener;
public TouchHelperCallback(OnSwipeListener adapter) {
mSwipeListener = adapter;
}
/**
* #return false if you dont want to enable drag else return true
*/
#Override
public boolean isLongPressDragEnabled() {
return false;
}
/**
* #return true of you want to enable swipe in your RecyclerView else return false
*/
#Override
public boolean isItemViewSwipeEnabled() {
return true;
}
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//We want to let the person swipe to the right on devices that run LTR and let the person swipe from right to left on devices that run RTL
int swipeFlags = ItemTouchHelper.END;
return makeMovementFlags(0, swipeFlags);
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
mSwipeListener.onSwipe(viewHolder.getAdapterPosition());
}
}
The full implementation demo is available here for review https://github.com/slidenerd/SpamWordList/tree/spamphraser_with_realmresults_base Feel free to suggest any improvements
I replaced the notifyXXX methods with notifyDataSetChanged, RealmResults objects are live objects which means they automatically change when the data is updated, I tried calling notifyXXX methods and they caused an RecyclerView inconsistency exception, I am well aware of the fact that notifyDataSetChanged() would mess with animations, will keep you guys updated on a solution that overcomes the inconsistency error and at the same time provides a good adapter experience
Now that with Realm 0.88.2 we can make a RecyclerView adapter that updates the RecyclerView with more precision than using notifyDataSetChanged() every time. This can be accomplished by using the new ability create custom methods.
Overriding the equals method, in the realm object that will be used with the recycler adapter, is all that will be needed. (You don't actually need to override equals... but you may find that realm objects do not equal each other when you expect them to. This will lead to unnecessary recyclerview updates after running diff)
Then add Google's java-diff-utils to your gradle dependencies
compile 'com.googlecode.java-diff-utils:diffutils:1.3.0'
Using this RealmRecyclerViewAdapter implementation a copy of realmResults is made at start, and on every change to compare against future changes. Detected changes are used to update the RecyclerView as appropriate
public abstract class RealmRecyclerViewAdapter<T extends RealmObject, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
protected RealmResults<T> realmResults;
protected List<T> lastCopyOfRealmResults;
int maxDepth = 0;
private RealmChangeListener realmResultsListener;
Realm realm;
public RealmRecyclerViewAdapter(RealmResults<T> realmResults, boolean automaticUpdate) {
this(realmResults, automaticUpdate, 0);
}
/**
*
* #param realmResults
* #param automaticUpdate
* #param maxDepth limit of the deep copy when copying realmResults. All references after this depth will be {#code null}. Starting depth is {#code 0}.
* A copy of realmResults is made at start, and on every change to compare against future changes. Detected changes are used to update
* the RecyclerView as appropriate
*/
public RealmRecyclerViewAdapter(RealmResults<T> realmResults, boolean automaticUpdate, int maxDepth) {
this.realmResultsListener = (!automaticUpdate) ? null : getRealmResultsChangeListener();
if (realmResultsListener != null && realmResults != null) {
realmResults.addChangeListener(realmResultsListener);
}
this.realmResults = realmResults;
realm = Realm.getDefaultInstance();
this.maxDepth = maxDepth;
lastCopyOfRealmResults = realm.copyFromRealm(realmResults, this.maxDepth);
}
#Override
public int getItemCount() {
return realmResults != null ? realmResults.size() : 0;
}
/**
* Make sure this is called before a view is destroyed to avoid memory leaks do to the listeners.
* Do this by calling setAdapter(null) on your RecyclerView
* #param recyclerView
*/
#Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
super.onDetachedFromRecyclerView(recyclerView);
if (realmResultsListener != null) {
if (realmResults != null) {
realmResults.removeChangeListener(realmResultsListener);
}
}
realm.close();
}
/**
* Update the RealmResults associated with the Adapter. Useful when the query has been changed.
* If the query does not change you might consider using the automaticUpdate feature.
*
* #param queryResults the new RealmResults coming from the new query.
* #param maxDepth limit of the deep copy when copying realmResults. All references after this depth will be {#code null}. Starting depth is {#code 0}.
* A copy of realmResults is made at start, and on every change to compare against future changes. Detected changes are used to update
* the RecyclerView as appropriate
*/
public void updateRealmResults(RealmResults<T> queryResults, int maxDepth) {
if (realmResultsListener != null) {
if (realmResults != null) {
realmResults.removeChangeListener(realmResultsListener);
}
}
realmResults = queryResults;
if (realmResults != null && realmResultsListener !=null) {
realmResults.addChangeListener(realmResultsListener);
}
this.maxDepth = maxDepth;
lastCopyOfRealmResults = realm.copyFromRealm(realmResults,this.maxDepth);
notifyDataSetChanged();
}
public T getItem(int position) {
return realmResults.get(position);
}
public int getRealmResultsSize(){
return realmResults.size();
}
private RealmChangeListener getRealmResultsChangeListener() {
return new RealmChangeListener<RealmResults<T>>() {
#Override
public void onChange(RealmResults<T> element) {
if (lastCopyOfRealmResults != null && !lastCopyOfRealmResults.isEmpty()) {
if (realmResults.isEmpty()) {
// If the list is now empty, just notify the recyclerView of the change.
lastCopyOfRealmResults = realm.copyFromRealm(realmResults,maxDepth);
notifyDataSetChanged();
return;
}
Patch patch = DiffUtils.diff(lastCopyOfRealmResults, realmResults);
List<Delta> deltas = patch.getDeltas();
lastCopyOfRealmResults = realm.copyFromRealm(realmResults,maxDepth);
if (!deltas.isEmpty()) {
List<Delta> deleteDeltas = new ArrayList<>();
List<Delta> insertDeltas = new ArrayList<>();
for (final Delta delta : deltas) {
switch (delta.getType()){
case DELETE:
deleteDeltas.add(delta);
break;
case INSERT:
insertDeltas.add(delta);
break;
case CHANGE:
notifyItemRangeChanged(
delta.getRevised().getPosition(),
delta.getRevised().size());
break;
}
}
for (final Delta delta : deleteDeltas) {
notifyItemRangeRemoved(
delta.getOriginal().getPosition(),
delta.getOriginal().size());
}
//item's should be removed before insertions are performed
for (final Delta delta : insertDeltas) {
notifyItemRangeInserted(
delta.getRevised().getPosition(),
delta.getRevised().size());
}
}
} else {
notifyDataSetChanged();
lastCopyOfRealmResults = realm.copyFromRealm(realmResults,maxDepth);
}
}
};
}
}
Your post does not even contain a real question.
Have you checked out this post: http://gradlewhy.ghost.io/realm-results-with-recyclerview/ ?
Not sure why you wouldn't just use an ArrayList in your adapter and add all elements from the RealmResult to that list though. Could anyone explain why the solution in the blog post would be better?
Implementing the Realm Add-on from Thorben Primke is a very convenient method
for handling Recycler View applications with Realm databases. His github has good examples of the ways that it can be implemented.
I'll include mine here so you have an example. First modify your project build gradle for jitpack.io:
allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }
}
Then your module gradle to point to the library: (note , check for latest version)
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.github.thorbenprimke:realm-recyclerview:0.9.20'
Create the xml layout for a recycler view using the RealmRecyclerView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<co.moonmonkeylabs.realmrecyclerview.RealmRecyclerView
android:id="#+id/realm_recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:rrvIsRefreshable="true"
app:rrvEmptyLayoutId="#layout/empty_view"
app:rrvLayoutType="LinearLayout"
app:rrvSwipeToDelete="true"
/>
</RelativeLayout>
Now in your RealmRecycler Fragment obtain a Realm query result of RealmObjects, inflate and define a Primke RealmAdapter:
Log.i(TAG, " Obtain Filtered List");
final RealmResults <Session> realmResults = queryD.findAllSorted(
"sessionId", Sort.DESCENDING);
Log.i(TAG, " Inflate realm List");
View view = inflater.inflate(R.layout.realm_card_recycler2, null);
Log.i(TAG, " Define and configure SessionRealmAdapter");
SessionRealmAdapter sessionRealmAdapter =
new SessionRealmAdapter(getActivity(), realmResults, true, true);`enter code here`
RealmRecyclerView realmRecyclerView =
(RealmRecyclerView) view.findViewById(R.id.realm_recycle_view);
realmRecyclerView.setAdapter(sessionRealmAdapter);
Finally configure the Realm Adapter for whatever you want for actions. I've got a couple for clicks and turned on the swipe to delete for deleting realm records.
public class SessionRealmAdapter
extends RealmBasedRecyclerViewAdapter<Session, SessionRealmAdapter.ViewHolder> {
public class ViewHolder extends RealmViewHolder {
public TextView sessionTextView;
public ViewHolder(FrameLayout container) {
super(container);
this.sessionTextView = (TextView) container.findViewById(R.id.session_text_view);
}
}
public SessionRealmAdapter(
Context context,
RealmResults<Session> realmResults,
boolean automaticUpdate,
boolean animateResults) {
super(context, realmResults, automaticUpdate, animateResults);
}
#Override
public ViewHolder onCreateRealmViewHolder(ViewGroup viewGroup, int viewType) {
View v = inflater.inflate(R.layout.session_simple_view, viewGroup, false);
return new ViewHolder((FrameLayout) v);
}
#Override
public void onBindRealmViewHolder(ViewHolder viewHolder, int position) {
final Session singleSession = realmResults.get(position);
viewHolder.sessionTextView.setText(singleSession.gettMethod());
viewHolder.sessionTextView.setOnClickListener(
new View.OnClickListener(){
#Override
public void onClick(View v){
selectSession(singleSession);
showMessage(" Selected "+singleSession.gettMethod());
}
}
);
viewHolder.sessionTextView.setOnLongClickListener(
new View.OnLongClickListener(){
#Override
public boolean onLongClick(View v){
showInformationDialog(singleSession);
showMessage("Long click selected for "
+singleSession.getSessionTitle());
return true;
}
}
);
}
}

Categories

Resources