hi i have a RecyclerView adapter as follow :-
public class RecyclerViewAdapter extends RecyclerView.Adapter<ViewHolder> {
List<Data> list = Collections.emptyList();
Context context;
public RecyclerViewAdapter(List<Data> list, Context context) {
this.list = list;
this.context = context;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//Inflate the layout, initialize the View Holder
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.apps_layout, parent, false);
ViewHolder holder = new ViewHolder(v);
return holder;
}
#Override
public void onViewDetachedFromWindow(ViewHolder holder) {
super.onViewDetachedFromWindow(holder);
((ViewHolder)holder).itemView.clearAnimation();
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
//Use the provided View Holder on the onCreateViewHolder method to populate the current row on the RecyclerView
holder.title.setText(list.get(position).title);
holder.description.setText(list.get(position).description);
}
public void setFilter(List<Data> searched_data) {
list = new ArrayList<>();
list.addAll(searched_data);
notifyDataSetChanged();
}
#Override
public int getItemCount() {
//returns the number of elements the RecyclerView will display
return list.size();
}
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
// Insert a new item to the RecyclerView on a predefined position
public void insert(int position, Data data) {
list.add(position, data);
notifyItemInserted(position);
}
// Remove a RecyclerView item containing a specified Data object
public void remove(Data data) {
int position = list.indexOf(data);
list.remove(position);
notifyItemRemoved(position);
}
}
okay now as you can see setFilter method to filter recyclerview list
here is how i call it
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String s) {
return false;
}
#Override
public boolean onQueryTextChange(String s) {
List<Data> filteredModelList = filter(data, s);
adapter.setFilter(filteredModelList);
return true;
}
});
now filter works fine now when i click on it the position is wrong
i know the error coming from the position as the older recyclerview on click has old position index i dont know how to fix it anyway here is onclicklistener
data = appsdata(); // load the list List<Data>
Collections.sort(data); // sorting
recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
adapter = new RecyclerViewAdapter(data, this);
recyclerView.setAdapter(adapter);
recyclerView.setNestedScrollingEnabled(false);
recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(this, recyclerView, new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
String s = data.get(position).title;
String ss = data.get(position).description;
Intent intent = new Intent(MainAcitivity.this, Work.class);
intent.putExtra("title", s);
intent.putExtra("desc", ss);
startActivity(intent);
}
}));
i know this line is causing problem
String s = data.get(position).title;
because it is same as old when list was not filtered ,how i can update this recyclerview so that i can get updated index when launching Work.class
okay guys this what works partially for me
i tried updating the data list by adding
data = filteredModelList; // add this line
after filtering and this worked fine
#Override
public boolean onQueryTextChange(String s) {
List<Data> filteredModelList = filter(data, s);
adapter.setFilter(filteredModelList);
data = filteredModelList; // add this line
return true;
}
now i get real index but when i search and clear the list the list is empty because i replaced the data list :(
EDIT :- i fixed it by hack not a nice way but works
as you can see the old list was gone by using above stuff before edit
when i used to change the data list
data = filteredModelList; // add this line
then the new data list has only filtered values and when you clear searchView then that data list is empty because it does not have those all apps list what i did was that i made new list as data_fix and onclicklistener i used datafix instead of data list
see this how
RecyclerItemClickListener method
String s = datafix.get(position).title; // data.get to datafix.get
String ss = datafix.get(position).description; // data.get to datafix.get
now onsetfilter call
i called this
datafix = filteredModelList; // instead of data = filteredModelList;
also i initialize it oncreate so it is not empty on launch of the app by
data = appsdata(); // load the list List<Data>
datafix = appsdata(); // load the list List<Data> added just below it to intialize
now this way the full loaded applist // List data stays full and doesnot clear even if you clear from searchView and index is all right
The logic lies to how you update the recycler view items,
public void setFilter(List<Data> searched_data) {
list = new ArrayList<>();
list.addAll(searched_data);
notifyDataSetChanged();
}
The above line is not necessary, adapter = new RecyclerViewAdapter(data, this); this line is very important to understand. You see when you pass data to the adapter is like this you are creating a symbolic link between the list in the adapter and outside. So therefore any changes made to the outside list, to update the adapter version.Just call adapter.notifyDatasetChange method
do this whenever any changes are made to the data model object.
CHANGE THE FOLLOWING FROM
List<Data> filteredModelList = filter(data, s);
adapter.setFilter(filteredModelList);
data = filteredModelList; // add this line
return true;
TO,
adapter.list.clear();
data = filter(data, s);
adapter.notifyDataSetChange();
Related
I have a recycleview showing a list of audio files fetched from my audios.json file hosted on my server. i have a model class with a getter method getLanguage() to see the audio language. I would like to show only audio files of users preference in recycle view. Say for example, if user wants only english and russian i would like to show only list of russian and english. How can we achieve this? Right now the entire list is displayed.
public class AudioAdapter extends RecyclerView.Adapter<AudioAdapter.HomeDataHolder> {
int currentPlayingPosition = -1;
Context context;
ItemClickListener itemClickListener;
List<Output> wikiList;
public AudioAdapter(List<Output> wikiList, Context context) {
this.wikiList = wikiList;
this.context = context;
}
#NonNull
#Override
public HomeDataHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(context).inflate(R.layout.audio_row_layout,viewGroup,false);
HomeDataHolder mh = new HomeDataHolder(view);
return mh;
}
#Override
public void onBindViewHolder(#NonNull final HomeDataHolder homeDataHolder, int i) {
String desc = wikiList.get(i).getLanguage() + " • " + wikiList.get(i).getType();
homeDataHolder.tvTitle.setText(wikiList.get(i).getTitle());
homeDataHolder.tvotherinfo.setText(desc);
homeDataHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (itemClickListener != null)
itemClickListener.onClick(view,homeDataHolder.getAdapterPosition());
}
});
homeDataHolder.rippleLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (itemClickListener != null)
itemClickListener.onClick(view,homeDataHolder.getAdapterPosition());
}
});
}
#Override
public int getItemCount() {
return wikiList.size();
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
public void setClickListener(ItemClickListener itemClickListener) { // Method for setting clicklistner interface
this.itemClickListener = itemClickListener;
}
public class HomeDataHolder extends RecyclerView.ViewHolder {
TextView tvTitle,tvotherinfo;
MaterialRippleLayout rippleLayout;
public HomeDataHolder(View v) {
super(v);
this.tvTitle = v.findViewById(R.id.title);
this.tvotherinfo = v.findViewById(R.id.audioDesc);
this.rippleLayout = v.findViewById(R.id.ripple);
}
}
}
The general idea for this should be:
you have one list with all items
you have filter rules selected by the user
You filter items from number 1, to see which ones match the constraints and store this in another list.
Then the recycler view only shows the items of the list from number 3.
This means that recycler view's getItemCount would return the size of the filtered list, not the whole list.
Instead of passing the wikiList as it is, filter it then send it:
Lets say that you filled up the wikiList, before passing it to the adapter, filter it like this:
In the activity that you initialize the adapter in:
public class YourActivity extends ............{
........
........
//your filled list
private List<Output> wikiList;
//filtered list
private List<Output> filteredList= new ArrayList<Output>();
//filters
private List<String> filters = new ArrayList<String>();
//lets say the user chooses the languages "english" and "russian" after a button click or anything (you can add as many as you want)
filters.add("english");
filters.add("russian");
//now filter the original list
for(int i = 0 ; i<wikiList.size() ; i++){
Output item = wikiList.get(i);
if(filters.contains(item.getLanguage())){
filteredList.add(item);
}
}
//now create your adapter and pass the filteredList instead of the wikiList
AudioAdapter adapter = new AudioAdapter(filteredList , this);
//set the adapter to your recyclerview........
......
.....
......
}
I use above "english" and "russian" for language. I don't know how they are set in your response, maybe you use "en" for "english" so be careful.
In the parent activity, I have an edit text in a toolbar, and user can make a search through the data displayed by the recyclerview.
When the user push enter key down, the string in the edittext is sent to the fragment by :
Bundle bundle = new Bundle();
bundle.putString(predResult, placeid);
MapFragment mapFragment = new MapFragment();
ListRestFragment listRestFragment = new ListRestFragment();
mapFragment.setArguments(bundle);
listRestFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.replace(R.id.map, mapFragment)
.replace(R.id.list_frag, listRestFragment)
.commit();
but, unfortunatly, the recyclerview is not resfreshed while my adapter is notified the data is changed as it shown below:
private void queryToList(Query query) {
query.addSnapshotListener((queryDocumentSnapshots, e) -> {
restaurantList = queryDocumentSnapshots.toObjects(Restaurant.class);
if (!myPrediction.contains("myPrediction")) {
System.out.println(myPrediction);
for (Restaurant item : restaurantList) {
if (item.getRestaurantID().contains(myPrediction)) {
restaurantListPred = new ArrayList<>();
restaurantListPred.add(item);
updateUI(restaurantListPred);
}
}
} else updateUI(restaurantList);
});
}
private void updateUI(List<Restaurant> restaurants) {
configureFab();
configureRecyclerView(restaurants);
}
private void configureRecyclerView(List<Restaurant> restaurant) {
this.adapter = new RestaurantAdapterClassic(restaurant);
this.recyclerView.setAdapter(this.adapter);
this.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL);
recyclerView.addItemDecoration(new SimpleItemDecorator(getContext()));
}
the new List is updated automatically when the user makes his request, but the recyclerView doesn't display the new data.
if you want to check my adapter implementation:
public class RestaurantAdapterClassic extends RecyclerView.Adapter<RestaurantViewHolder> {
private List<Restaurant> restaurantsList;
// CONSTRUCTOR
public RestaurantAdapterClassic(List<Restaurant> restaurants) {
this.restaurantsList = restaurants;
}
#Override
public RestaurantViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// CREATE VIEW HOLDER AND INFLATING ITS XML LAYOUT
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.listview_pattern, parent, false);
return new RestaurantViewHolder(view);
}
#Override
public void onBindViewHolder(RestaurantViewHolder viewHolder, int position) {
viewHolder.updateWithRestaurant(this.restaurantsList.get(position));
}
// RETURN THE TOTAL COUNT OF ITEMS IN THE LIST
#Override
public int getItemCount() {
return this.restaurantsList.size();
}
public Restaurant getRestaurant(int position) {
return this.restaurantsList.get(position);
}
public void filterList(List<Restaurant> filteredList) {
restaurantsList = filteredList;
notifyDataSetChanged();
}
}
where is my error or my misunderstanding?
EDIT SOLUTION -
Create an Interface
Actually, to send the new data data from my Parent Activity to my Fragment with a listener to observe when the data changes.
Keep The data reference sent to the adapter
Actually, the main big problem that I had was my adapter doesn't refresh when I sent new array. The reason was an adapter creates a reference with the list/array. if you want to refresh it, you need to keep this reference by get the list, erase it, and put/add new data inside by the method addALL for example.
First of all, adapter.notifyDataSetChanged(); doesn't have any effect in the code as inside updateUI you create the adapter every time you call it.
I think, the main problem is in here:
if (item.getRestaurantID().contains(myPrediction)) {
restaurantListPred = new ArrayList<>();
restaurantListPred.add(item);
updateUI(restaurantListPred);
}
This block doesn’t execute at all. Thats why the list not updated.
My database (Firestore) stores Charts, created by Users.
On opening the app, the User is shown their own Charts, sorted by Chart Name (this is displayed in a Fragment called MyChartsFragment). They can click a button to change the sort field; this should retrieve the same list of Charts, but sorted by the selected field.
I'm able to pass the new sort field name into MyChartsFragment, but at that point the list of Charts goes blank. I assume I need to redraw it somehow.
The MyChartsFragment is as follows (I've removed most of the try-catch blocks, error checks etc, for clarity):
public class FragmentMyCharts extends FragmentChartsList {
public FragmentMyCharts() {}
public Query getQuery(FirebaseFirestore databaseReference, String orderBy) {
// Specify the query which is used to retrieve this user's charts
return databaseReference.collection("charts")
.whereEqualTo("uid", getUid())
.orderBy(orderBy);
}
}
This extends FragmentChartsList, which contains:
public abstract class FragmentChartsList extends Fragment {
private FirebaseFirestore mDatabaseRef;
private FirestoreRecyclerAdapter<Chart, ChartViewHolder> mAdapter;
private Query mChartsQuery;
private RecyclerView mRecycler;
private String mOrder = "name";
FragmentManager mFragmentManager;
ToolbarFragmentListCharts mFragmentTLC;
public FragmentChartsList() {}
#Override
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
// Inflate layout, and find Recycler View to hold the list
View rootView = inflater.inflate(
R.layout.fragment_charts_list, container, false);
mRecycler = rootView.findViewById(R.id.charts_list);
mRecycler.setHasFixedSize(true);
// Set up Layout Manager, and set Recycler View to use it
LinearLayoutManager mManager = new LinearLayoutManager(getActivity());
mManager.setReverseLayout(true);
mManager.setStackFromEnd(true);
mRecycler.setLayoutManager(mManager);
// Connect to the database, and get the appropriate query
mDatabaseRef = FirebaseFirestore.getInstance();
mChartsQuery = getQuery(mDatabaseRef, mOrder);
// Set up Recycler Adapter
FirestoreRecyclerOptions<Chart> recyclerOptions = new FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.build();
mAdapter = new ChartListAdapter(recyclerOptions, this.getActivity());
// Use Recycler Adapter in RecyclerView
mRecycler.setAdapter(mAdapter);
return rootView;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Add listener to charts collection, and deal with any changes by re-showing the list
mChartsQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots,
#Nullable FirebaseFirestoreException e) {
if (queryDocumentSnapshots != null && queryDocumentSnapshots.isEmpty()) {
((MainActivity) Objects.requireNonNull(getActivity())).setPage(1);
}
}
});
}
#Override
public void onStart() {
super.onStart();
mAdapter.startListening();
}
public void setOrderField(String order) {
mOrder = order;
mChartsQuery = getQuery(mDatabaseRef, mOrder);
// Set up Recycler Adapter
FirestoreRecyclerOptions<Chart> recyclerOptions = new FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.build();
mAdapter = new ChartListAdapter(recyclerOptions, this.getActivity());
// Use Recycler Adapter in RecyclerView
mRecycler.setAdapter(mAdapter);
}
public abstract Query getQuery(FirebaseFirestore databaseReference, String orderBy);
}
So, when the main activity is notified by the toolbar that the sort field has changed, it retrieves the MyChartsFragment (as a FragmentChartsList) and calls the setOrderField function:
public void setOrder(String order) {
FragmentChartsList myCharts = (FragmentChartsList) mPagerAdapter.getItem(0);
myCharts.setOrderField(order);
}
This appears to update the value of mOrder correctly, but then it doesn't re-display the RecyclerView, so I just get a blank space.
What am I doing wrong?
EDITED TO ADD DETAIL:
My ChartListAdapter class is:
public class ChartListAdapter
extends FirestoreRecyclerAdapter<Chart, ChartViewHolder> {
private Activity mActivity;
private FirestoreRecyclerOptions<Chart> recyclerOptions;
public ChartListAdapter(FirestoreRecyclerOptions<Chart> recyclerOptions, Activity activity) {
super(recyclerOptions);
mActivity = activity;
}
#Override
protected void onBindViewHolder(#NonNull ChartViewHolder holder, int position, #NonNull Chart model) {
final String chartKey = this.getSnapshots().getSnapshot(position).getId();
model.setKey(chartKey);
// Set click listener for the chart
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(mActivity, ActivityViewChart.class);
intent.putExtra("ChartKey", chartKey);
mActivity.startActivity(intent);
}
});
// Implement long-click menu
mActivity.registerForContextMenu(holder.itemView);
// Bind Chart to ViewHolder
holder.bindToChart(model);
}
#NonNull
#Override
public ChartViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_chart, parent, false);
return new ChartViewHolder(view);
}
public void setRecyclerOptions(FirestoreRecyclerOptions<Chart> recyclerOptions) {
this.recyclerOptions = recyclerOptions;
}
}
rather then recreating the recyclerView adapter, try updating the options in the adapter.
public void setOrderField(String order) {
mOrder = order;
mChartsQuery = getQuery(mDatabaseRef, mOrder);
// Set up Recycler Adapter
FirestoreRecyclerOptions<Chart> recyclerOptions = new FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.build();
// Use Recycler Adapter in RecyclerView
mAdapter.setRecyclerOptions(recyclerOptions);
// then call the notify data set changed on the adapter to recreate the recyclerView elements
mAdapter.notifyDataSetChanged();
}
and in the recyclerView adapter class, add a setter for the recyclerOptions :
public void setRecyclerOptions(FirestoreRecyclerOptions<Chart> recyclerOptions) {
this.recyclerOptions = recyclerOptions;
}
Found one problem... I had declared mAdapter with:
private FirestoreRecyclerAdapter<Chart, ChartViewHolder> mAdapter;
when it should have been:
private ChartListAdapter mAdapter;
Solved... in setOrderField I was declaring a new mAdapter, but not listening to it. Once I added
mAdapter.startListening()
it all worked fine!
UPDATE 2021:
I don't know, for how long this new method works, but I'm so thankful for finding it out by myself, because I couldn't find any good response to that:
You don't have to create any Methods in your Adapter anymore.
Simply create the setOrder Method like this:
public FirestoreRecyclerOptions<event_log_Item> getOrder(String order){
String mOrder = order;
Query query = current_productRef.orderBy(mOrder, Query.Direction.DESCENDING);
FirestoreRecyclerOptions<event_log_Item> options = new FirestoreRecyclerOptions.Builder<event_log_Item>().setQuery(query, event_log_Item.class).build();
return options;
}
This Method will return the order-options for the field you want.
Then simply do:
mAdapter.updateOptions(setOrder("Timestamp"));
mAdapter.notifyDataSetChanged();
The Method "updateOptions" is already implemented. Just simply give it the option you want and add "notifyDataSetChanged();"
I have implemented my RecyclerView with it's Custom Adapter as follows
Global Declarations as follows
private LinearLayoutManager linearLayoutManager;
private int pastVisibleItems, visibleItemCount, totalItemCount;
private CustomRecyclerViewAdapter customRecyclerViewAdapter;
First I created Adapter Instance inside onCreate() method which has Empty Array inside it and set it to recyclerView
linearLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayoutManager);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(
Utility.ItemDecorationConst);
recyclerView.addItemDecoration(dividerItemDecoration);
customRecyclerViewAdapter = new CustomRecyclerViewAdapter(getActivity());
recyclerView.setAdapter(customRecyclerViewAdapter);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
visibleItemCount = linearLayoutManager.getChildCount();
totalItemCount = linearLayoutManager.getItemCount();
pastVisibleItems = linearLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if ((visibleItemCount + pastVisibleItems) >= totalItemCount) {
loading = false;
customRecyclerViewAdapter.addProgressBarEntry();
controller.getNextPage(PublisherAppContainerFragment.this);
}
}
}
});
After rendering complete View when I get data from AsyncTask for filling in recyclerView
I call following method of the Adapter to fill data
customRecyclerViewAdapter.addAll(myArray);
note : addAll() is not any overridden method
following is code of my CustomRecyclerViewAdapter
class CustomRecyclerViewAdapter extends RecyclerView.Adapter<CustomRecyclerViewAdapter.ViewHolder> {
ArrayList<MyModel> arrayList = new ArrayList<>();
Context context;
public CustomRecyclerViewAdapter(Context context) {
this.context = context;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewHolder viewHolder = null;
//inflated some view
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
//binded data to holder
}
#Override
public int getItemCount() {
return arrayList.size();
}
public void addAll(ArrayList myArray) {
this.arrayList.addAll(myArray)
}
public void clear() {
arrayList.clear();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public CardView cardView;
public ViewHolder(View view) {
super(view);
this.cardView = (CardView) view.findViewById(R.id.card_view);
this.cardView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
//handle operations
}
}
}
So whenever I get data from AsynTask I call method addAll() and recyclerView works like charm.
Now, My question is how it's working very well even though I have never called notifyDataSetChanged() on the adapter. Are there any previously registered Observers for the adapter? who observes if the dataset which has been returned in public int getItemCount() has been changed?
As I have read from documentation
void notifyDataSetChanged ()
Notify any registered observers that the data set has changed.
that means even though there are some observers registered you need to notify them using notifyDataSetChanged(). Right?
I also called
boolean flag = customRecyclerViewAdapter.hasObservers();
to know if there are any observers registered? Flag is True.
So anyone would please help me understand how exactly these things work?
If you look at the source of RecyclerView setAdapter call you will find a method setAdapterInternal(adapter, false, true);which is responsible for
Replaces the current adapter with the new one and triggers listeners.
This method is responsible for swapping the old adapter with the new one and internally it also registers for the custom Data Observer. This is the reason you are getting the flag as true
Based on what I can see of your code, I would say that there are not any observers attached to your RecyclerView that are picking up changes and keeping the list updated. What is more likely is that you are just getting "lucky" as when you scroll through the list the layout manager is continually calling getItemCount() on the adapter to determine if it should show more items. Whenever you call addAll(), you silently update the item count and it just happens to appear that observers were notified of the changes.
This is definitely a bug, and you would more likely see its effects in your implementation if you were dependent on a particular observer to monitor some aspect of the list, or doing more than just appending new items to the bottom (for example altering or inserting between existing items). The correct implementation as you pointed out is to call notifyDataSetChanged() whenever the list is updated, or even better be more specific with what changed if you can. For example, you can use:
public void addAll(ArrayList myArray) {
int positionStart = getItemCount() - 1;
this.arrayList.addAll(myArray);
notifyItemRangeInserted(positionStart, myArray.size());
}
public void clear() {
int oldSize = getItemCount();
arrayList.clear();
notifyItemRangeRemoved(0, oldSize);
}
My question is how it's working very well even though I have never called notifyDataSetChanged() on the adapter
It's because the addAll method by default calls the notifyDataSetChanged().
public void addAll(T ... items) {
synchronized (mLock) {
if (mOriginalValues != null) {
Collections.addAll(mOriginalValues, items);
} else {
Collections.addAll(mObjects, items);
}
}
if (mNotifyOnChange) notifyDataSetChanged();
}
And
public void addAll(#NonNull Collection<? extends T> collection) {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.addAll(collection);
} else {
mObjects.addAll(collection);
}
}
if (mNotifyOnChange) notifyDataSetChanged();
}
Here's the link - https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/ArrayAdapter.java
EDIT - I see that you have your own addAll method which is calling addAll method of ArrayList.
This is how addAll method works -
private ArrayList<String> ex1 = new ArrayList();
private ArrayList<String> ex2 = new ArrayList();
private ArrayList<String> ex3 = new ArrayList();
ex1.add("one");
ex2.add("two");
ex3.addAll(ex1);
ex3.addAll(ex2);
System.out.println(ex3);
OUTPUT - [one, two]
This is what happening in your case.
I have shown progress bar and once I fetch data I hide the progress bar and make recyclerView visible - If in layout or code you set RecyclerView visibility GONE then layout will not happen and that is why Adapter.getItemsCount() not get called. So if you fetch data and populate adapter array with it and then change RecyclerView visibility from GONE to VISIBLE it will trigger update.
In case you don't call notifyDataSetChanged() RecyclerView will not know about update. I guess there is something else in your code that trigger RecyclerView update. To clarify this behavior let's use some dummy adapter:
private class DummyViewHolder extends RecyclerView.ViewHolder {
public DummyViewHolder (View itemView) {
super(itemView);
}
}
private class Adapter extends RecyclerView.Adapter<DummyViewHolder> {
private int mDummySize = 5;
#Override
public DummyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.dummy_view, parent, false);
return new DummyViewHolder(view);
}
#Override
public void onBindViewHolder(DummyViewHolder holder, int position) {
}
void setSize(int size) { this.mDummySize = size; }
#Override
public int getItemCount() {
return mDummySize;
}
}
And in onCraete() :
ViewHolder v = ...
final Adapter adapter = ..
...
//postpone adapter update
(new Handler()).postDelayed(new Runnable() {
#Override
public void run() {
adapter.setSize(10);//and nothing happend only 5 items on screen
}
}, 5000);
I saw all the questions which is similar to my question ( in this , this , this and this link )
I had myAdapter.notifyDataSetChanged() in my Activity but it doesn't work
I have 3 classes,
DBHelper - For storing and getting Database contents ( NO ISSUES HERE )
SimpleRecyclerAdapter - Adapter for RecyclerList
ThirdActivity
What i did in ThirdActivity :
I have TextBox to get data and store it in Database and a Button. In
the Onclicklistener of Button, i specified code to
get text from textbox
add it into table using DBHelper
retrive data as ArrayList from DBHelper
myAdapter.notifyDataSetChanged()
When i click the Button, I got Data in LogCat which i specified inside OnclickListener but it is not reflected to the listview.
Here is my code,
ThirdActivity:
public class ThirdActivity extends AppCompatActivity{
private DrawerLayout mDrawerLayout;
DbHelper dbHelper;
EditText et;
Button addButton;
RecyclerView rv;
ArrayList<String> myNotesList;
SimpleRecycler3Adapter adapter3;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.thirdactivity);
myNotesList = new ArrayList<>();
et=(EditText) findViewById(R.id.et);
addButton=(Button)findViewById(R.id.addButton);
rv = (RecyclerView) findViewById(R.id.dbListrv);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getBaseContext());
rv.setLayoutManager(linearLayoutManager);
rv.setHasFixedSize(true);
adapter3 = new SimpleRecycler3Adapter(myNotesList);
rv.setAdapter(adapter3);
dbHelper = new DbHelper(this, null, null, 1);
addButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("DB", "Constructor");
String note=et.getText().toString();
dbHelper.addNote(note);
printData();
}
});
}
public void printData(){
Log.d("DB","Constructor");
myNotesList=dbHelper.databasetostring();
Log.d("DB","Data came"+myNotesList.get(myNotesList.size()-1));
// adapter3 = new SimpleRecycler3Adapter(myNotesList);
// rv.setAdapter(adapter3);
adapter3.notifyDataSetChanged();
}
}
SimpleRecyclerViewAdapter :
public class SimpleRecycler3Adapter extends RecyclerView.Adapter<SimpleRecycler3Adapter.NotesHolder> {
private ArrayList<String> myNotesList=new ArrayList<String>();
String TAG="ThirdAdapter kbt";
RecyclerView rv;
public SimpleRecycler3Adapter(ArrayList<String> myList) {
Log.d(TAG,"Constructor");
Log.d(TAG,"Not null");
int i = 0;
while (i < myNotesList.size()) {
myNotesList.add(myList.get(i).toString());
}
Log.d(TAG,"finish");
}
#Override
public NotesHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
Log.d(TAG,"On create started");
View view2 = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recyclerlist3_item, viewGroup, false);
Log.d(TAG,"ADAP STAR ONCR second switch 2nd line");
NotesHolder viewHolder2 = new NotesHolder(view2);
Log.d(TAG,"ADAP STAR ONCR second switch 3nd line");
return viewHolder2;
}
#Override
public void onBindViewHolder(NotesHolder notesHolder, int i) {
Log.d(TAG, "ONBIND SECOND i value is " + i);
// notesHolder.thumbnail.setImageResource(R.drawable.placeholder);
notesHolder.dblistitem.setText(myNotesList.get(i));
Log.d(TAG,"ONBIND second title issssss");
}
#Override
public int getItemCount() {
return myNotesList.size();
}
class NotesHolder extends RecyclerView.ViewHolder {
protected ImageView thumbnail;
protected TextView dblistitem;
public NotesHolder(View itemView) {
super(itemView);
Log.d(TAG, "JSON Inside HOLDER");
rv=(RecyclerView)itemView.findViewById(R.id.dbListrv);
// thumbnail = (ImageView) itemView.findViewById(R.id.thumbnail);
dblistitem = (TextView) itemView.findViewById(R.id.dblistitem);
}
}
}
You're not updating the myNotesList that is in adapter class but in activity class. But the adapter uses it's local myNotesList.
So on button click, update myNotesList of adapter with latest data available and notify the adapter.
EDIT
Pass the latest data to adapter. Have this method in adapter class and call this before notifyDataSetChanged();
public void updateNotes(ArrayList<String> notesList) {
myNotesList = notesList;
}
1.you are intializing your dbhelper after setting adapter to listview so it couldn't contain any data initially
2.for updating recycler view data list do as follows
myNotesList.clear();
myNotesList.addAll(dbHelper.databasetostring());
adapter3.notifyDataSetChanged();
You have a problem in your SimpleRecyclerViewAdapter, just change this:
while (i < myNotesList.size()) {
myNotesList.add(myList.get(i).toString());
}
For this:
myNotesList = myList;
And in your activity's printData() change:
myNotesList=dbHelper.databasetostring();
for this:
myNotesList.clear();
myNotesList.addAll(dbHelper.databasetostring());
adapter3.notifyDataSetChanged();
Explanation:
First you initialize myNotesList variable:
myNotesList = new ArrayList<>();
Then you initialize adapter3
adapter3 = new SimpleRecycler3Adapter(myNotesList);
But your adapter is not saving the reference, instead you're copying its data into another variable:
while (i < myNotesList.size()) {
myNotesList.add(myList.get(i).toString());
}
Doing that, if you change myNotesList variable in your activity will not modify your adapter's dataset.
In your method printData() you change myNotesList variable. Which will not touch your adapter or its data
public void printData(){
Log.d("DB","Constructor");
myNotesList=dbHelper.databasetostring();
Log.d("DB","Data came"+myNotesList.get(myNotesList.size()-1));
// adapter3 = new SimpleRecycler3Adapter(myNotesList);
// rv.setAdapter(adapter3);
adapter3.notifyDataSetChanged();
}
You can't change myNotesList by changing myList.
public SimpleRecycler3Adapter(ArrayList<String> myList) {
Log.d(TAG,"Constructor");
Log.d(TAG,"Not null");
// int i = 0;
// while (i < myNotesList.size()) {
// myNotesList.add(myList.get(i).toString());
// }
this.myNotesList = myList;
Log.d(TAG,"finish");
}
Not a good idea to call notifyDataSetChanged() when you know exactly what changed in your data collection.
See this implementation here.
They have even documented to use notifyDataSetChanged() as a last resort in this doc.
You get nice animations for free if you use methods like notifyItemInserted() and the rest.
Also do not go on replacing the collection object entirely, see the implmentation link that has been attached.