recyclerview item mishap(undesirable behaviour) in android studio - android

I have a recycler view with a list of items and each of those items have checkboxes attached to them. When a checkbox is checked, it behaves properly and there is no problem with unwanted items getting checked. But when a checked item is deleted, then the unwanted item gets checked.
My Adapter Class :
public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.TaskHolder> {
private static List<Task> tasks = new ArrayList<>();
private static OnItemClickListener listener;
private static TaskAdapter adapter = new TaskAdapter();
#NonNull
#Override
public TaskHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.task_item, parent, false);
return new TaskHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull TaskHolder holder, int position) {
Task currentTask = tasks.get(position);
holder.a_tname.setText(currentTask.getTname());
holder.a_tdate.setText(currentTask.getTDate());
holder.a_ttime.setText(currentTask.getTTime());
holder.a_tprior.setText(currentTask.getTprior());
holder.bind(tasks.get(position));
holder.bind2(tasks.get(position));
}
#Override
public int getItemCount() {
return tasks.size();
}
public void setTasks(List<Task> tasks) {
this.tasks = tasks;
Collections.sort( tasks, Task.comparepriority);
notifyDataSetChanged();
}
public Task getTaskAt(int position){
return tasks.get(position);
}
class TaskHolder extends RecyclerView.ViewHolder {
private final TextView a_tname;
private final TextView a_tdate;
private final TextView a_ttime;
private final TextView a_tprior;
ImageView priorityIndicator;
CheckBox checkbox;
public TaskHolder(View itemView) {
super(itemView);
a_tname = itemView.findViewById(R.id.a_tname);
a_tdate=itemView.findViewById(R.id.a_tdate);
a_ttime = itemView.findViewById(R.id.a_ttime);
a_tprior = itemView.findViewById(R.id.a_tprior);
priorityIndicator = itemView.findViewById(R.id.priorityIndicator);
checkbox = itemView.findViewById(R.id.checkbox);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if(listener!=null&&position!=RecyclerView.NO_POSITION){
listener.onItemClick(tasks.get(position));
}
}
});
}
private void bind(Task task){
int drawableId;int red = R.color.red;int yellow = R.color.yellow;int green = R.color.green;
int color1 = ContextCompat.getColor(a_tprior.getContext(), red);
int color2 = ContextCompat.getColor(a_tprior.getContext(),yellow);
int color3 = ContextCompat.getColor(a_tprior.getContext(),green);
switch(task.t_prior){
case "1": drawableId = R.drawable.ic_baseline_priority_high_24;
a_tprior.setTextColor(color1);
break;
case "2": drawableId = R.drawable.ic_baseline_priority_middle_24;
a_tprior.setTextColor(color2);
break;
case "3" : drawableId = R.drawable.ic_baseline_low_priority_24;
a_tprior.setTextColor(color3);
break;
default: drawableId = R.drawable.ic_baseline_crop_square_24;
}
priorityIndicator.setImageDrawable(ContextCompat.getDrawable(priorityIndicator.getContext(),drawableId));
}
public void bind2(Task task){
final boolean[] checked = {true};
checkbox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(checkbox.isChecked()) {
String pos = Integer.valueOf(getAdapterPosition()).toString();
PreferenceManager.getDefaultSharedPreferences(checkbox.getContext()).edit().
putBoolean("checkbox" + pos , checked[0]).apply();
Toast.makeText(checkbox.getContext(), "Way to go! Now swipe to delete", Toast.LENGTH_LONG).show();
}
else {
checked[0] =false;
String pos = Integer.valueOf(getAdapterPosition()).toString();
PreferenceManager.getDefaultSharedPreferences(checkbox.getContext()).edit().
putBoolean("checkbox" + pos, checked[0]).apply();
}
}
}); String pos = Integer.valueOf(getAdapterPosition()).toString();
boolean cb = PreferenceManager.getDefaultSharedPreferences(checkbox.getContext()).getBoolean
("checkbox" + pos, false);
checkbox.setChecked(cb);
}
}
public interface OnItemClickListener {
void onItemClick(Task ta);
}
public void setOnItemClickListener(OnItemClickListener listener){
this.listener = listener;
}
}
My code to delete in HomeFragment.java -
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull
RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
new AlertDialog.Builder(getActivity())
.setMessage("Do you want to delete this task?")
.setPositiveButton("Delete", ((dialog, which) ->
taskViewmodel.delete(adapter.getTaskAt(viewHolder.getAdapterPosition()))))
.setNegativeButton("Cancel", ((dialog, which) -> adapter.notifyItemChanged(position)))
.setOnCancelListener(dialog -> adapter.notifyItemChanged(position))
.create().show();
}
}).attachToRecyclerView(recyclerView);
Edit : I guess the problem is with the code associated with saving the state of the checkbox because the checkbox at a particular item position is checked so when the item is deleted, the below item takes its place and so it gets checked. Suppose item at 2nd position is checked, and I delete that item, then the item at the 3rd position takes its place and so that gets checked. I need to know how to resolve this.May i know what changes should i make to rectify this problem?
Thankyou

back at the day, there was no delete intuition, now since you're storing the state of whether an item checked or not in SharedPreferences using its position, if you have 3 items where position =2 is checked, and you delete this checked item at pos=2, then the third item would take its place behaving as new item at pos=2, that's why when you delete one, all checked states will get shifted.
I guess there's no option here but to use some identifier in your Task class, where one Task is uniquely identified using this number/string and you use that unique identifier to store the items state in your SharedPreferences as a key.
a quick cheap way to do it is to make Task class behave like how Room database identify its automatic unique int/long identifiers.
the way to do it is by
defining a static int/long counter field in your Task class that identifies how many ids you used so far so to not repeat any id that was taken before (even if it was deleted and not used now)
define a private int/long id field in the Task class, while in the Task class constructor when you're initializing a new Task you would increment the static counter field and use this new value as a value for your private id for your newly created Task.
N.B : you shouldn't increment the static value and assign a new id for every created task if the one you're creating an object for is an old one that you retrieved from SharedPreferences/Database that already have an old id, for that case you might have two constructors one that accepts an old id as paramter and one that increments the taskCounter and get a new id, you might also have two constructors that one calls the other incase you have some other paramteres you're passing to the task object at creation and you want to avoid duplicating code in both constructors.
that way when you're checking status using SharedPreferences for some task, you would use the private id value of Task instead of its position.
your Task class might look like this (in case of two constructors without any additional code in the constructor):
public class Task {
public static int tasksCounter =0;
public int taskId ;
...
//constructor for a new Task
public Task(){
this.taskId= ++tasksCounter ;
}
//constructor for an old Task
public Task(int oldId){
this.taskId= oldId ;
}
your Task class might look like this (in case of two constructors and you want to avoid code dublication):
public class Task {
public static int tasksCounter =0;
public int taskId ;
//you'd call that one if you're creating a completely new Task and it will call
the other constructor for you
public Task(){
Task(++tasksCounter)
}
//you'd call that one if you're creating a Task that you already have an id for
public Task(int id){
this.taskId= id ;
//some other code here
}
and in your adapter when you ask about whether its checked or not it would be like this :
PreferenceManager.getDefaultSharedPreferences(checkbox.getContext()).getBoolean
("checkbox" + task.taskId, false);
and when you the checkbox status get changed you'd change its value in preferences likewise using the task.taskId instead of position.
that of course would raise another problem that every time you start your application that static field will get reset to 0 again.
so you should store its value too in sharedpreferences maybe either
when you create a new Task, its value will be incremented so you should store the new value
or
just before the activity or fragment getting destroyed by overriding the method onDestroy() in either the activity or the fragment, you should store the last value it had
and when you start your activity or fragment you need to fetch it from sharedpreferences and assign it to Task.tasksCounter.
N.B : just incase you don't know, you get the value of static field by calling the class it self and you don't need to create a new object of this class to get its value, calling the next code is sufficent to get and edit its value :
Task.tasksCounter
and at the end,
since you have your complex data now (Task class), I would highly suggest to stop using SharedPreferences for storing everything and you start reading and switching to Room Database
Room Database provides you with necessary storing abilities of a Database including :
having an autoincrement identifier without having you worried about their values
storing and getting your data with just one line of code with a simple query instead of calling get with keys a 100 times.
with changing your Task class into an entity that have an autoincrement field you'll be good to go and ready to use Room to store your tasks.

I am not much familiar with lambda function so I am sharing how I would have did the same task.I tried this code for my own app and it worked perfectly fine.
Check the below code:
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAbsoluteAdapterPosition();//getAdapterPosition is depreciated, use getAbsoluteAdapterPosition
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setMessage("Do you want to delete this task?");
builder.setPositiveButton("Delete",new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
taskViewmodel.delete(adapter.getTaskAt(viewHolder.getAdapterPosition()))
adapter.notifyItemRemoved(position);
}
});
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
#Override
public void onCancel(DialogInterface dialog) {
adapter.notifyItemChanged(position);
}
}).show();
}
}).attachToRecyclerView(recyclerView);

Related

How to change the appearance of an android view with a click listener and background worker?

I have an android app that users can use to check into a course at a certain time. Their daily schedule shows up on a RecyclerView as a series of CardViews. Each CardView has basic information about each class, including instructor name, course title, attendance status, etc..
The app communicates with a MySQL database through a php script which is called from an AsyncTask background worker. In the PostExecute method, the app receives a result from the php script ("Present" if checking in on time, "Tardy" if late, "Absent" if totally missed) to show in an AlertDialog. The PostExecute method also sets a String variable to equal the result.
The background worker is called from a click listener like so:
baf.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int bright = status.getResources().getColor(R.color.colorPresent);
status.setBackgroundColor(bright);
lol = getAdapterPosition()+1;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
Calendar c = Calendar.getInstance();
String time = sdf.format(c.getTime());
Log.d("Current Time",time);
String type="checkin";
PresentBackgroundWorker presentBackgroundWorker = new PresentBackgroundWorker(getActivity());
presentBackgroundWorker.execute(type,date,time, "t"+String.valueOf(lol));
String aaa = ontime;
status.setText(aaa);
}
});
The variable ontime (global variable) is the result from the php script, and its value is set from the background worker like so:
protected void onPostExecute(String result) {
writeitout(result);
alertDialog.setMessage(result);
alertDialog.show();
}
private void writeitout(String string) {
ontime = string;
Log.d("STATS",ontime);
}
Based on the Log.d() commands, the ontime variable is changing appropriately, but the change to the 'status' TextView is delayed by one click. That is, the result for the previous course, shows up for the present course. How do I make sure the changes in the ontime variable show up on time?
EDIT: ADDED ORIGINAL COURSEADAPTER
public class CourseAdapter extends RecyclerView.Adapter<CourseAdapter.MyViewHolder> {
private List<Course> courseList;
public CourseAdapter(List<Course> sc) { this.courseList = sc; }
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView title, teacher, type, status;
FloatingActionButton baf;
View subIten;
int lol;
public MyViewHolder(View view) {
super(view);
title = (TextView) view.findViewById(R.id.texttitle);
teacher = (TextView) view.findViewById(R.id.textteach);
type = (TextView) view.findViewById(R.id.texttype);
baf = (FloatingActionButton) view.findViewById(R.id.fab);
status = view.findViewById(R.id.textstatus);
subIten = (View) view.findViewById(R.id.sub_item);
}
private void bind(Course course) {
title.setText(course.getTitle());
teacher.setText(course.getTeacher());
type.setText(course.getType());
baf.setImageResource(course.getPhotoID());
baf.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int bright = status.getResources().getColor(R.color.colorPresent);
status.setBackgroundColor(bright);
lol = getAdapterPosition()+1;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
Calendar c = Calendar.getInstance();
String time = sdf.format(c.getTime());
Log.d("Current Time",time);
String type="checkin";
PresentBackgroundWorker presentBackgroundWorker = new PresentBackgroundWorker(getActivity());
presentBackgroundWorker.execute(type,date,time, "t"+String.valueOf(lol));
}
});
}
}
#Override
public CourseAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.cardview, parent, false);
MyViewHolder viewHolder = new MyViewHolder(itemView);
return viewHolder;
}
#Override
public void onBindViewHolder(final CourseAdapter.MyViewHolder holder, final int position) {
final Course course = courseList.get(position);
holder.bind(course);
}
public int getItemCount() {
return courseList.size();
}
}
The problem is you are saving the asynctask result in a variable and then later accessing it on button click.
So, what basically is happening here:
You click on a button
you start asynctask
your asynctask is still working. So, onTime still has no string. And, status.setText() is being called even before the asynctask produce result and save it in onTime.
your asynctask has finished it's job and saved the result in onTime, but the button has already finished it's rest of the code, so textview doesn't get the latest change.
you again click on the button
your asynctask again starts to work, but this time onTime has a value because previous asynctask saved it's result in it. so when status.setText() is being called, it set's the value of onTime (which holds the result from previous asynctask)
So, to fix the issue, you shouldn't update the textview on button click, rather update them inside asynctask's onPostExecute()
Just change your onPostExecute() like this and you have your solution.
protected void onPostExecute(String result) {
writeitout(result);
status.setText(result); //updating the textview directly here
alertDialog.setMessage(result);
alertDialog.show();
}
Also, remove this two lines from your baf clickListener
String aaa = ontime;
status.setText(aaa);

UnsupportedOperationException: Alert dialogcrashing when positive button clicked

So i have this recycler view in which the last item is supposed to show a dialog on its click. the dialog is further comprising of 2 buttons, positive and negetive( the default ones).
On the press of Positive button, the dialog is supposed to trigger a callback which will add a new entry in the recycler view. On the press of the negetive button, the dialog simply dismisses.
Everything is working fine until when the user clicks positive button. the app crashes with the following log:
2019-08-13 18:27:35.668 29482-29482/z.y.x E/AndroidRuntime: FATAL EXCEPTION: main
Process: z.y.x, PID: 29482
java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at z.y.x.dashboard_files.dashboard_fragment.QuantityButtonsAdapter.addItemInCentre(QuantityButtonsAdapter.java:85)
at z.y.x.dashboard_files.dashboard_fragment.DashboardFragment$2$1.onPositiveButtonClick(DashboardFragment.java:144)
at z.y.x.dashboard_files.dashboard_fragment.DashboardFragment$QuantityDialog$3.onClick(DashboardFragment.java:253)
at com.android.internal.app.AlertController$ButtonHandler.handleMessage(AlertController.java:172)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6692)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Here is the custom dialog class:
public static class QuantityDialog {
#Nullable
private QuantityButtonModel currentData;
private AlertDialog.Builder builder;
public interface OnPositiveClickListener {
void onPositiveButtonClick(QuantityButtonModel data);
}
public QuantityDialog(Context ctx) {
currentData = QUANTITY_GLASS;
View dialogView = createView(ctx);
builder = new AlertDialog.Builder(ctx)
.setView(dialogView)
.setCancelable(false)
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
}
// public void show() {
// show(null);
// }
#SuppressLint("InflateParams")
private View createView(Context ctx) {
View v = LayoutInflater.from(ctx)
.inflate(R.layout.dialog_new_quantitiy_btn, null);
//init ui
SeekBar seekQty = v.findViewById(R.id.seekbar_qty);
final TextView tvQty = v.findViewById(R.id.tv_qty_text_dialog);
final ImageView ivQty = v.findViewById(R.id.iv_qty_icon_dialog);
//init data and defaults
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
seekQty.setMin(0);
}
seekQty.setMax(QUANTITY_MAX);
if (currentData != null) {
seekQty.setProgress(currentData.getQty());
tvQty.setText(String.format(Locale.ROOT, "%d ml", currentData.getQty()));
ivQty.setImageResource(currentData.getQtyImage());
}
//init listener
seekQty.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
tvQty.setText(String.format(Locale.ROOT, "%d ml", progress));
int resID = getResForQty(progress);
ivQty.setImageResource(resID);
currentData = new QuantityButtonModel(resID, progress);
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
return v;
}
public void setOnPositiveClickListener(#Nullable final OnPositiveClickListener listener) {
builder.setPositiveButton("Done", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
if (listener != null) {
listener.onPositiveButtonClick(currentData);
}
dialogInterface.dismiss();
}
});
}
public void show() {
builder.show();
}
}
Here I am setting a listener to my recycler view's adapter. the listener is made in a way that if item is at any positon except last, it will show a toast, else it will show a dialog. The dialog is also not being reused(bad naming, i changed it later), but rather getting generated on every click and a click listener being attached at the same point, i.e on rv item's click.
adpButtons.setClickListener(new QuantityButtonClickListener() {
#Override
public void onItemClick(int qty) {
Toast.makeText(
fragView.getContext(),
"add:" + qty + " to shared preferences",
Toast.LENGTH_SHORT)
.show();
}
#Override
public void onAddNewItemClick() {
QuantityDialog reusableQuantityDialog;
reusableQuantityDialog = new QuantityDialog(fragView.getContext());
reusableQuantityDialog.setOnPositiveClickListener(
new OnPositiveClickListener() {
#Override
public void onPositiveButtonClick(QuantityButtonModel data) {
adpButtons.addItemInCentre(data);
}
});
reusableQuantityDialog.show();
}
});
I hope i tried to explain this clearly. I am guessing there is some kind of callback hell that is causing the problem. But i even tried reusing the dialogs which still caused the same error.
Please help.
Edit: Here is the adapter code:
public class QuantityButtonsAdapter extends RecyclerView.Adapter<QuantityButtonsAdapter.RvHolder> {
#NonNull
private List<QuantityButtonModel> buttonModelList;
private QuantityButtonClickListener clickListener;
QuantityButtonsAdapter() {
this(new ArrayList<QuantityButtonModel>(), null);
}
private QuantityButtonsAdapter(#NonNull List<QuantityButtonModel> buttonModelList,
QuantityButtonClickListener listener) {
this.buttonModelList = buttonModelList;
this.clickListener = listener;
}
#NonNull
#Override
public RvHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater
.from(parent.getContext())
.inflate(R.layout.layout_recycler_buttons, parent, false);
return new RvHolder(v);
}
#Override
public void onBindViewHolder(#NonNull RvHolder holder, int pos) {
QuantityButtonModel data = buttonModelList.get(pos);
//is last is a check based on which our code to add new data will get triggerred
boolean isLast = (pos==(buttonModelList.size()-1));
holder.bind(data.getQtyImage(), data.getQty(), clickListener, isLast);
}
#Override
public int getItemCount() {
return buttonModelList.size();
}
#NonNull
public List<QuantityButtonModel> getButtonModelList() {
return buttonModelList;
}
public void setButtonModelList(#NonNull List<QuantityButtonModel> buttonModelList) {
this.buttonModelList = buttonModelList;
notifyDataSetChanged();
}
public QuantityButtonClickListener getClickListener() {
return clickListener;
}
public void setClickListener(QuantityButtonClickListener clickListener) {
this.clickListener = clickListener;
notifyDataSetChanged();
}
public void addItemInCentre(QuantityButtonModel model) {
//int pos= buttonModelList.size()/2;
buttonModelList.add(model);
notifyDataSetChanged();
}
class RvHolder extends RecyclerView.ViewHolder {
ImageButton ibtQty;
TextView tvQty;
RvHolder(#NonNull View itemView) {
super(itemView);
ibtQty = itemView.findViewById(R.id.ibt_qty_btn);
tvQty = itemView.findViewById(R.id.tv_qty_text);
//ibtQty.setOnTouchListener(getMyItemTouchListener());
}
void bind(int qtyRes, final int qty, final QuantityButtonClickListener listener, final boolean isLast) {
ibtQty.setImageResource(qtyRes);
tvQty.setText(String.format(Locale.getDefault(), "%d ml", qty));
ibtQty.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (isLast) {
listener.onAddNewItemClick();
} else {
listener.onItemClick(qty);
}
showButtonPressAnimation(view);
}
});
}
}
interface QuantityButtonClickListener {
void onItemClick(int qty);
void onAddNewItemClick();
}
public static void showButtonPressAnimation(View view) {
final float shrinkTo = 0.90f;
final long duration = 100;
ScaleAnimation grow = new ScaleAnimation(
shrinkTo, 1,
shrinkTo, 1,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
grow.setDuration(duration / 2);
ScaleAnimation shrink = new ScaleAnimation(
1, shrinkTo,
1, shrinkTo,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
shrink.setDuration(duration / 2);
grow.setStartOffset(duration / 2);
AnimationSet set = new AnimationSet(true);
set.setInterpolator(new LinearInterpolator());
set.addAnimation(shrink);
set.addAnimation(grow);
view.startAnimation(set);
}
}
Didn't read through your Dialog source too much, but I don't expect any issue with it.
I guess the issue is adpButtons.addItemInCentre(data);, which you don't show in the question, but the addItemInCentre implementation is calling List.add on some list instance which is missing the add implementation.
Maybe it's some deserialized list, or some immutable one, can't recall from head any example, but I think there's some which can be created from Strings in XML, etc...
To have add available, you need list instance similar to ArrayList, etc...
So without showing this part of source, how the list (you try to add to) is defined and instantiated, it's difficult to tell precisely, what is the issue.
Edit: it's not clear from OP responses what precisely helped, but among the ways already mentioned above (deserialization, reflection), one can get the List<> instance with fixed size also by using the Arrays.asList(..) utility, which does NOT produce ArrayList, but specialized implementation of List<> which does bridge the original array with List<> interface (you can modify the original array through List<>.set(index, value) call), and thus it does refuse to do add/remove like operations, because underlying classic Type[] array is fixed size and the add/remove then would require new array, invalidating the original reference = makes no sense for asList functionality.
I.e. if you need implementation of java.util.List<E> interface which supports also add and remove like functionality of List, you must make sure the instance of your List<> is provided by class capable of these operations, like java.util.ArrayList<E> or java.util.Vector<E>, and avoid other implementations which do not support add/remove (like the one produced by Arrays.asList(..) call).
Also if you are writing Adapter, where you know you will need add/remove features of List<>, you may sometimes want to enforce particular List implementation, like ArrayList, on the API level, so when you later try to use that adapter with other implementation, it will fail at compile time. (then again having such specific API also prevents usage of other implementations which do support add/remove, and unfortunately the List<E> interface doesn't have direct interface derivative, which would ensure the add/remove are mandatory (they are optional in original List<E> interface), and could be used to narrow the API like that.

RecyclerView getAdapterPosition() returns -1 on a callback so I can't show the new appearance for the item

Each item on my RecyclerView has a button that has three states: OPEN, LOADING, and CLOSED.
Initially all the buttons are in the OPEN state. When a button is clicked, the state is changed to LOADING and a network call is performed in the background. After the network call succeeds, the button state should be changed to CLOSED.
So in my adapter I used the following:
holder.button.setOnClickListener(v -> {
holder.state = LOADING;
notifyItemChanged(holder.getAdapterPosition()); /* 1 */
callNetwork(..., () -> {
/* this is the callback that runs on the main thread */
holder.state = CLOSED;
notifyItemChanged(holder.getAdapterPosition()); /* 2 */
});
});
The LOADING state is always visualized correctly at /* 1 */ because getAdapterPosition() gives me the correct position.
However, the CLOSED state of the button is never visualized, because getAdapterPosition at /* 2 */ always returns -1.
I might understand getAdapterPosition() wrongly in this case.
How do I refresh the appearance of an item on a callback?
From the docs:
Note that if you've called notifyDataSetChanged(), until the next
layout pass, the return value of this method will be NO_POSITION
NO_POSITION is a constant whose value is -1. This might explain why you are getting a return value of -1 here.
In any case, why don't you find the position of the model in the underlying dataset and then call notifyItemChanged(int position)? You could save the model as a field in the holder.
For example:
public class MyHolder extends RecyclerView.ViewHolder {
private Model mMyModel;
public MyHolder(Model myModel) {
mMyModel = myModel;
}
public Model getMyModel() {
return mMyModel;
}
}
holder.button.setOnClickListener(v -> {
holder.state = LOADING;
notifyItemChanged(holder.getAdapterPosition());
callNetwork(..., () -> {
/* this is the callback that runs on the main thread */
holder.state = CLOSED;
int position = myList.indexOf(holder.getMyModel());
notifyItemChanged(position);
});
});
Alternatively you can just ignore if the position is -1, like this:
holder.button.setOnClickListener(v -> {
holder.state = LOADING;
int preNetworkCallPosition = holder.getAdapterPosition();
if (preNetworkCallPosition != RecyclerView.NO_POSITION) {
notifyItemChanged(preNetworkCallPosition);
}
callNetwork(..., () -> {
/* this is the callback that runs on the main thread */
holder.state = CLOSED;
int postNetworkCallPosition = holder.getAdapterPosition();
if (postNetworkCallPosition != RecyclerView.NO_POSITION) {
notifyItemChanged(postNetworkCallPosition);
}
});
});
getAdapterPosition(); It will always return -1 when recyclerview makes layout calculations. You are calling this methods inside ViewHolder.. It means RecyclerView is doing calculations.
If you need position inside click actions of view, call it in the public void onClick(final View v) method for example:
"#Override
public void onBindViewHolder(#NonNull final ViewHolder holder, final int position) {
final Students user = mUsers.get(position);
holder.Name.setText(user.getFullname());
holder.Index.setText(user.getIndex_number());
if (user.getThumbnail().equals("default")) {
holder.profile_image.setImageResource(R.drawable.profile_pic);
} else {
Picasso.get().load(user.getThumbnail())
.placeholder(R.drawable.profile_pic)
.into(holder.profile_image);
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
**list_user_id = mUsers.get(position).getId();**
Intent Sub = new Intent(mContext, UserProfileActivity.class);
Sub.putExtra("user_id1", list_user_id);
mContext.startActivity(Sub);
BUT NOT
getAdapterPosition(); It will always return -1 when recyclerview makes layout calculations. You are calling this methods inside ViewHolder.. It means RecyclerView is doing calculations.
If you need position inside click actions of view, call it in the public void onClick(final View v) method for example:
#Override
public void onBindViewHolder(#NonNull final ViewHolder holder, final int position) {
final Students user = mUsers.get(position);
holder.Name.setText(user.getFullname());
holder.Index.setText(user.getIndex_number());
**list_user_id = mUsers.get(position).getId();**
if (user.getThumbnail().equals("default")) {
holder.profile_image.setImageResource(R.drawable.profile_pic);
} else {
Picasso.get().load(user.getThumbnail())
.placeholder(R.drawable.profile_pic)
.into(holder.profile_image);
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
Intent Sub = new Intent(mContext, UserProfileActivity.class);
Sub.putExtra("user_id1", list_user_id);
mContext.startActivity(Sub);

Unable to refresh listView from other class

In my main fragment, I have a listView called notesListView. noteAdapter populates notesListView. When user long clicks on one of the notesListView's elements, a dialog shows up and asks if user really wants to remove an item. If he agrees, then that item is removed from the database. If not - then life goes on.
The issue is that my Dialog is other class (other Fragment). For this class, I pass my database object and noteAdapter object as well, so it could remove item from database and then notify noteAdapter that data has changed. Sounds good enough, but it doesn't work, and I have absolutely no idea why. Give it a look please and help me out.
This is a method in mainFragment, which handles the mentioned listView:
public void handleNotes(final ListView notesListView) {
if (database.getNoteCount() != 0) {
notesListView.setAdapter(noteAdapter);
notesListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
TextView textViewId = (TextView) view.findViewById(R.id.textViewId);
DeleteNoteFragment newFragment = new DeleteNoteFragment(database, noteAdapter, Integer.parseInt(textViewId.getText().toString()));
newFragment.show(getActivity().getSupportFragmentManager(), "deleteConfirmation");
return false;
}
});
}
}
As you can see, DeleteNoteFragment is being created and then shown.
Lets look at DeleteNoteFragment itself:
public class DeleteNoteFragment extends DialogFragment {
private Database database;
private NoteAdapter noteAdapter;
private int i;
public DeleteNoteFragment(Database database, NoteAdapter noteAdapter, int i) {
this.database = database;
this.noteAdapter = noteAdapter;
this.i = i;
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.dialog_delete_note)
.setPositiveButton(R.string.dialog_delete_confirm, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
database.removeNote(i);
noteAdapter.notifyDataSetChanged();
Toast.makeText(getActivity(), "Note deleted successfully!", Toast.LENGTH_LONG).show();
}
})
.setNegativeButton(R.string.dialog_delete_denny, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
// Create the AlertDialog object and return it
return builder.create();
}
}
Maybe you can spot where I am making a mistake, or got any tips how to solve this issue?
You are deleting the data in database but not in the adapter:
database.removeNote(i);
noteAdapter.notifyDataSetChanged();
Creates a method in the adapter to get the list that you have in the adapter. Something like:
database.removeNote(i);
noteAdapter.getListOfItems().remove(i);
noteAdapter.notifyDataSetChanged();
notifyDataSetChanged - "Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself." It doesn't reload data from database. You still have to remove the item from the adapter by calling remove method and then call notifyDataSetChanged

Checkboxes not changing checked status in UI?

I have a ListView that I am trying to use with a checkable item list. I am calling the toggle() method on my list item class in the ArrayAdaptor, but the checkbox is not being ticked in the UI. However, I can confirm that the correct items are being selected in the UI, and that the "isChecked()" status reports back correctly--just the UI doesn't change at all. Are there any special methods I need to call to update the checkbox graphic for the UI?
To put it another way--how do I programmatically tell the UI that a checkbox should show up as "checked"? It seems this should be a very simple process, but I've been having a lot of trouble finding this information.
Code is as follows:
For the item data class in the ArrayAdaptor:
public class SelectedItemData extends CheckedTextView {
public String _item_name;
public String getItemName()
{
return _item_name;
}
public void setItemName(String in_name)
{
_item_name = in_name;
}
// methods
public SelectedItemData(Context context) {
super(context);
init();
}
public void init()
{
this._item_name = "UNSET";
this.setChecked(false);
}
#Override
public String toString()
{
return _item_name;
}
}
In the Activity class (located within the onCreate method):
_selectedItemsListView = (ListView) findViewById(R.id.selected_items_listview);
_selectedItemsListView.setItemsCanFocus(false);
_selectedItemsListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
_selectedItemsListView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> listview, View view, int position, long id) {
#SuppressWarnings("unchecked")
ArrayAdapter<SelectedItemData> itemsAdapter = (ArrayAdapter<SelectedItemData>)_selectedItemsListView.getAdapter();
SelectedItemData selectedItem = itemsAdapter.getItem(position);
selectedItem.toggle();
Toast.makeText(view.getContext(), "Item " + selectedItem.getItemName() + " Selected!", Toast.LENGTH_SHORT).show();
Log.d(TAG, "Is Item Checked? " + selectedItem.isChecked());
_selectedItemsListView.setAdapter(itemsAdapter);
}
});
Any guidance on how to enable the UI to properly display that one of the items have been selected/checked would be great. Thanks!
You have to update your adapter with the new item and set it to the listview. (itemsAdapter.setItem(item,position))

Categories

Resources