I have made a chat app where I have used a RecyclerView. Messages can be either text messages or audio messages. Everything is working fine except when I change the timer text on the TextView (for audio player layout I have made) , of how long has the song been played. I do this in a Runnable. But when I scroll the RecyclerView, the timer TextView changes text at random position.
Here is how I am changing the TextView text:
public void updateTimer(final int position) {
View view = mRecyclerViewChat.getLayoutManager().findViewByPosition(position);
timer = (TextView) view.findViewById(R.id.timer);
r = new Runnable() {
public void run() {
int currentDuration;
if (player.isPlaying()) {
currentDuration = player.getCurrentPosition();
timer.setText("" + milliSecondsToTimer((long) currentDuration));
timer.postDelayed(this, 1000);
} else {
timer.removeCallbacks(this);
}
}
};
timer.post(r);
}
Here position is the position value I am getting from the onBindViewHolder.
EDIT
Here is the onBindViewHolder
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (TextUtils.equals(mChats.get(position).senderUid,
FirebaseAuth.getInstance().getCurrentUser().getUid())) {
if (mChats.get(position).mediaUrlLocal == null) {
configureMyChatViewHolder((MyChatViewHolder) holder, position);
} else {
configureMyChatMediaViewHolder((MyChatMediaViewHolder) holder, position);
}
} else {
if (mChats.get(position).mediaUrlLocal == null) {
configureOtherChatViewHolder((OtherChatViewHolder) holder, position);
} else {
configureOtherChatMediaViewHolder((OtherChatMediaViewHolder) holder, position);
}
}
}
and here is the playMedia method which is called from configureMyChatMediaViewHolder method:
private void playMyMedia(final MyChatMediaViewHolder myChatViewHolder, final Chat chat, final int position) {
MediaMetadataRetriever metaRetriever = new MediaMetadataRetriever();
metaRetriever.setDataSource(chat.mediaUrlLocal);
String duration =
metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
long dur = Long.parseLong(duration);
String seconds = String.valueOf((dur % 60000) / 1000);
String minutes = String.valueOf(dur / 60000);
String out = minutes + ":" + seconds;
myChatViewHolder.timer.setText(out);
if (chat.isPlay) {
myChatViewHolder.play.setVisibility(View.GONE);
myChatViewHolder.pause.setVisibility(View.VISIBLE);
} else {
myChatViewHolder.play.setVisibility(View.VISIBLE);
myChatViewHolder.pause.setVisibility(View.GONE);
}
myChatViewHolder.play.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
chat.isPlay = !chat.isPlay;
if (previousChat != position && previousChat != -1) {
previousChatObj = mChats.get(previousChat);
}
previousChat = position;
myChatViewHolder.play.setVisibility(View.GONE);
myChatViewHolder.pause.setVisibility(View.VISIBLE);
callback.onPlayClickListener(chat, previousChatObj, position);
}
});
myChatViewHolder.pause.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
chat.isPlay = !chat.isPlay;
myChatViewHolder.play.setVisibility(View.VISIBLE);
myChatViewHolder.pause.setVisibility(View.GONE);
callback.onPauseClickListener(chat, position);
}
});
}
I have only one media player instance in the fragment from which the adapter for chat is set.
Recycler view is re-using the views that are not currently visible. So if your audio player starts updating one Text View , when you scroll that textview is the same reference but with different text ( song ), but your runnable instance is still active and doing its job to update the Text View with same reference.
You should create a custom Runnable class which
will hold a position value and will be only responsible for the
TextView it should update.
Other approach ( less efficient ) is to use ListView and not to re-use the cells to create new one for each item. This will cause performance issues
If you can send some code i would like to help you out.
*** EDITED ****
Here is some code part that can make things more clear
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.songDurationView.setTag(position);
//TODO: implement some more logic here and start the MusicSongRunnable
}
class MusicSongRunnable implements Runnable {
int positionOfSong;
TextView textView;
public MusicSongRunnable(int positionOfSong, TextView textView) {
this.positionOfSong = positionOfSong;
this.textView = textView;
}
#Override
public void run() {
if (player.isPlaying() && positionOfSong == textView.getTag()) {
//TODO: update the song;
}
}
It is not clear where you call updateTimer, but I assume that you call it from within callback.onPlayClickListener(..) and callback.onPauseClickListener(..).
First, do not pass it the position you've got as a parameter in click listeners, but as documentation states you should use ViewHolder.getAdapterPosition() method. Note, that when you use the value of the position you should also check that it is different from RecuclerView.NO_POSITION each time and only then use it. Make your position parameter not final in all methods. It will prevent you from making mistakes. Each time you want to use position in click listeners use myChatViewHolder.getAdapterPosition()
Second, since you cache position value in the Runnable anyway, this is probably not enough. So you should pass myChatViewHolder.timer as a parameter to updateTimer and get rid of first two lines. In the end you call updateTimer like this:
updateTimer(myChatViewHolder.timer);
and your 'updateTimer' now is:
public void updateTimer(final TextView timer) {
r = new Runnable() {
public void run() {
int currentDuration;
if (player.isPlaying()) {
currentDuration = player.getCurrentPosition();
timer.setText("" + milliSecondsToTimer((long) currentDuration));
timer.postDelayed(this, 1000);
} else {
timer.removeCallbacks(this);
}
}
};
timer.post(r);
}
More from the documentation on this matter:
RecyclerView will not call onBindViewHolder() method again if the position of the item changes in the data set unless the item itself is invalidated or the new position cannot be determined. For this reason, you should only use the position parameter while acquiring the related data item inside this method and should not keep a copy of it.
Related
I have a list view of songs with play and pause button in every row..
I can't have two pause Icon(two playing song) in my list view So I need to first reset all of them to play Icon then set the selected view to pause Icon..
How can I do this ? Or can you offer a better solution for this ?
This is my code :
In model class ( Product ) :
public int currentPosition= -1;
in Adapter:
public interface PlayPauseClick {
void playPauseOnClick(int position);
}
private PlayPauseClick callback;
public void setPlayPauseClickListener(PlayPauseClick listener) {
this.callback = listener;
}
.
.
.
holder.playPauseHive.setImageResource(product.getPlayPauseId());
holder.playPauseHive.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (callback != null) {
callback.playPauseOnClick(position);
if (position == product.currentPosition) {
product.setPlayPauseId(R.drawable.ic_pause);
//set the image to pause icon
}else{
//set the image to play icon
product.setPlayPauseId(R.drawable.ic_play);
}
notifyDataSetChanged();
}
}
});
my Callback inside my Activity:
#Override
public void playPauseOnClick(int position) {
final Product product = songList.get(position);
if(product.currentPosition == position){
product.currentPosition = -1; //pause the currently playing item
}else{
product.currentPosition = position; //play the item
}
this.adapter.notifyDataSetChanged();
}
For my case I use a variable to store the current playing item position. Let say
int x = -1; //-1 can indicate nothing was currently playing
So in the playPauseOnClick() you can do something like this
#Override
public void playPauseOnClick(int position) {
if(x == position){
x = -1; //pause the currently playing item
}else{
x = position; //play the item
}
this.adapter.notifyDataSetChanged();
}
The reason i remove product.setPlayPauseId() is because you really don't need them. You just need to set the play or pause icon based on your x varible which I created earlier. Do Something like this in your getView()
Product product = songList.get(position);
if (position == x) {
//set the image to pause icon
}else{
//set the image to play icon
}
So once you call adapter.notifyDataSetChanged() your adapter will do all the work for you. Whenever the x variable having the value -1 none of the icon will be display the pause icon, this can also make sure only one position will showing the pause icon as well.
Hope it help you.
I have a List view of ringtones with a play image view in each row for each ringtone ..
This is the view of it..
Now obviously when user clicks on a play button it should switch into pause button.. I have implemented this and it's all good so far :
Interface(In adapter)
public interface PlayPauseClick {
void playPauseOnClick(int position);
}
private PlayPauseClick callback;
public void setPlayPauseClickListener(PlayPauseClick listener) {
this.callback = listener;
}
Adapter(In getView)
Product product = (Product) mDataItems.get(position);
holder.playPause=(ImageView)v.findViewById(R.id.playPause);
holder.playPause.setImageResource(product.getPlayPauseId());
holder.playPause.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (callback != null) {
callback.playPauseOnClick(position);
}
}
});
Activity
#Override
public void playPauseOnClick(int position) {
final Product product = productList.get(position);
if (product.paused) {
product.setPlayPauseId(R.drawable.ic_pause);
product.paused=false;
}else {
product.setPlayPauseId(R.drawable.ic_play);
product.paused = true;
}
adapter.notifyDataSetChanged();
};
Now I have a problem :
I don't know how to track the currently playing row,I mean if user wants to play a song from another row, first I have to change currently pause toggle to play, then play the new one.
Can you help me with this please !?
After some digging I found Two answers..
1: We can store the index of the current song which is played in the adapter.
So we just need to turn on pause when we click on an other button.
In Adapter :
int currentPosition = -1;
...
if (callback != null) {
callback.playPauseOnClick(position);
if (currentPosition == -1){
currentPosition = position;
} else if (currentPosition == position){
currentPosition = -1;
} else if (currentPosition != -1 && currentPosition != position){
callback.playPauseOnClick(currentPosition);
currentPosition = position;
}
}
2: Set resources for every listview data item
inside callback or anywhere can get listview data:
#Override
public void playPauseOnClick(int position) {
final Product product = productList.get(position);
if (product.paused) {
for(int i=0;i< productList.size();i++)
{
movieList.get(i).setPlayPauseId(R.drawable.ic_play);
movieList.get(i).paused = true;
}
product.setPlayPauseId(R.drawable.ic_pause);
product.paused=false;
}else {
product.setPlayPauseId(R.drawable.ic_play);
product.paused = true;
}
adapter.notifyDataSetChanged();
}
You can do this
first, declare a global variable
int lastposition=null;
then
#Override
public void playPauseOnClick(int position) {
final Product product = productList.get(position);
if(lastposition!=null)
{
Product previous = productList.get(lastposition);
previous.setPlayPauseId(R.drawable.ic_pause);
previous.paused=false;
lastposition=position;
} else lastposition=position;
if (product.paused) {
//new loadSingleView().execute(product.image_url);
//Log.d(LOG, product.image_url);
product.setPlayPauseId(R.drawable.ic_pause);
product.paused=false;
}else {
product.setPlayPauseId(R.drawable.ic_play);
product.paused = true;
}
adapter.notifyDataSetChanged();
};
Replace the above code with code in your activity
You could hold a variable "mCurrentPosition" to hold the currently playing track.
Then create two separate methods, one for play and one for pause. Simply pause the current track and play the one that was clicked.
Remember to also set the current position when you play a new track.
Something like this:
#Override
public void onClick(View v) {
if (callback != null) {
callback.pause(mCurrentPosition);
mCurrentPosition = position;
callback.play(position);
}
}
You may need to adjust it a bit as I don't know how you've setup your "callback" or where "position" is coming from. I'm assuming something like "getAdapterPosition()". But hopefully you can see how to do it by this example. If not please comment.
Edit in response to updated question:
For the interface do something like this:
public interface PlayPauseClick {
void pause(int position);
void play(int position);
}
Edit2:
#Override
void pause(int position) {
if(position != null) {
Product product = productList.get(position);
product.setPlayPauseId(R.drawable.ic_play);
product.paused = true;
adapter.notifyDataSetChanged();
}
}
#Override
void play(int position) {
// Homework exercise.
}
i facing a problem in recyclerview's item.
My Adapter's code :
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
Profile item = mListChatting.get(position);
Log.d("TAG", "CEK : " + viewable);
if(viewable==true){
holder.mFormBookingan.setVisibility(View.GONE);
holder.mDetailBookingan.setVisibility(View.VISIBLE);
}else{
//assume that one way is show first as default
holder.mViewOneWay.setVisibility(View.VISIBLE);
holder.mViewRoundTrip.setVisibility(View.GONE);
holder.mOneOway.setBackgroundResource(R.drawable.round_just_left_white_focus);
holder.mRoundTrip.setBackgroundResource(R.drawable.state_pressed_booking_button_left);
holder.mSendBooking.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
viewable = true;
Log.d("TAG", "CEK 2 : " + viewable);
}
});
}
Like my code above, I want to hide mFormBookingan after mSendBooking has pressed. mFormBookingan never show anymore until user calls it again.
I have tried with a lot of ways but still can't find like what i need. After i press mSendBooking the form hide, but when i send new item to recyclerview, the from mFormBookingan that has been hide, appears again.
My Question, how to hide mFormBookingan forever? Until user call it again.
Thank in advance, i will appreciate anyone who help me for this one.
I not sure what the clear situation you want.
But if you want to set View invisible you can try this code then check it.
You need to add ismFormBookingVisible in viewHolder class as a boolean attribute.
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
Profile item = mListChatting.get(position);
Log.d("TAG", "CEK : " + viewable);
if(holder.ismFormBookingVisible==true){
holder.mFormBookingan.setVisibility(View.GONE);
holder.mDetailBookingan.setVisibility(View.VISIBLE);
}else{
//assume that one way is show first as default
holder.mViewOneWay.setVisibility(View.VISIBLE);
holder.mViewRoundTrip.setVisibility(View.GONE);
holder.mOneOway.setBackgroundResource(R.drawable.round_just_left_white_focus);
holder.mRoundTrip.setBackgroundResource(R.drawable.state_pressed_booking_button_left);
holder.mSendBooking.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.ismFormBookingVisible = false;
Log.d("TAG", "CEK 2 : " + viewable);
}
Try this :
Create a boolean in your model class "Profile" to keep track of visibility of button : say boolean isBookingVisible;
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
Profile item = mListChatting.get(position);
if(!item.isBookingVisible){
holder.mFormBookingan.setVisibility(View.GONE);
holder.mDetailBookingan.setVisibility(View.VISIBLE);
}else{
//assume that one way is show first as default
holder.mViewOneWay.setVisibility(View.VISIBLE);
holder.mViewRoundTrip.setVisibility(View.GONE);
holder.mOneOway.setBackgroundResource(R.drawable.round_just_left_white_focus);
holder.mRoundTrip.setBackgroundResource(R.drawable.state_pressed_booking_button_left);
holder.mSendBooking.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
item.isBookingVisible = false;
//Use notiyItemChanged(position); or notifyDataSetChanged(); here as per your selection criterion
Log.d("TAG", "CEK 2 : " + viewable);
}
});
}
You may have to call notifyDataSetChanged() after changing the value of viewable.
onBindViewHolder will be called when you notify your data set.
So you need to save the viewable in mListChatting. When you click the button, change the viewable in the mListChatting.
And then, change the code in onBindViewHolder
holder.mFormBookingan.setVisibility(item.getViewable() ? View.VISIBLE : View.GONE);
On refresh android will destroy the view and create new view with new adapter data. So you have to track the current state (visibility) of mFormBookingan. You can use a simple visibility list. When mFormBookingan state (visibility) change update it in visibility list so that whenever the list is refreshed, you can use it to check and set the last state (visibility) of your mFormBookingan. Here is an example
private ArrayList<Boolean> isVisible;
public MyAdapter(ArrayList<Boolean> isVisible){
// initial state list of mFormBookingan for each row of list
this.isVisible = isVisible;
}
public void onBindViewHolder(final MyViewHolder holder, final int position) {
if (isVisible.get(position)) {
holder.mFormBookingan.setVisibility(View.VISIBLE);
}else {
holder.mFormBookingan.setVisibility(View.GONE);
}
holder.mSendBooking.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (holder.mFormBookingan.getVisibility() == View.GONE){
holder.mFormBookingan.setVisibility(View.VISIBLE);
isVisible.set(position, true);
}else {
holder.mFormBookingan.setVisibility(View.GONE);
isVisible.set(position, false);
}
}
});
}
when you click mSendBooking the mFormBookingan visibility will change and it will remain same after sending new item to recyclerview.
I'm building an app that allows user to have a real time base-conversion of a number. Users input their number in an edit text, and they choose the base using plus and minus button. The problem I encountered so far is providing the real time conversion. All the editText inside the recycler view set their text to a BigInteger that can be converted depending on their base.
My idea was to update the BigInteger as the user is inputting a new number. Therefore every time users input a digit I should be able to update the BigInteger, notify the recycler view that the data as changed and then the edit text views should update automatically. Here's my ConvertViewHolder
public ConvertViewHolder(final View itemView) {
super(itemView);
mBaseTextView = (TextView) itemView.findViewById(R.id.baseLabel);
mEditText = (EditText) itemView.findViewById(R.id.numberEditText);
mMinusButton = (Button) itemView.findViewById(R.id.minusButton);
mPlusButton = (Button) itemView.findViewById(R.id.plusButton);
mRemoveButton = (ImageButton)itemView.findViewById(R.id.removeButton);
mMinusButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mRoot.get(getPosition()) > MIN_BASE) {
mRoot.set(getPosition(), (mRoot.get(getPosition()) - 1));
notifyDataSetChanged();
}
}
});
mPlusButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mRoot.get(getPosition()) < MAX_BASE){
mRoot.set(getPosition(), (mRoot.get( getPosition() ) +1));
notifyDataSetChanged();
}
}
});
mRemoveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mRoot.remove(getPosition());
notifyDataSetChanged();
}
});
mEditText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
if(editable.toString().length() > 0) {
// change Big Integer
mNumber.setDecimalNumber(editable.toString(), mRoot.get( getPosition() ) );
// notify change
notifyDataSetChanged();
}
}
});
// TODO: convert numbers at the same time
}
public void bindConverter(final int root){
mBaseTextView.setText(String.format("%02d", root));
// String containing all the allowed digits depending on base
String digits = mNumber.getScaleFromBase(root);
if (root < 11) {
// filter input
mEditText.setInputType(InputType.TYPE_CLASS_NUMBER |
InputType.TYPE_TEXT_FLAG_MULTI_LINE);
mEditText.setKeyListener(DigitsKeyListener.getInstance(digits));
mEditText.setSingleLine(false);
mEditText.setMaxLines(2);
} else {
mEditText.setInputType(InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS |
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
mEditText.setSingleLine(false);
mEditText.setMaxLines(2);
mEditText.setImeOptions(EditorInfo.IME_FLAG_NO_ENTER_ACTION);
// TODO: filter input for base higher than 10
}
// set editText to BigInteger displaying it in the correct base
// i.e. if the BigInteger is "8" it will be displayed as 8 if the base is 10
// and as 1000 if the base is 2
mEditText.setText(mNumber.getDecimalNumber(mRoot.get( getPosition() )));
}
}
But apparently I am not allowed to call notifySetDataHasChanged inside the TextWatcher.onTextChanged() as the compiler throws me this error:
java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
which is pretty self-explanatory, but unfortunately I haven't figured out a possible workaround.
view.post(
new Runnable() {
public void run() { notifyDatasetChanged(); };
}
);
self explanatory:
call this method after RecyclerView compute a layout or scrolling (in next loop)
more explanation:
one thread = code flow synchronous
main thread = looper
looper = message que = runnable = loop
other possible solution ??
call after u exit from recycler view method
ps.
one more hint if this "piece of code" will run more then once pull the runnable in higher stack existence place (in GC scope of RecyclerView) for less usage of resources and computing time :)
private Runnable r = new Runnable {
public void run() { notifyDatasetChanged(); }
}
...
view.post(r);
...
or for generic solution pull in method using interface
public Runnable postNotify(ListAdapter la) {
return = new Runnable {
public void run() { la.notifyDatasetChanged(); }
};
}
...
private Runnable changed = postNotify(adapter);
...
view.pos(changed);
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);