My application failed to update view after inserting items. I am currently using MVP for building my application. I followed this guide. The author didn't give any further explanations about insert or remove item from list.
I've updated my RecyclerView to latest version (recyclerview-v7:28.0.0). I already checked my layout and also scrolling down. In my latest effort, I know that the list is successfully added, but my getItemCount is not updated yet. I insert inside model and after that notify adapter in Activity (notifyItemInserted). I've tried using notifyDataSetChanged but nothing works.
public class MainActivity extends AppCompatActivity implements MVPInterface.viewActivity{
private RecyclerView.Adapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState){
recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setHasFixedSize(true);
adapter = new WordAdapter(this);
recyclerView.setAdapter(adapter);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
mPresenter = new ActivityPresenter(this);
addButton = (FloatingActionButton) findViewById(R.id.fab);
addButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mPresenter.onAddList();
}
});
}
#Override
public void AddSuccess(int size){
Log.d("HELLO","createthis "+size);
recyclerView.getAdapter().notifyItemInserted(size);
recyclerView.smoothScrollToPosition(size);
recyclerView.getAdapter().notifyDataSetChanged();
}
}
class WordAdapter extends RecyclerView.Adapter<WordAdapter.WordViewHolder>{
#NonNull
#Override
public WordViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
Log.d("CREATE","create");
android.view.View mItemView = mInflater.inflate(R.layout.content_main, viewGroup, false);
return new WordViewHolder(mItemView, this);
}
#Override
public void onBindViewHolder(#NonNull WordViewHolder viewHolder, int position) {
Log.d("CREATE1","bind");
mPresenter.onSetView(viewHolder, position);
}
#Override
public int getItemCount() {
int size = mPresenter.getListSize();
Log.d("item", ""+size);
return mPresenter.getListSize();
}
public class WordViewHolder extends RecyclerView.ViewHolder implements MVPInterface.WordHolder, android.view.View.OnClickListener {
public TextView wordItemView;
final WordAdapter mAdapter;
public WordViewHolder(android.view.View mItemView, WordAdapter wordAdapter) {
super(mItemView);
wordItemView = (TextView) mItemView.findViewById(R.id.word);
this.mAdapter = wordAdapter;
mItemView.setOnClickListener(this);
}
#Override
public void setText(String string) {
wordItemView.setText(string);
}
#Override
public void onClick(android.view.View v) {
wordItemView.setText ("Clicked! "+ wordItemView.getText());
}
}
}
public class AdapterPresenter {
private LinkedList<String> wordList;
private WordModel model;
public AdapterPresenter(){
this.model = new WordModel();
this.wordList = model.initList();
}
public void onSetView(MVPInterface.WordHolder holder, int position) {
holder.setText(wordList.get(position));
}
public int getListSize(){
return model.getSize();
}
}
public class WordModel {
private LinkedList<String> wordList = new LinkedList<>();
private MVPInterface.viewActivity activity;
private boolean isFirst = true;
//private Listener listener;
public WordModel(){
this.wordList = initList();
}
public LinkedList<String> initList(){
if(isFirst){
for(int i = 0; i<20; i++){
wordList.addLast("Word "+i);
}
isFirst = false;
}
return wordList;
}
public LinkedList<String> getWordList(){
return wordList;
}
public int getSize(){
return wordList.size();
}
public void addList(Listener listener){
wordList.add("+ Word "+ getSize());
listener.onAddSuccess();
}
}
public class ActivityPresenter implements Listener {
private WordModel wordModel;
private MVPInterface.viewActivity activity;
public ActivityPresenter(MVPInterface.viewActivity activity){
this.activity = activity;
this.wordModel = new WordModel();
}
public void onAddList() {
wordModel.addList(this);
}
#Override
public void onAddSuccess() {
int size = wordModel.getSize();
activity.AddSuccess(size);
}
}
The list is successfully inserted (create this logcat message) but getItemCount remains same (CREATEItem).
Android Logcat:
EDIT
I suspect that there's might be problem inside AdapterPresenter. I initialized wordModel inside presenter and thus making my list not dynamically changed (as in logcat, getItemCount won't update to lastest size). But i still don't know what is the exactly problem.
I think you should remove setHasFixedSize(true) or replace it with setHasFixedSize(false)
Related
I want to loop through all CardViews and change the text and color of a TextView within a single CardView item using a button click. The following code seems to produce the desired results but I'm not certain that it's the most effective code or even accurate (index).
// CustomAdapter
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class CustomAdapter extends RecyclerView.Adapter<CustomViewHolder> {
private Context context;
private List<MyModel> list;
public CustomAdapter(Context context, List<MyModel> list) {
this.context = context;
this.list = list;
}
#NonNull
#Override
public CustomViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new CustomViewHolder(LayoutInflater.from(context).inflate(R.layout.single_items, parent, false));
}
#Override
public void onBindViewHolder(#NonNull CustomViewHolder holder, int position) {
holder.textName.setText(list.get(position).getName());
holder.textAge.setText(String.valueOf(list.get(position).getAge()));
}
#Override
public int getItemCount() {
return list.size();
}
}
//CustomViewHolder
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public class CustomViewHolder extends RecyclerView.ViewHolder {
public TextView textName, textAge;
public CustomViewHolder(#NonNull View itemView) {
super(itemView);
textName = itemView.findViewById(R.id.textName);
textAge = itemView.findViewById(R.id.textAge);
}
}
MainActivity
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
List<MyModel> myModelList;
CustomAdapter customAdapter;
private Button button1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
private void loadData() {
recyclerView = findViewById(R.id.recycler_main);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new GridLayoutManager(this, 1));
myModelList = new ArrayList<>();
myModelList.add(new MyModel("Joe", 21));
myModelList.add(new MyModel("Jane", 26));
myModelList.add(new MyModel("Kyle", 19));
myModelList.add(new MyModel("Scott", 30));
customAdapter = new CustomAdapter(this, myModelList);
recyclerView.setAdapter(customAdapter);
}
public void onClickBtn(View v)
{
String searchString = "Kyle";
for (int x = recyclerView.getChildCount(), i = 0; i < x; ++i) {
RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(recyclerView.getChildAt(i));
TextView txtName = holder.itemView.findViewById(R.id.textName);
if (txtName.getText().toString().equals(searchString.toString())) {
txtName.setText("Found " + txtName.getText().toString());
txtName.setTextColor(Color.GREEN);
customAdapter.notifyItemChanged(x);
}
}
}
}
//MyModel
public class MyModel {
String name = "";
int age = 0;
public MyModel(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
It's important that I iterate through the list in button click event. Functionality to be changed later. Really appreciate any advice and feedback. Update. Must be an index or other related problem. When my ArrayList contains many, many more items and button is clicked, a lot of non found rows text and color are changed.
Try this adapter:
public class CustomAdapter extends RecyclerView.Adapter<CustomViewHolder> {
private Context context;
private List<MyModel> list;
private String searchString = "";
public CustomAdapter(Context context, List<MyModel> list) {
this.context = context;
this.list = list;
}
#NonNull
#Override
public CustomViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new CustomViewHolder(LayoutInflater.from(context).inflate(R.layout.single_items, parent, false));
}
#Override
public void onBindViewHolder(#NonNull CustomViewHolder holder, int position) {
holder.textAge.setText(String.valueOf(list.get(position).getAge()));
if(list.get(position).getName().equals(searchString)){
holder.textName.setText("Found " + list.get(position).getName());
holder.textName.setTextColor(Color.GREEN);
} else {
holder.textName.setText(list.get(position).getName());
holder.textName.setTextColor(Color.BLACK);
}
}
#Override
public int getItemCount() {
return list.size();
}
public void setNewSearchString(String searchString) {
this.searchString = searchString;
notifyDataSetChanged();
}
}
and button click:
public void onClickBtn(View v)
{
customAdapter.setNewSearchString("Kyle");
}
For Multiple search, the adapter:
public class CustomAdapter extends RecyclerView.Adapter<CustomViewHolder> {
private Context context;
private List<MyModel> list;
//private String searchString = "";
private ArrayList<String> arraySearchStrings = new ArrayList<>();
public CustomAdapter(Context context, List<MyModel> list) {
this.context = context;
this.list = list;
}
#NonNull
#Override
public CustomViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new CustomViewHolder(LayoutInflater.from(context).inflate(R.layout.single_items, parent, false));
}
#Override
public void onBindViewHolder(#NonNull CustomViewHolder holder, int position) {
holder.textAge.setText(String.valueOf(list.get(position).getAge()));
boolean found = false;
for (String searchString : arraySearchStrings) {
if (list.get(position).getName().equals(searchString)) {
found = true;
break;
}
}
if (found) {
holder.textName.setText("Found " + list.get(position).getName());
holder.textName.setTextColor(Color.GREEN);
} else {
holder.textName.setText(list.get(position).getName());
holder.textName.setTextColor(Color.BLACK);
}
}
#Override
public int getItemCount() {
return list.size();
}
public void setNewSearchString(String searchString) {
//this.searchString = searchString;
arraySearchStrings.add(searchString);
notifyDataSetChanged();
}
public void resetSearchString() {
arraySearchStrings.clear();
notifyDataSetChanged();
}
}
Button click:
public void onClickBtn(View v)
{
customAdapter.setNewSearchString("Kyle");
customAdapter.setNewSearchString("Steve");
customAdapter.setNewSearchString("Joe");
}
Alternative answser:
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.CustomViewHolder> {
private Context context;
private List<MyModel> list;
private ArrayList<String> arraySearchStrings = new ArrayList<>();
private ArrayList<Boolean> arrayFound = new ArrayList<>();
private int[] arrayFoundCount;
private int foundTotalCount = 0;
public CustomAdapter(Context context, List<MyModel> list) {
this.context = context;
this.list = list;
arrayFoundCount = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
arrayFound.add(false);
arrayFoundCount[i] = 0;
}
}
#NonNull
#Override
public CustomViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new CustomViewHolder(LayoutInflater.from(context).inflate(R.layout.single_items, parent, false));
}
#Override
public void onBindViewHolder(#NonNull CustomViewHolder holder, int position) {
holder.textAge.setText(String.valueOf(list.get(position).getAge()));
holder.textCount.setText(String.valueOf(arrayFoundCount[position]));
if (arrayFound.get(position)) {
holder.textName.setText("Found " + list.get(position).getName());
holder.textName.setTextColor(Color.GREEN);
} else {
holder.textName.setText(list.get(position).getName());
holder.textName.setTextColor(Color.BLACK);
}
}
#Override
public int getItemCount() { return list.size(); }
public class CustomViewHolder extends RecyclerView.ViewHolder {
public TextView textName, textAge, textCount;
public CustomViewHolder(#NonNull View itemView) {
super(itemView);
textName = itemView.findViewById(R.id.textName);
textAge = itemView.findViewById(R.id.textAge);
textCount = itemView.findViewById(R.id.textCount);
}
}
private int countFoundNameInList() {
int count = 0;
boolean found;
MyModel model;
arrayFound.clear();
for (int i = 0; i < list.size(); i++) {
model = list.get(i);
found = false;
for (String searchString : arraySearchStrings) {
if (model.getName().equals(searchString)) {
found = true;
arrayFoundCount[i] = arrayFoundCount[i]++;
count++;
break;
}
}
arrayFound.add(found);
}
return count;
}
public void setNewSearchString(String searchString) {
arraySearchStrings.add(searchString);
int newCount = countFoundNameInList();
if (newCount > foundTotalCount) {
Toast.makeText(context, searchString + " found.", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "Error: Nothing found!!", Toast.LENGTH_LONG).show();
}
foundTotalCount = newCount;
notifyDataSetChanged();
}
}
I don't think this code will give you accurate result.
you have to come up with different logic for this
In your logic you are searching static name i am sure this is for demo purpose only. later you will implement with user input string.
So As per my opinion you can create variable in your adapter that search mode is on or off
after this when ever user start searching make searchMode On and same as when they done with searching set searchMode Off
in your viewHolder Class you can update View on searchMode on off Status.
You can Create 2 list
1 is for main List
seconds is for searched list
when user starts searching you have to filter main list with search string and then set it to searchedList and also make searchMode On and then update it to your adapter. rest will handle your adapter. no need to change it one by one from your list.
I am adding here required changes as per my opinion
Your Custom Adapter
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.CustomViewHolder> {
private Context context;
private List<MyModel> list;
private Boolean isSearchModeOn = false;
public CustomAdapter(#NonNull Context context, #NonNull List<MyModel> list) {
this.context = context;
this.list = list;
}
#NonNull
#Override
public CustomViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new CustomViewHolder(LayoutInflater.from(context).inflate(R.layout.single_items, parent, false));
}
#Override
public void onBindViewHolder(#NonNull CustomViewHolder holder, int position) {
if (isSearchModeOn){
holder.textName.setText("Found " + list.get(position).getName());
holder.textName.setTextColor(Color.GREEN);
}else {
holder.textName.setText(list.get(position).getName());
//Also set Here normal text color
}
holder.textAge.setText(String.valueOf(list.get(position).getAge()));
}
#SuppressLint("NotifyDataSetChanged")
public void updateList(#NonNull List<MyModel> searchedList){
list = searchedList;
notifyDataSetChanged();
}
#SuppressLint("NotifyDataSetChanged")
public void setSearchMode(#NonNull Boolean isOn){
isSearchModeOn = isOn;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return list.size();
}
public class CustomViewHolder extends RecyclerView.ViewHolder {
#NonNull
public TextView textName, textAge;
public CustomViewHolder(#NonNull View itemView) {
super(itemView);
textName = itemView.findViewById(R.id.textName);
textAge = itemView.findViewById(R.id.textAge);
}
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
List<MyModel> myModelList;
List<MyModel> searchedList;
CustomAdapter customAdapter;
#Override
protected void onCreate(#NonNull Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
loadData();
}
private void loadData() {
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new GridLayoutManager(this, 1));
myModelList = new ArrayList<>();
myModelList.add(new MyModel("Joe", 21));
myModelList.add(new MyModel("Jane", 26));
myModelList.add(new MyModel("Kyle", 19));
myModelList.add(new MyModel("Scott", 30));
customAdapter = new CustomAdapter(this, myModelList);
recyclerView.setAdapter(customAdapter);
}
public void onClickBtn(#NonNull View v)
{
String searchString = "Kyle";
searchedList = new ArrayList<>();
for (int x = myModelList.size(), i = 0; i < x; ++i) {
if (myModelList.get(i).getName().equals(searchString)){
searchedList.add(myModelList.get(i));
}
}
customAdapter.updateList(searchedList);
customAdapter.setSearchMode(true);
}
}
I am mostly develop in kotlin so maybe some text error can happen in this code. you can check the logic for this requirement
I've added swipe to my app, to delete specified note. For couple of days, I've facing problem with displaying data after swipe. For clarify:
Let's say, we have two items in recycler view. Whenever we swipe one of them, the second one should be visible, but it's not until I'll re-run the app. How I may solve it?
Main Activity
public class MainActivity extends AppCompatActivity implements MemoAdapter.OnNoteListener {
private static final String TAG = "MainActivity";
//Vars
private ArrayList<Note> mNotes = new ArrayList<>();
private MemoRepository mRepository;
private MemoAdapter mMemoAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRepository = new MemoRepository(this);
Toolbar toolbar = (Toolbar) findViewById(R.id.memoToolbar);
setSupportActionBar(toolbar);
setTitle("My memos");
initRecyclerView();
}
//This method would be called after getting result from memo_content such as new memo, or edited existing memo.
#Override
protected void onResume() {
super.onResume();
getMemos();
}
private void getMemos(){
mRepository.getAllMemos().observe(this, new Observer<List<Note>>() {
#Override
public void onChanged(List<Note> notes) {
if (mNotes.size() > 0){
notes.clear();
}
if (notes != null){
mNotes.addAll(notes);
mMemoAdapter.watchMemoChanges((ArrayList<Note>) notes);
}
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.icon_menu, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.addNewNote:
Toast.makeText(this, "Click!", Toast.LENGTH_SHORT).show();
startActivity(new Intent(this, memo_content.class));
break;
case R.id.deleteAllNotes:
Toast.makeText(this, "Delete!", Toast.LENGTH_SHORT).show();
mRepository.deleteAllMemos();
break;
}
return super.onOptionsItemSelected(item);
}
private void initRecyclerView(){
//UI
RecyclerView mRecyclerView = findViewById(R.id.recyclerView);
mRecyclerView.setHasFixedSize(true);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(this);
mMemoAdapter = new MemoAdapter(mNotes, this);
new ItemTouchHelper(itemTouch).attachToRecyclerView(mRecyclerView);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mMemoAdapter);
}
#Override
public void onMemoClick(int position) {
Intent intent = new Intent(this, memo_content.class);
intent.putExtra("memo_content", mNotes.get(position));
startActivity(intent);
}
private ItemTouchHelper.SimpleCallback itemTouch = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.START | ItemTouchHelper.LEFT) {
#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) {
mRepository.deleteMemo(mNotes.get(viewHolder.getAdapterPosition()));
Log.d(TAG, "onSwiped: "+mNotes.get(viewHolder.getAdapterPosition()));
}
};
}
Adapter
public class MemoAdapter extends RecyclerView.Adapter<MemoAdapter.MemoViewHolder> {
private ArrayList<Note> mNotes;
private OnNoteListener mListener;
public class MemoViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView title, timestamp;
private MemoViewHolder(#NonNull final View itemView) {
super(itemView);
this.title = (TextView) itemView.findViewById(R.id.title);
this.timestamp = (TextView) itemView.findViewById(R.id.timestamp);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
mListener.onMemoClick(getAdapterPosition());
}
}
public MemoAdapter(ArrayList<Note> notes, OnNoteListener listener) {
this.mNotes = notes;
this.mListener = listener;
}
#NonNull
#Override
public MemoViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.single_memo, parent, false);
return new MemoViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull MemoViewHolder holder, int position) {
holder.title.setText(mNotes.get(position).getTitle());
holder.timestamp.setText(mNotes.get(position).getTimestamp());
}
public void watchMemoChanges(ArrayList<Note> notes){
this.mNotes = notes;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return mNotes.size();
}
public interface OnNoteListener{
void onMemoClick(int position);
}
Short answer:
You need to remove the statement notes.clear() when you receive a change in the LiveData list from the database via the observer.
Detailed answer
When you first run your app, it will show all right list because the condition if (mNotes.size() > 0) is not met, and so the received list won't be cleared via notes.clear(), so the RecyclerView will display the right data.
But when you delete a note, then when the observer is triggered again with the new list, the condition if (mNotes.size() > 0) will be met, so you will clear the list that is coming from the database before feeding the adapter with it, so the RecyclerView will be free of data.
So to solve this please replace notes.clear(); with mNotes.clear();
So the right code will be:
private void getMemos(){
mRepository.getAllMemos().observe(this, new Observer<List<Note>>() {
#Override
public void onChanged(List<Note> notes) {
if (mNotes.size() > 0){
mNotes.clear();
}
if (notes != null){
mNotes.addAll(notes);
mMemoAdapter.watchMemoChanges((ArrayList<Note>) notes);
}
}
});
}
Wish that help you out.
I didn't examine the rest of code, please let me know if there is another issue to help more
I was just playing around with some code, learning new things, when I ran into this problem... I'm trying to pass a variable from my RecylcerViewAdapter to a method in MainActivity, but I just can't seem to accomplish it.
I tried a lot of different thing with interfaces and casting, but nothing did the trick. Since I'm fairly new to all of this, maybe I'm making a trivial mistake somewhere?
My Interface:
public interface AdapterCallback {
void onMethodCallback(int id);
}
This is my adapter class:
public class PostAdapter extends RecyclerView.Adapter<PostAdapter.ViewHolder> {
private List<Post> postList;
private Context context;
private AdapterCallback listener;
public PostAdapter() {
}
public PostAdapter(List<Post> postList, Context context) {
this.postList = postList;
this.context = context;
}
public void setListener(AdapterCallback listener) {
this.listener = listener;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_layout, viewGroup, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull final ViewHolder viewHolder, final int position) {
viewHolder.tvTitle.setText(postList.get(position).getTitle());
viewHolder.tvBody.setText(new StringBuilder(postList.get(position).getBody().substring(0, 20)).append("..."));
viewHolder.tvId.setText(String.valueOf(postList.get(position).getUserId()));
viewHolder.parentLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int id = postList.get(position).getId();
if (listener != null) {
listener.onMethodCallback(id);
}
}
});
}
#Override
public int getItemCount() {
return postList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView tvTitle;
TextView tvBody;
TextView tvId;
LinearLayout parentLayout;
public ViewHolder(View itemView) {
super(itemView);
tvTitle = itemView.findViewById(R.id.tvTitle);
tvBody = itemView.findViewById(R.id.tvBody);
tvId = itemView.findViewById(R.id.tvId);
parentLayout = itemView.findViewById(R.id.parentLayout);
}
}
}
And my MainActivity:
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivityLog";
private CompositeDisposable disposable = new CompositeDisposable();
#BindView(R.id.rvPosts)
RecyclerView rvPosts;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
rvPosts.setHasFixedSize(true);
rvPosts.setLayoutManager(new LinearLayoutManager(this));
populateList();
logItems();
}
private void populateList() {
MainViewModel viewModel = ViewModelProviders.of(MainActivity.this).get(MainViewModel.class);
viewModel.makeQuery().observe(MainActivity.this, new Observer<List<Post>>() {
#Override
public void onChanged(#Nullable List<Post> posts) {
PostAdapter adapter = new PostAdapter(posts, getApplicationContext());
rvPosts.setAdapter(adapter);
}
});
}
public void logItems() {
PostAdapter adapter = new PostAdapter();
adapter.setListener(new AdapterCallback() {
#Override
public void onMethodCallback(int id) {
MainViewModel viewModel = ViewModelProviders.of(MainActivity.this).get(MainViewModel.class);
viewModel.makeSingleQuery(id).observe(MainActivity.this, new Observer<Post>() {
#Override
public void onChanged(#Nullable final Post post) {
Log.d(TAG, "onChanged: data response");
Log.d(TAG, "onChanged: " + post);
}
});
}
});
}
#Override
protected void onDestroy() {
super.onDestroy();
disposable.clear();
}
}
The populateList() method works just fine, but the logItems() method is the problem.
So when i click on a view in RecyclerView I expect the log to output the title, description and ID of the post that was clicked. nut nothing happens...
So, any help would be appreciated.
Make adapter global variable i.e. a field. Use the same object to set every properties.
private PostAdapter adapter;
Replace your logItems method with this:
public void logItems() {
adapter.setListener(new AdapterCallback() {
#Override
public void onMethodCallback(int id) {
MainViewModel viewModel = ViewModelProviders.of(MainActivity.this).get(MainViewModel.class);
viewModel.makeSingleQuery(id).observe(MainActivity.this, new Observer<Post>() {
#Override
public void onChanged(#Nullable final Post post) {
Log.d(TAG, "onChanged: data response");
Log.d(TAG, "onChanged: " + post);
}
});
}
});
}
And populateList with this:
private void populateList() {
MainViewModel viewModel = ViewModelProviders.of(MainActivity.this).get(MainViewModel.class);
viewModel.makeQuery().observe(MainActivity.this, new Observer<List<Post>>() {
#Override
public void onChanged(#Nullable List<Post> posts) {
adapter = new PostAdapter(posts, getApplicationContext());
rvPosts.setAdapter(adapter);
logItems();
}
});
}
And don't call logItems() from onCreate
This is how I implement with my ListAdapters:
public class FeedbackListAdapter extends RecyclerView.Adapter<FeedbackListAdapter.ViewHolder> {
private final ArrayList<Feedback> feedbacks;
private View.OnClickListener onItemClickListener;
private View.OnLongClickListener onItemLongClickListener;
private final Context context;
public FeedbackListAdapter(ArrayList<Feedback> feedbacks, Context context) {
this.feedbacks = feedbacks;
this.context = context;
}
public void setItemClickListener(View.OnClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public void setOnItemLongClickListener(View.OnLongClickListener onItemLongClickListener){
this.onItemLongClickListener = onItemLongClickListener;
}
public class ViewHolder extends RecyclerView.ViewHolder{
final TextView feedback, created, updated;
final LinearLayout mainLayout;
ViewHolder(View iv) {
super(iv);
/*
* Associate layout elements to Java declarations
* */
mainLayout = iv.findViewById(R.id.main_layout);
feedback = iv.findViewById(R.id.feedback);
created = iv.findViewById(R.id.created_string);
updated = iv.findViewById(R.id.updated_string);
}
}
#Override
public int getItemCount() {
return feedbacks.size();
}
#Override
#NonNull
public FeedbackListAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_feedback_table_row, parent, false);
return new FeedbackListAdapter.ViewHolder(view);
}
#Override
public void onBindViewHolder(final #NonNull FeedbackListAdapter.ViewHolder holder, final int position) {
/*
* Bind data to layout
* */
try{
Feedback feedback = feedbacks.get(position);
holder.feedback.setText(feedback.getContent());
holder.created.setText(feedback.getCreated());
holder.updated.setText(feedback.getUpdated());
holder.mainLayout.setOnClickListener(this.onItemClickListener);
holder.mainLayout.setOnLongClickListener(this.onItemLongClickListener);
holder.mainLayout.setTag(feedback.getDbID());
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
holder.mainLayout.setBackgroundResource(outValue.resourceId);
}catch(IndexOutOfBoundsException e){
e.printStackTrace();
}
}
}
In onPopulateList you create an adaptor:
PostAdapter adapter = new PostAdapter(posts, getApplicationContext());
rvPosts.setAdapter(adapter);
However in public void logItems() { you used a different adapter
PostAdapter adapter = new PostAdapter();
adapter.setListener(new AdapterCallback() {
#Override
public void onMethodCallback(int id) {
...
}
});
Therefore the list is being populated with 1 adapter, but you are setting the listener on an unused second adapter.
The fix is to use the same adapter for both. If you make the adapater a field, and don't create a new one inside of logItems, but just set your listener it should work.
i.e.
// as a field in your class
private PostAdapter adapter;
then
// in `populateList()`
adapter = new PostAdapter(posts, getApplicationContext());
rvPosts.setAdapter(adapter);
and
// in `logItems()`
adapter.setListener(new AdapterCallback() {
#Override
public void onMethodCallback(int id) {
...
}
});
In Adapter
public class CustomerListAdapter extends RecyclerView.Adapter<CustomerListAdapter.OrderItemViewHolder> {
private Context mCtx;
ProgressDialog progressDialog;
//we are storing all the products in a list
private List<CustomerModel> customeritemList;
public CustomerListAdapter(Context mCtx, List<CustomerModel> orderitemList) {
this.mCtx = mCtx;
this.customeritemList = orderitemList;
progressDialog = new ProgressDialog(mCtx);
}
#NonNull
#Override
public OrderItemViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(mCtx);
View view = inflater.inflate(R.layout.activity_customer_list, null);
return new OrderItemViewHolder(view, mCtx, customeritemList);
}
#Override
public void onBindViewHolder(#NonNull OrderItemViewHolder holder, int position) {
CustomerModel customer = customeritemList.get(position);
try {
//holder.textViewPINo.setText("PINo \n"+Integer.toString( order.getPINo()));
holder.c_name.setText(customer.getCustomerName());
holder.c_address.setText(customer.getAddress());
holder.c_contact.setText(customer.getMobile());
holder.i_name.setText(customer.getInteriorName());
holder.i_contact.setText(customer.getInteriorMobile());
holder.i_address.setText(customer.getAddress());
} catch (Exception E) {
E.printStackTrace();
}
}
#Override
public int getItemCount() {
return customeritemList.size();
}
class OrderItemViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener, View.OnClickListener {
AlertDialog.Builder alert;
private Context mCtx;
TextView c_name, c_contact, c_address, i_name, i_contact, i_address;
TextView OrderItemID, MaterialType, Price2, Qty, AQty;
//we are storing all the products in a list
private List<CustomerModel> orderitemList;
public OrderItemViewHolder(View itemView, Context mCtx, List<CustomerModel> orderitemList) {
super(itemView);
this.mCtx = mCtx;
this.orderitemList = orderitemList;
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
// CatelogOrderDetailModel catelogOrderDetailModel = new CatelogOrderDetailModel();
c_name = itemView.findViewById(R.id.customerName);
c_contact = itemView.findViewById(R.id.contact);
c_address = itemView.findViewById(R.id.address);
i_name = itemView.findViewById(R.id.interiorName);
i_address = itemView.findViewById(R.id.interiorAddress);
i_contact = itemView.findViewById(R.id.interiorContact);
}
#Override
public void onClick(View v) {
int position = getAdapterPosition();
CustomerModel orderitem = this.orderitemList.get(position);
}
#Override
public boolean onLongClick(View v) {
int position = getAdapterPosition();
CustomerModel orderitem = this.orderitemList.get(position);
if (v.getId() == itemView.getId()) {
// showUpdateDeleteDialog(order);
try {
} catch (Exception E) {
E.printStackTrace();
}
Toast.makeText(mCtx, "lc: ", Toast.LENGTH_SHORT).show();
}
return true;
}
}
}
I have a RecyclerView with edittext for search in my android app. When I search in it and click on an item, it shows wrong data.
I know why it happens but I don't know how to fix it. I have tried many things but still I have the problem. i am new in programming, please help :).
Here is the code of my Adapter.
public class ProjectAdapter extends RecyclerView.Adapter<ProjectAdapter.ProjectViewHolder> {
private Context mCtx;
private List<Project> projectList;
private OnItemClickListener mListener;
public interface OnItemClickListener {
void onItemClick(int position);
}
public void setOnItemClickListener(OnItemClickListener listener) {
mListener = listener;
}
public ProjectAdapter(Context mCtx, List<Project> projectList) {
this.mCtx = mCtx;
this.projectList = projectList;
}
#Override
public ProjectViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(mCtx);
View view = inflater.inflate(R.layout.project_list, null);
return new ProjectAdapter.ProjectViewHolder(view);
}
#Override
public void onBindViewHolder(ProjectViewHolder holder, int position) {
Project project = projectList.get(position);
holder.textViewProject.setText(project.getProject());
}
#Override
public int getItemCount() {
return projectList.size();
}
class ProjectViewHolder extends RecyclerView.ViewHolder {
TextView textViewProject;
public ProjectViewHolder(View itemView) {
super(itemView);
textViewProject = itemView.findViewById(R.id.textViewProject);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mListener != null){
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION){
mListener.onItemClick(position);
}
}
}
});
}
}
}
and this is my ListprojectActivity.java
public class ListprojectActivity extends AppCompatActivity implements ProjectAdapter.OnItemClickListener {
public static final String project_select = "project";
private static final String URL_PRODUCTS = "http://192.168.43.245/android_register_login/Api_1.php";
EditText editTextProject;
//a list to store all the products
List<Project> projectList;
//the recyclerview
RecyclerView recyclerView;
ProjectAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listproject);
//getting the recyclerview from xml
recyclerView = findViewById(R.id.recylcerViewProject);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
editTextProject = findViewById(R.id.EditTextProject);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
DividerItemDecoration itemDecoration = new DividerItemDecoration(this, layoutManager.getOrientation());
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(itemDecoration);
//initializing the productlist
projectList = new ArrayList<>();
editTextProject.addTextChangedListener (new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
final String query = s.toString().toLowerCase().trim();
final ArrayList<Project> filteredList = new ArrayList<>();
for (int i = 0; i < projectList.size(); i++) {
final String text = projectList.get(i).getProject().toLowerCase();
if (text.contains(query)) {
filteredList.add(projectList.get(i));
}
}
recyclerView.setLayoutManager(new LinearLayoutManager(ListprojectActivity.this));
adapter = new ProjectAdapter(ListprojectActivity.this, filteredList);
recyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(ListprojectActivity.this);
adapter.notifyDataSetChanged();
}
#Override
public void afterTextChanged(Editable s) {
}
});
//this method will fetch and parse json
//to display it in recyclerview
loadProjects();
}
private void loadProjects() {
/*
* Creating a String Request
* The request type is GET defined by first parameter
* The URL is defined in the second parameter
* Then we have a Response Listener and a Error Listener
* In response listener we will get the JSON response as a String
* */
StringRequest stringRequest = new StringRequest(Request.Method.GET, URL_PRODUCTS,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
try {
//converting the string to json array object
JSONArray array = new JSONArray(response);
//traversing through all the object
for (int i = 0; i < array.length(); i++) {
//getting product object from json array
JSONObject project = array.getJSONObject(i);
//adding the product to product list
projectList.add(new Project(
project.getInt("id_project"),
project.getString("project")
));
}
//creating adapter object and setting it to recyclerview
ProjectAdapter adapter = new ProjectAdapter(ListprojectActivity.this, projectList);
recyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(ListprojectActivity.this);
} catch (JSONException e) {
e.printStackTrace();
}
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
});
//adding our stringrequest to queue
Volley.newRequestQueue(this).add(stringRequest);
}
#Override
public void onItemClick(int position) {
Intent detailMasalah = new Intent(this, ListproblemActivity.class);
Project projectclick = projectList.get(position);
detailMasalah.putExtra(project_select, projectclick.getProject());
startActivity(detailMasalah);
}
}
and project.java
public class Project {
private int id_project;
private String project;
public Project (int id_project, String project) {
this.id_project = id_project;
this.project = project;
}
public int getId() {
return id_project;
}
public String getProject() {
return project;
}
}
Try this
#Override
public void onBindViewHolder(ProjectViewHolder holder, int position) {
Project project = projectList.get(position);
holder.textViewProject.setText(project.getProject());
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mListener != null){
int position = holder.getAdapterPosition();
if (position != RecyclerView.NO_POSITION){
mListener.onItemClick(position);
}
}
}
});
}
It happened because you are setting the listener in your ViewHolder class instead of your onBindViewHolder. As the viewholder is recycled, no new objects are created after some scrolling. The created object's click listener is bound to the item that first created it.
As Vishrut mentioned, you should move the listener to onBindViewHolder.
What i'm trying to do is that, I have an adapter which I'm using for many different activities each having their own ViewHolder. So I'm firstly making an abstract recycler adapter named DimRecyclerAbstractAdapter without a ViewHolder. Then for each different activity I'm making a static inner class named DimCustomAdapter which extends DimRecyclerAbstractAdapter having it's own ViewHolder. But I'm getting this error.
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.get(ArrayList.java:411)
This is the abstract adapter class -
public abstract class DimRecyclerAbstractAdapter<VHA extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VHA> {
private List<tbl_dim_1> mtbldimList1;
public DimRecyclerAbstractAdapter(List<tbl_dim_1> tblDimList1) {
this.mtbldimList1 = tblDimList1;
}
#Override
public int getItemCount() {
return mtbldimList1.size();
}
public void addItems(List<tbl_dim_1> tblDimList1) {
this.mtbldimList1 = tblDimList1;
notifyDataSetChanged();
}
#Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
mtbldimList1 = null;
super.onDetachedFromRecyclerView(recyclerView);
}
}
This is the activity in which I've implemented custom ViewHolder -
public class DetailActivity1 extends LifecycleActivity{
DimListViewModel dmvmodel;
RecyclerView rcView;
DimCustomAdapter rcAdapter;
public static final String LOG_TAG = "In DetailActivity1 ";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.recycler_view);
Intent receivedIntent = getIntent();
int rid_int = receivedIntent.getIntExtra("mRId",0);
Log.v(LOG_TAG,"Rid value int == " + rid_int);
rcView = (RecyclerView) findViewById(R.id.recyclerView);
rcAdapter = new DimCustomAdapter(new ArrayList<tbl_dim_1>());
rcView.setLayoutManager(new LinearLayoutManager(DetailActivity1.this));
Log.v(LOG_TAG, "Layout manager set");
rcView.setAdapter(rcAdapter);
dmvmodel = ViewModelProviders.of(this).get(DimListViewModel.class);
Log.v(LOG_TAG, "View model returned");
dmvmodel.getDimList2con1(rid_int).observe(DetailActivity1.this, new Observer<List<tbl_dim_1>>() {
#Override
public void onChanged(#Nullable List<tbl_dim_1> changedItems) {
Log.v(LOG_TAG, "onChanged called, items will be added");
rcAdapter.addItems(changedItems);
}
});
}
#Override
protected void onDestroy() {
rcAdapter = null;
rcView.setAdapter(null);
rcView.setLayoutManager(null);
rcView = null;
dmvmodel = null;
super.onDestroy();
RefWatcher refWatcher = LeakCheckApplication.getRefWatcher(this);
refWatcher.watch(this);
}
public static class DimCustomAdapter extends DimRecyclerAbstractAdapter<DimCustomAdapter.RecyclerViewHolder> {
private static List<tbl_dim_1> mtbldimCustom;
DimCustomAdapter(List<tbl_dim_1> tblDimListPassed) {
super(mtbldimCustom);
mtbldimCustom = tblDimListPassed;
}
#Override
public DimCustomAdapter.RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View vw = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_item, parent, false);
// vw.setOnClickListener(vwOnClickListener);
return new DimCustomAdapter.RecyclerViewHolder(vw);
}
#Override
public void onBindViewHolder(final RecyclerViewHolder holder, int position) {
// Log.v(LOG_TAG, "Inside onBindViewHolder - ");
if (holder.dataTextView.getText() != null) {
String LOG_TAG = "DimCustomAdapter:";
Log.d(LOG_TAG, "holder is not null, i was right");
holder.dataTextView.setText(null);
holder.dataTextView.setOnClickListener(null);
}
final tbl_dim_1 dimAtPosition = mtbldimCustom.get(position);
holder.dataTextView.setText(dimAtPosition.mCONTENT);
holder.dataTextView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg) {
Intent dw = new Intent(arg.getContext(), DetailActivity2.class);
dw.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
dw.putExtra("mRId", dimAtPosition.mR_ID);
arg.getContext().startActivity(dw);
}
});
}
class RecyclerViewHolder extends RecyclerView.ViewHolder {
private TextView dataTextView;
RecyclerViewHolder(View view) {
super(view);
dataTextView = (TextView) view.findViewById(R.id.data_text_view);
}
}
}
}
And this is how I'm calling this inner class from outer class within same activity -
DimCustomAdapter rcAdapter = new DimCustomAdapter(new ArrayList<tbl_dim_1>());
Its because of empty list that you are passing while creating the adapter.
DimCustomAdapter rcAdapter = new DimCustomAdapter(new ArrayList<tbl_dim_1>());
Just add a null or empty check in onBindViewHolder method before calling
final tbl_dim_1 dimAtPosition = mtbldimCustom.get(position);
Hope this will stop crashing your application.
You can try like below,
ArrayList<tbl_dim_1> list = new ArrayList<>()
rcAdapter = new DimCustomAdapter(list);
then in observe method,
dmvmodel.getDimList2con1(rid_int).observe(DetailActivity1.this, new Observer<List<tbl_dim_1>>() {
#Override
public void onChanged(#Nullable List<tbl_dim_1> changedItems) {
Log.v(LOG_TAG, "onChanged called, items will be added");
list.addAll(changedItems);
rcAdapter.notifyDataSetChanged()
}
});
This is the solution i worked out for my problem( In case anyone face the same problem ).
Modyfying Durga's answer -
Firstly i added the isEmpty() check that he mentioned in comment.
Then in the activity i changed the code to this -
a) Added this new method in DimCustomAdapter -
public void changeList(List<tbl_dim_1> addedList){
mtbldimCustom = addedList;
}
b) Called this method in observe method -
dmvmodel.getDimList2con1(rid_int).observe(DetailActivity1.this, new Observer<List<tbl_dim_1>>() {
#Override
public void onChanged(#Nullable List<tbl_dim_1> changedItems) {
Log.v(LOG_TAG, "onChanged called, items will be added");
rcAdapter.changeList(changedItems);
rcAdapter.addItems(changedItems);
}
});