I am using a RecyclerView to show the data that is retrieved after parsing from the web services. The problem is notifydatasetchanged is not working at all. For solving that thing, I am setting the adapter again.
I am having two problems using this technique:
There is a screen blink while setting a new adapter again.
Also the recycler view points to first item of the recycler view. (I just want to append the new data at the end of the existing list.Array list size is updating. But the only problem, the list is started from the zeroth position.)
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_book_mag_list_child, container, false);
recyclerView = (RecyclerView) view.findViewById(R.id.bookMagRecyclerViewList);
bookMagDataList = new ArrayList<>();
adapter = new BookMagListRecyclerViewAdapter(getActivity(), bookMagDataList);
recyclerView.setAdapter(adapter);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
//linearLayoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setOnScrollListener(new EndlessRecyclerScrollListener(linearLayoutManager) {
#Override
public void onLoadMore(int current_page) {
Log.d("Do Something", "Do Something" + current_page);
params.setStart("" + current_page);
dataModel.init(params);
}
});
return view;
}
#Override
public void update() {
if (dataModel.getFragmentData(subScreenId) != null) {
bookMagDataList = dataModel.getFragmentData(subScreenId);
Log.d("MyDataListSize: ", "" + bookMagDataList.size());
adapter = new BookMagListRecyclerViewAdapter(getActivity(), bookMagDataList);
recyclerView.setAdapter(adapter);
}
}
Please check what is the issue and let me know if it can be solved.
The problem is in your update function. You are overriding the bookMagDataList list with new instance everytime your update. You should have use .addAll() function to add in new element. Then call adapter.notifyDataSetChanged() after that. Make changes as below.
#Override
public void update() {
if (dataModel.getFragmentData(subScreenId) != null) {
bookMagDataList.addAll(dataModel.getFragmentData(subScreenId));
Log.d("MyDataListSize: ", "" + bookMagDataList.size());
adapter.notifyDataSetChanged();
}
}
since u didnt posted your adapter code u have to use styx approach:
#Override
public void update() {
if (dataModel.getFragmentData(subScreenId) != null) {
bookMagDataList.addAll(dataModel.getFragmentData(subScreenId));
Log.d("MyDataListSize: ", "" + bookMagDataList.size());
adapter.notifyDataSetChanged();
}
}
answer is given by styx so credits to him
my solution is below
if (adapter == null) {
adapter = new BookMagListRecyclerViewAdapter(getActivity(), bookMagDataList);
recyclerView.setAdapter(adapter);
} else {
adapter.bookMagDataList.clear();
adapter.bookMagDataList = bookMagDataList;
myAdapter.notifyDataSetChanged()
}
Don't instantiate adapter again, only you should add new data in arrayList then invoke notifyDatasetChanged()
like--
bookMagDataList.add("YOUR ITEM");
adpter.notifyDatasetChanged();
you have to do like below in update method,
if(adapter == null){
adapter = new BookMagListRecyclerViewAdapter(getActivity(), bookMagDataList);
recyclerView.setAdapter(adapter);
}else{
myAdapter.notifyDataSetChanged();
}
Related
I have a recycler view for image display with a simple adapter.
public class ImageListAdapter extends RecyclerView.Adapter<ImageListAdapter.SingleItemRowHolder> {
private ArrayList<Bitmap> itemsList;
public ImageListAdapter(ArrayList<Bitmap> itemsList) {
this.itemsList = itemsList;
}
#Override
public SingleItemRowHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.image_card, null);
return new SingleItemRowHolder(v);
}
#Override
public void onBindViewHolder(SingleItemRowHolder holder, int i) {
Bitmap img = itemsList.get(i);
holder.itemImage.setImageBitmap(img);
}
#Override
public int getItemCount() {
return (null != itemsList ? itemsList.size() : 0);
}
public void addItem(Bitmap image) {
itemsList.add(image);
notifyItemInserted(itemsList.size()-1);
}
public void setItemsList(ArrayList<Bitmap> bmps) {
itemsList = bmps;
}
public ArrayList<Bitmap> getItemsList() {
return itemsList;
}
public void cleanData() {
itemsList = new ArrayList<>();
notifyDataSetChanged();
}
public class SingleItemRowHolder extends RecyclerView.ViewHolder {
protected ImageView itemImage;
public SingleItemRowHolder(View view) {
super(view);
this.itemImage = (ImageView) view.findViewById(R.id.itemImage);
}
}
}
Nothing complex here. Just a simple adapter that getting bitmap list as input and show them in the recycle view.
The point is, the bitmaps are asynchronously added via a retrofit call, as shown in the folloing:
googleMapService.getImageByPhotoReference(s, 300, 300, apiKey).enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
if (response.body() != null) {
Bitmap bmp = BitmapFactory.decodeStream(response.body().byteStream());
adapter.addItem(bmp);
}
}
}
});
As you can see here, the bitmap is added to the adapter and the adapter should be able to notify the changes as addItem() contains a notifyItemInserted() call. And this retrofit call is called several times depending on how many times the view model has changes its value (there is a observer observing the change of view model's value. Once the value changes, the above retrofit will get called). So the code that changes view model value look like this:
if (placeDetail.getPhotoRef() != null) {
for (PlaceDetail.PhotoRef ref : placeDetail.getPhotoRef()) {
viewModel.setPhoto(ref.getPhotoRef()); // ref.getPhotoRef returns the photo ref string
}
}
In my expectation, the item list of the adapter should now contains several images. I tried to debug it, and I found when the addItem() method get called, the amount of item list did change as expected. But when it came into the onBindViewHolder() callback, there is only one item left in the item list, which is the first bitmap.
Can anyone tell me why this issue happened? Any suggestion would be appreciated.
---------------------------------- Update --------------------------------
Interesting. I finally found why it only shows one photo. As you can see here, the adapter (id:24856) item list size is 4, which is in my expectation.
However, there is another adapter(id:24962) exists. For that adapter, the add item method only called once thus only one item in that adapter. When the recycler view changes the content, the onBindViewHolder is called on the second adapter. Now my question is, where does the second adapter come from ???
Here is my code to initialize recycler view and adapter
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
binding = PlaceFragmentBinding.inflate(getLayoutInflater());
RecyclerView recyclerView = binding.imageGallery;
recyclerView.setHasFixedSize(true);
ImageListAdapter adapter = new ImageListAdapter(new ArrayList<>());
recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
recyclerView.setAdapter(adapter);
return binding.getRoot();
}
Hej AhSoHard,
did you check if it is a timing issue, because you are loading your data asynchronously and it could lead to missing updates.
also could it be that setItemsList(ArrayList<Bitmap> bmps) is called and overwrites the current list?
is there a reason why you do not use the viewGroup as a base for inflating the layout resource in onCreateViewHolder()?
LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.image_card, viewGroup, false)
in my opinion the Adapter should not be the holder of the current state, maybe have a list outside and use a ListAdapter to display it. When you have a new list of bitmaps, you just call adapter.submit(bitmaps) and the new list will be displayed. Together with a DiffCallback, the old items will not update and the new ones added at the end of the list.
Let's implement the DiffUtils adapter and try to change your calls in this way. As kotlin works as interoperable with java, therefore I am mentioning the gist for an adapter that is written in kotlin.
Check adapter and view holder implementation
Code
ImageListAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = PlaceFragmentBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
binding.imageGallery.setHasFixedSize(true);
adapter = new ImageListAdapter(new ArrayList<>());
binding.imageGallery.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
binding.imageGallery.setAdapter(adapter);
return binding.getRoot();
}
Interface googleMapService
Single<ResponseBody> getImageByPhotoReference(_,_,_,_)
Network Call
googleMapService.getImageByPhotoReference(s, 300, 300, apiKey)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ResponseBody>() {
#Override
public void onSuccess(ResponseBody response) {
if (response != null) {
Bitmap bmp = BitmapFactory.decodeStream(response.body().byteStream());
adapter.addItem(bmp);
}
}
#Override
public void onError(Throwable e) {
// Do with your error
}
});
It's interesting that you initialize the adapter in onCreateView method and it has local visibility(only visible inside of the onCreateView method). But in the googleMapService.getImageByPhotoReference() method's callback you are referencing it adapter.addItem(bmp);. So my guess is that you are referencing the wrong adapter object in the googleMapService.getImageByPhotoReference() method's callback. The callback may reference an old instance of ImageListAdapter.
Try to initialize ImageListAdapter adapter as a property of your Fragment class and see if it works.
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.
I must implement comments list with replies. I use same Item for comment and it's reply. But when I have multiple replies for comment and scroll up to it Recycler View lost position while binding that item.
public void setItem(Adaptable item) {
super.setItem(item);
this.comment = (Comment) item;
binding.setComment(comment);
binding.setViewHolder(this);
binding.tvMessage.setText(getFormattedText());
initReplies();
}
private void initReplies() {
if (comment.repliesCount <= 0) {
binding.recyclerView.setVisibility(View.GONE);
return;
}
binding.recyclerView.setVisibility(View.VISIBLE);
binding.recyclerView.setNestedScrollingEnabled(false);
if (adapter == null) {
LinearLayoutManager layoutManager = new LinearLayoutManager(fragment.getContext(), LinearLayoutManager.VERTICAL, true);
binding.recyclerView.setLayoutManager(layoutManager);
adapter = new RecyclerViewAdapter(fragment, Pages.ID_COMMENTS_REPLY, false);
}
adapter.clear();
adapter.addItems(new ArrayList<>(comment.replies), getCommentMeta());
binding.recyclerView.setAdapter(adapter);
}
setItem method I call from onBindViewHolder
I have an app which displays the Matchday of some soccer competitions depending date of the match. I am using a spinner to re-create the adapter of my RecyclerView depending on the User selection with some dummyData(). But sometimes while the RecyclerView is first initialized or recycled it displays literally the format of my .xml.
I managed to reduce the level of visual glitch by adding a custom animation on the Adapter but still sometimes it just happens, like 10% of the time I change Matchday.
format_home.xml
This is the placeholder I created to design this format.
Gif showing problem:
HomeFragment.java
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_home, container, false);
mRecyclerView = view.findViewById(R.id.home_recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
mSpinner = view.findViewById(R.id.spinner_home);
List<String> matchDay = new ArrayList<>();
for (int i = 0; i < 20; i++){
matchDay.add("Matchday " + (i + 1));
}
HomeSpinnerAdapter dataAdapter = new HomeSpinnerAdapter(getContext(), R.layout.format_home_spinner, matchDay);
mSpinner.setAdapter(dataAdapter);
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mHomeItems = new ArrayList<>();
mDisposable.add(Observable.fromArray(mHomeItems)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(homeItems -> {
Map<String, List<Matchday>> hashMap = toMap(dummyData());
// Map Key
for (String date : hashMap.keySet()) {
Header header = new Header(date);
homeItems.add(header);
for (Matchday matchday : hashMap.get(date)) {
MatchItem matchItem = new MatchItem(matchday);
homeItems.add(matchItem);
}
}
mAdapter = new HomeAdapter(homeItems, getActivity(), mTeamViewModel);
mRecyclerView.setAdapter(mAdapter);
}));
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
return view;
}
// Map Value List<T>
private Map<String,List<Matchday>> toMap(List<Matchday> matchdays) {
Map<String, List<Matchday>> map = new TreeMap<>();
for (Matchday matchday : matchdays){
List<Matchday> value = map.get(matchday.getDate());
if (value == null) {
value = new ArrayList<>();
map.put(matchday.getDate(), value);
}
value.add(matchday);
}
return map;
}
public List<Matchday> dummyData(){
mMatchdayList = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 20; i ++){
mMatchdayList.add(new Matchday((i+1), buildRandomDateInCurrentMonth(), 1, random.nextInt(36), 0, 0, random.nextInt(36)));
}
return mMatchdayList;
}
If you have any feedback or need any other activity, let me know! TY
Thanks to #jantursky and some deep digging in the web I came across this series of articles and managed to identify that my RecyclerView was recreating all views in the absence of setStableIds(true) because in recyclers Scrap lists are the first place where the RecyclerView looks when searching for a ViewHolder.
So to resolve the problem:
Created an interface to retrieve List IDS.
Created a refreshAdapter() method in the Adapter.
Initialized and instantiate the Adapter outside the ` mSpinner.setOnItemSelectedListener() with setHasStableIds(true).
Inside the mSpinner.setOnItemSelectedListenercall refreshAdapter()
I am using a Staggered Grid Layout to show data to recycler view from database.I have faced a problem where after deleting an item from db as well as remove position from adapter, I got some item rendering issues.Like the scattered all over the place.
Here is my code
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_note_favorite);
ButterKnife.bind(this);
toolbar = (Toolbar) findViewById(R.id.fav_note_toolbar);
setSupportActionBar(toolbar);
noteAdapter = new NoteAdapter(noteModelList, NoteFavoriteActivity.this);
layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(noteAdapter);
touchListenerRecycler();
loadDataAll();
}
private void loadAll() {
noteModelList.clear();
DBManagerFav dbManagerFav = DBManagerFav.getInstance(NoteFavoriteActivity.this);
dbManagerFav.openDataBase();
noteModelList = dbManagerFav.getAllNoteList();
Log.i(TAG, " size : " + noteModelList.size());
noteAdapter = new NoteAdapter(noteModelList, NoteFavoriteActivity.this);
recyclerView.setAdapter(noteAdapter);
noteAdapter.notifyDataSetChanged();
dbManagerFav.closeDataBase();
}
private void deleteOperation() {
DBManagerFav dbManagerFav = DBManagerFav.getInstance(NoteFavoriteActivity.this);
dbManagerFav.openDataBase();
NoteModel noteModel = new NoteModel();
noteModel.setId(noteModelList.get(adapterClickedPosition).getId());
int status = dbManagerFav.deleteNote(noteModelList.get(adapterClickedPosition).getId());
if (status > 0) {
noteAdapter.removeAt(adapterClickedPosition);
}
dbManagerFav.closeDataBase();
loadDataAll();
}
//this belongs to adapter
public void removeAt(int position) {
Log.d(TAG, " removing at position : " + position);
noteModelList.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, noteModelList.size());
notifyDataSetChanged();
}
I have attached two screenshots before and after
Before deleting an item
after deleting an item
Can you point me out what else do I need ?
You can put inside loadDataAll(){
layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
//........
}
Voila..