The problem is that in my tablayout when im switching between tabs my list duplicating. So i need to remove list on onStop() to recreate it then. Or might be other better solution.
I have tried the following solutions
https://code-examples.net/en/q/1c97047
How to reset recyclerView position item views to original state after refreshing adapter
Remove all items from RecyclerView
My code of adapter
public class OnlineUsersAdapter extends RecyclerView.Adapter<OnlineUsersAdapter.OnlineUserViewHolder> {
private List<OnlineUser> onlineUsers = new ArrayList<>();
private OnItemClickListener.OnItemClickCallback onItemClickCallback;
private OnItemClickListener.OnItemClickCallback onChatClickCallback;
private OnItemClickListener.OnItemClickCallback onLikeClickCallback;
private Context context;
public OnlineUsersAdapter(Context context) {
this.onlineUsers = new ArrayList<>();
this.context = context;
}
#NonNull
#Override
public OnlineUsersAdapter.OnlineUserViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
return new OnlineUsersAdapter.OnlineUserViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull OnlineUsersAdapter.OnlineUserViewHolder holder, int position) {
OnlineUser user = onlineUsers.get(position);
Log.d("testList", "rating " + user.getRating() + " uid " + user.getUid());
holder.bind(user, position);
}
#Override
public int getItemCount() {
return onlineUsers.size();
}
class OnlineUserViewHolder extends RecyclerView.ViewHolder {
RelativeLayout container;
ImageView imageView, likeBtn, chatBtn;
TextView name, country;
private LottieAnimationView animationView;
OnlineUserViewHolder(View itemView) {
super(itemView);
context = itemView.getContext();
container = itemView.findViewById(R.id.item_user_container);
imageView = itemView.findViewById(R.id.user_img);
likeBtn = itemView.findViewById(R.id.search_btn_like);
chatBtn = itemView.findViewById(R.id.search_btn_chat);
name = itemView.findViewById(R.id.user_name);
country = itemView.findViewById(R.id.user_country);
animationView = itemView.findViewById(R.id.lottieAnimationView);
}
void bind(OnlineUser user, int position) {
ViewCompat.setTransitionName(imageView, user.getName());
if (FirebaseUtils.isUserExist() && user.getUid() != null) {
new FriendRepository().isLiked(user.getUid(), flag -> {
if (flag) {
likeBtn.setBackground(ContextCompat.getDrawable(context, R.drawable.ic_favorite));
animationView.setVisibility(View.VISIBLE);
} else {
likeBtn.setBackground(ContextCompat.getDrawable(context, R.drawable.heart_outline));
animationView.setVisibility(View.GONE);
}
});
}
if (user.getUid() != null) {
chatBtn.setOnClickListener(new OnItemClickListener(position, onChatClickCallback));
likeBtn.setOnClickListener(new OnItemClickListener(position, onLikeClickCallback));
}
imageView.setOnClickListener(new OnItemClickListener(position, onItemClickCallback));
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
if (user.getImage().equals(Consts.DEFAULT)) {
Glide.with(context).load(context.getResources().getDrawable(R.drawable.default_avatar)).into(imageView);
} else {
Glide.with(context).load(user.getImage()).thumbnail(0.5f).into(imageView);
}
country.setText(user.getCountry());
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(500);
animator.addUpdateListener(valueAnimator ->
animationView.setProgress((Float) valueAnimator.getAnimatedValue()));
if (animationView.getProgress() == 0f) {
animator.start();
} else {
animationView.setProgress(0f);
}
}
}
public OnlineUsersAdapter(OnItemClickListener.OnItemClickCallback onItemClickCallback,
OnItemClickListener.OnItemClickCallback onChatClickCallback,
OnItemClickListener.OnItemClickCallback onLikeClickCallback) {
this.onItemClickCallback = onItemClickCallback;
this.onChatClickCallback = onChatClickCallback;
this.onLikeClickCallback = onLikeClickCallback;
}
public void addUsers(List<OnlineUser> userList) {
int initSize = userList.size();
onlineUsers.addAll(userList);
// notifyItemRangeInserted(onlineUsers.size() - userList.size(), onlineUsers.size());
}
public String getLastItemId() {
return onlineUsers.get(onlineUsers.size() - 1).getUid();
}
public void clearData() {
List<OnlineUser> data = new ArrayList<>();
addUsers(data);
notifyDataSetChanged();
}
My code in fragment
#Override
public void onStop() {
super.onStop();
firstUid = "";
stopDownloadList = false;
List<OnlineUser> list = new ArrayList<>();
mAdapter.addUsers(list);
mAdapter.notifyDataSetChanged();
}
`users are added after callback
#Override
public void addUsers(List<OnlineUser> onlineUsers) {
if (firstUid.equals("")){
firstUid = onlineUsers.get(0).getUid();
}
if (!firstUid.equals("") && onlineUsers.contains(firstUid)){
stopDownloadList = true;
}
if (!stopDownloadList){
mAdapter.addUsers(onlineUsers);
}
setRefreshProgress(false);
isLoading = false;
isMaxData = true;
}
The line mAdapter.addUsers(onlineUsers); from addUsers method gets called twice. Looks like your asynchronous operation gets triggered twice (e. g. from repeating lifecycle methods like onCreate/onCreateView/onViewCreated).
Solution #1: request users a single time
Move your user requesting machinery to onCreate or onAttach. This will save network traffic but could lead to showing outdated data.
Solution #2: replaceUsers
Your clearData calls mAdapter.addUsers(new ArrayList<>()); (btw, take a look at Collections.emptyList()). Looks like you're trying to replace adapter data but appending instead. Replacement method could look like
public void replaceUsers(List<OnlineUser> userList) {
int oldSize = userList.size();
onlineUsers = userList;
notifyItemRangeRemoved(0, oldSize);
notifyItemRangeInserted(0, userList.size);
}
This version still requeses users every time your fragment gets focused but shows fresher data.
Related
I've implemented recycler-view with multi selection using the selection-tracker api.
First list of data works properly, but when there's new data with different size then the problem occurs. I've tried to create new tracker on each load but that totally failed to work.
Problem is, when the listdata is refreshed from the api the ItemKeyProvider.class#getPosition() returns null.
How can i implement selections tracker with dynamic data avoiding this issue?
private void loadPurchaseOrders(ArrayList<PurchaseOrder> newPurchaseOrders) {
purchaseOrders.clear();
purchaseOrders.addAll(newPurchaseOrders);
if (purchaseOrderRecyclerAdapter == null) {
/* if (workOrderRecyclerAdapter == null) {*/
purchaseOrderRecyclerAdapter = new PurchaseOrderRecyclerAdapter(getPurchaseOrders(),
statusId,
purchaseOrder -> getPurchaseOrderDetail(CURRENT_STATE, purchaseOrder));
LinearLayoutManager layoutManager = new LinearLayoutManager(this,
LinearLayoutManager.VERTICAL, false);
binding.rvPo.setLayoutManager(layoutManager);
binding.rvPo.setAdapter(purchaseOrderRecyclerAdapter);
selectionTrackerBuilder = new SelectionTracker.Builder<>(
new Random().toString(),//unique id
binding.rvPo,
new POItemKeyProvider(getPurchaseOrders()),
new POItemDetailsLookup(binding.rvPo),
StorageStrategy.createStringStorage());
selectionTracker = selectionTrackerBuilder.build();
purchaseOrderRecyclerAdapter.setSelectionTracker(selectionTracker);
} else {
purchaseOrderRecyclerAdapter.notifyDataSetChanged();
}
POItemKeyProvider.class
private static class POItemKeyProvider extends ItemKeyProvider<String> {
private final Map<String, Integer> mKeyToPosition;
private List<PurchaseOrder> mPokemonList;
POItemKeyProvider(List<PurchaseOrder> pokemonList) {
super(SCOPE_CACHED);
mPokemonList = pokemonList;
mKeyToPosition = new HashMap<>(mPokemonList.size());
int i = 0;
for (PurchaseOrder pokemon : pokemonList) {
mKeyToPosition.put(pokemon.getOrderNo(), i);
i++;
}
}
#Nullable
#Override
public String getKey(int i) {
return mPokemonList.get(i).getOrderNo();// directly from position to key
}
#Override
public int getPosition(#NonNull String s) {
return mKeyToPosition.get(s);
}
}
POItemDetailsLookup.class
private static class POItemDetailsLookup extends ItemDetailsLookup<String> {
RecyclerView mRecyclerView;
POItemDetailsLookup(RecyclerView recyclerView) {
this.mRecyclerView = recyclerView;
}
#Nullable
#Override
public ItemDetails<String> getItemDetails(#NonNull MotionEvent motionEvent) {
View view = mRecyclerView.findChildViewUnder(motionEvent.getX(), motionEvent.getY());
if (view != null) {
RecyclerView.ViewHolder viewHolder = mRecyclerView.getChildViewHolder(view);
// int position = viewHolder.getAdapterPosition();
if (viewHolder instanceof PurchaseOrderRecyclerAdapter.ViewHolder) {
return ((PurchaseOrderRecyclerAdapter.ViewHolder) viewHolder).getItemDetails(motionEvent);
}
}
return null;
}
}
This implementation should work with dynamic data:
https://github.com/ExpensiveBelly/RecyclerViewSelection
Because of this:
override fun submitList(list: List<T>?) {
super.submitList(list)
tracker.deselectIfItemsNotFound(currentList)
}
When I try to load more items when the user had scrolled to the end, a progress dialog is displayed and removed and then again displayed and then more items are displayed but progress dialog is still not removed from recycler view. And sometimes progress dialog is removed but still, it takes more time to display more items in recycler view. I'm using firestore addOnSuccessListener to retrieve items from cloud firestore and adding it to an arraylist and then adding native ads advanced to that arraylist and add the data to the recycler adapter.
Activity where recycler view items are displayed, MovieLists.java
public class MovieLists extends AppCompatActivity implements ItemInterface{
//nothing important just a comment
boolean runshowMovieOnce = true;
Query mQuery;
int loaderPosition;
DocumentSnapshot lastVisible;
String docId, catName, titleName;
ItemInterface itemInterface;
DatabaseReference listData;
String VideoId;
MovieListAdapter movieListAdapter;
RecyclerView movieLists;
// List of native ads that have been successfully loaded.
private List<UnifiedNativeAd> mNativeAds = new ArrayList<>();
List<Object> itemDatas = new ArrayList<>();
LinearLayoutManager layoutManager;
// The number of native ads to load.
public static final int NUMBER_OF_ADS = 2;
// The AdLoader used to load ads.
private AdLoader adLoader;
int index = 2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_movie_lists);
MaterialToolbar mToolBar = (MaterialToolbar) findViewById(R.id.imagePreviewToolbar);
setSupportActionBar(mToolBar);
docId = getIntent().getStringExtra("documentId");
catName = getIntent().getStringExtra("catName");
titleName = getIntent().getStringExtra("titleName");
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeAsUpIndicator(R.drawable.back_button);
getSupportActionBar().setTitle(titleName);
}
movieLists = (RecyclerView)findViewById(R.id.movie_lists);
layoutManager = new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);
itemInterface = this;
loadMovies();
}
#Override
public void onFirebaseLoadSuccess(List<Object> itemDataList) {
loadNativeAds();
}
#Override
public void onFirebaseLoadFailed(String message) {
}
#SuppressLint("MissingPermission")
private void loadNativeAds() {
AdLoader.Builder builder = new AdLoader.Builder(this, "ca-app-pub-3940256099942544/8135179316");
adLoader = builder.forUnifiedNativeAd(
new UnifiedNativeAd.OnUnifiedNativeAdLoadedListener() {
#Override
public void onUnifiedNativeAdLoaded(UnifiedNativeAd unifiedNativeAd) {
// A native ad loaded successfully, check if the ad loader has finished loading
// and if so, insert the ads into the list.
mNativeAds.add(unifiedNativeAd);
if (!adLoader.isLoading()) {
insertAdsInMenuItems();
}
}
}).withAdListener(
new AdListener() {
#Override
public void onAdFailedToLoad(int errorCode) {
// A native ad failed to load, check if the ad loader has finished loading
// and if so, insert the ads into the list.
Log.e("MainActivity", "The previous native ad failed to load. Attempting to"
+ " load another.");
if (!adLoader.isLoading()) {
insertAdsInMenuItems();
}
}
}).build();
// Load the Native ads.
adLoader.loadAds(new AdRequest.Builder().build(), NUMBER_OF_ADS);
}
private void insertAdsInMenuItems() {
if (mNativeAds.size() <= 0) {
return;
}
int offset = 3;
for (UnifiedNativeAd ad : mNativeAds) {
if (index<itemDatas.size()) {
itemDatas.add(index, ad);
}
index = index + offset;
}
//index = itemDatas.size() - 3;
if (runshowMovieOnce){
showMoviesList();
}
runshowMovieOnce = false;
}
private void showMoviesList() {
movieLists.setLayoutManager(layoutManager);
movieListAdapter = new MovieListAdapter(movieLists,MovieLists.this,itemDatas);
movieLists.setHasFixedSize(true);
movieLists.setAdapter(movieListAdapter);
movieListAdapter.setLoadMore(new ILoadMore() {
#Override
public void onLoadMore() {
itemDatas.add(null);
movieLists.post(new Runnable() {
public void run() {
movieListAdapter.notifyItemInserted(itemDatas.size() - 1);
Log.d("load position insert", ""+(itemDatas.size()-1));
}
});
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
itemDatas.remove(itemDatas.size()-1);
movieListAdapter.notifyItemRemoved(itemDatas.size() - 1);
Log.d("load position rem", ""+(itemDatas.size()-1));
int previousListSize = itemDatas.size();
Log.d("previousListSize",""+previousListSize);
loadMovies();
int newListSize = itemDatas.size();
Log.d("newListSize",""+newListSize);
movieListAdapter.notifyDataSetChanged();
movieListAdapter.setLoaded();
}
},5000);
}
});
}
private void loadMovies() {
FirebaseFirestore mFirestore = FirebaseFirestore.getInstance();
CollectionReference mPostsCollection = mFirestore.collection(catName).document(docId).collection("listItem");
//Defining Query cursors
if (lastVisible == null){
mQuery = mPostsCollection.orderBy("pr", Query.Direction.ASCENDING)
.limit(6);
}else {
mQuery = mPostsCollection.orderBy("pr", Query.Direction.ASCENDING)
.startAfter(lastVisible)
.limit(6);
}
mQuery.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
for (QueryDocumentSnapshot documentSnapshot : documentSnapshots){
ItemData itemData = documentSnapshot.toObject(ItemData.class);
itemDatas.add(itemData);
Log.d("movie name",itemData.getVn());
}
// Get the last visible document
if (documentSnapshots.size() > 0)
lastVisible = documentSnapshots.getDocuments()
.get(documentSnapshots.size() -1);
onFirebaseLoadSuccess(itemDatas);
}
});
}
}
MovieListAdapter.java
public class MovieListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<Object> itemDatas;
Context context;
String urlStart = "https://i1.ytimg.com/vi/";
String urlLast = "/mqdefault.jpg";
private static final int MOVIE_ITEM_VIEW_TYPE = 0;
private static final int UNIFIED_NATIVE_AD_VIEW_TYPE = 1;
private final int VIEW_TYPE_LOADING = 2;
boolean isLoading;
ILoadMore loadMore;
int visibleThreshold ;
int lastVisibleItem,totalItemCount;
View itemV;
public MovieListAdapter(RecyclerView recyclerView, Context context, #NonNull List<Object> itemData) {
this.itemDatas = itemData;
this.context = context;
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager();
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
totalItemCount = linearLayoutManager.getItemCount();
visibleThreshold = linearLayoutManager.getChildCount();
lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
if (!isLoading && (visibleThreshold + lastVisibleItem) >= totalItemCount){
if (loadMore != null){
loadMore.onLoadMore();
isLoading = true;
}
}
}
});
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
switch (viewType) {
case UNIFIED_NATIVE_AD_VIEW_TYPE:
View unifiedNativeLayoutView = LayoutInflater.from(
parent.getContext()).inflate(R.layout.ad_unified,
parent, false);
return new UnifiedNativeAdViewHolder(unifiedNativeLayoutView);
case VIEW_TYPE_LOADING:
View viewL = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_loading, parent, false);
return new LoadingViewHolder(viewL);
case MOVIE_ITEM_VIEW_TYPE:
// Fall through.
default: {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_movie_list, parent, false);
return new MovieListAdapter.MovieListsMyViewHolder(view);
}
}
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder myViewHolder, int i) {
//int viewType = getItemViewType(i);
if (myViewHolder instanceof MovieListAdapter.MovieListsMyViewHolder){
MovieListsMyViewHolder movieListsMyViewHolder = (MovieListsMyViewHolder) myViewHolder;
ItemData model = (ItemData)itemDatas.get(i);
movieListsMyViewHolder.txt_item_title.setText(model.getVn());
String urlImage = urlStart + model.getVi() + urlLast;
Log.e("urlImage", urlImage);
Glide.with(context)
.load(urlImage)
.placeholder(R.drawable.placeholder)
.error(R.drawable.placeholder)
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.into(movieListsMyViewHolder.img_title);
movieListsMyViewHolder.setiItemClickListener(new IItemClickListener() {
#Override
public void onItemclickListener(View view, int position) {
ItemData itemData = new ItemData(model.getVi(),model.getVn(),model.getVd(),model.getPr(),model.getTc(),model.getVvc());
Log.d("Tmdb ID",String.valueOf(model.getTc()));
MovieDetails.startActivity(context, ((MovieListsMyViewHolder) myViewHolder).transformationLayout, itemData);
}
});
}else if (myViewHolder instanceof UnifiedNativeAdViewHolder){
Object recyclerViewItems = itemDatas.get(i);
UnifiedNativeAd nativeAd = (UnifiedNativeAd) recyclerViewItems;
populateNativeAdView(nativeAd, ((UnifiedNativeAdViewHolder) myViewHolder).getAdView());
}else if (myViewHolder instanceof LoadingViewHolder){
LoadingViewHolder loadingViewHolder = (LoadingViewHolder)myViewHolder;
loadingViewHolder.progressBar.setIndeterminate(true);
}
}
#Override
public int getItemCount() {
return itemDatas.size();
}
public class MovieListsMyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
AppCompatTextView txt_item_title;
ImageView img_title;
IItemClickListener iItemClickListener;
CardView itemCardView;
TransformationLayout transformationLayout;
public void setiItemClickListener(IItemClickListener iItemClickListener) {
this.iItemClickListener = iItemClickListener;
}
public MovieListsMyViewHolder(#NonNull View itemView) {
super(itemView);
itemV = itemView;
txt_item_title = (AppCompatTextView) itemView.findViewById(R.id.item_movie_Title);
img_title = (ImageView)itemView.findViewById(R.id.item_movie_Image);
//itemCardView = (CardView)itemView.findViewById(R.id.item_cardview);
//itemCardView.setOnClickListener(this);
transformationLayout = (TransformationLayout)itemView.findViewById(R.id.transformationMovieLayout);
transformationLayout.setOnClickListener(this);
}
#Override
public void onClick(View view){
iItemClickListener.onItemclickListener(view,getAdapterPosition());
}
}
private void populateNativeAdView(UnifiedNativeAd nativeAd,
UnifiedNativeAdView adView) {
// Some assets are guaranteed to be in every UnifiedNativeAd.
((TextView) adView.getHeadlineView()).setText(nativeAd.getHeadline());
((TextView) adView.getBodyView()).setText(nativeAd.getBody());
((Button) adView.getCallToActionView()).setText(nativeAd.getCallToAction());
// These assets aren't guaranteed to be in every UnifiedNativeAd, so it's important to
// check before trying to display them.
NativeAd.Image icon = nativeAd.getIcon();
if (icon == null) {
adView.getIconView().setVisibility(View.INVISIBLE);
} else {
((ImageView) adView.getIconView()).setImageDrawable(icon.getDrawable());
adView.getIconView().setVisibility(View.VISIBLE);
}
if (nativeAd.getPrice() == null) {
adView.getPriceView().setVisibility(View.INVISIBLE);
} else {
adView.getPriceView().setVisibility(View.VISIBLE);
((TextView) adView.getPriceView()).setText(nativeAd.getPrice());
}
if (nativeAd.getStore() == null) {
adView.getStoreView().setVisibility(View.INVISIBLE);
} else {
adView.getStoreView().setVisibility(View.VISIBLE);
((TextView) adView.getStoreView()).setText(nativeAd.getStore());
}
if (nativeAd.getStarRating() == null) {
adView.getStarRatingView().setVisibility(View.INVISIBLE);
} else {
((RatingBar) adView.getStarRatingView())
.setRating(nativeAd.getStarRating().floatValue());
adView.getStarRatingView().setVisibility(View.VISIBLE);
}
if (nativeAd.getAdvertiser() == null) {
adView.getAdvertiserView().setVisibility(View.INVISIBLE);
} else {
((TextView) adView.getAdvertiserView()).setText(nativeAd.getAdvertiser());
adView.getAdvertiserView().setVisibility(View.VISIBLE);
}
// Assign native ad object to the native view.
adView.setNativeAd(nativeAd);
}
#Override
public int getItemViewType(int position) {
//DocumentSnapshot itemData = getItem(position);
Object recyclerViewItem = itemDatas.get(position);
if (recyclerViewItem instanceof UnifiedNativeAd) {
return UNIFIED_NATIVE_AD_VIEW_TYPE;
}else if (itemDatas.get(position) == null){
return VIEW_TYPE_LOADING;
}else {
return MOVIE_ITEM_VIEW_TYPE;
}
}
public void setLoadMore(ILoadMore loadMore){
this.loadMore = loadMore;
}
private class LoadingViewHolder extends RecyclerView.ViewHolder {
ProgressBar progressBar;
public LoadingViewHolder(#NonNull View itemView) {
super(itemView);
progressBar = itemView.findViewById(R.id.progressBar);
}
}
public void setLoaded(){
isLoading = false;
}
}
The short term answer: There's a lot going on in your code. If you just want to remove the loadingProgress, add a void viewsLoaded() method inside your LoadingViewHolder with progressbar.setVisibility(View.GONE), and inside onBindViewHolder method, on the if(myViewHolder instanceof LoadingViewHolder), call the viewsLoaded() method if viewHolder is not positioned as the last one on the Adapter.
The long term answer: If you want to enhance the performance, read about how to use DiffUtil.ItemCallback<> instead of using notifydatasetchanged(), which is used for simple small datasets. You have a list of objectData on your activity and another one on your adapter, read about using LiveData, databinding and MVVM pattern.
i suggest first of all you should get The linearLayoutManager inside The Scroll View Listener.
another suggestion is to not call movieListAdapter.notifyItemInserted(itemDatas.size() - 1); Async inside the Runnable because after you add a null into list The Adapter should be notify:
itemsData.add(null);
movieListAdapter.notifyItemInserted(itemsData.size() - 1);
another is in the line: itemDatas.remove(itemDatas.size()-1)
after you remove the item from list, The List size will decrease by one.
so this would be correct:
movieListAdapter.notifyItemRemoved(itemDatas.size());
i Hope these would help.
In my project, there is need of searching data from server using keyword. After search, i am displaying results using RecyclerView . While searching fast, the data in RecyclerView is duplicating. If searching slowly, it's working fine. Any suggestions are appreciated. Thank you.
The below code for making server call:
private void callSearchUserApi(final String searchText, int currentPage, boolean clearData) {
isApiCallInProcess = true;
String URL = "userinfo/api/v1/user-search/" + "?page=" + currentPage;
if (!Connectivity.isConnected(activity)) {
Common.snackBarNoConnection(activity, activity.getString(R.string.no_conection));
//setOnProgressbarVisibility(View.GONE);
return;
}
if (clearData) {
globalSearchUsersModelList.clear();
//BS globalSearchUserResultsAdapter.notifyDataSetChanged();
}
ApiInterface apiCall = ApiClient.getApiService(activity);
final Call<SearchUsersModel> globalUserSearchApiCall = apiCall.searchUser(
URL,
searchText);
globalUserSearchApiCall.enqueue(new Callback<SearchUsersModel>() {
#Override
public void onResponse(Call<SearchUsersModel> call, Response<SearchUsersModel> response) {
if (response.isSuccessful() && response.body().getStatus().equalsIgnoreCase(Common.SUCCESS_RESPONSE)) {
//BS globalSearchUsersModelList.addAll(response.body().getData().getData());
for (int i = 0; i < response.body().getData().getData().size(); i++) {
SearchUsersModel.DataBeanX.DataBean dataBean = new SearchUsersModel.DataBeanX.DataBean();
dataBean.setDesignation(response.body().getData().getData().get(i).getDesignation());
dataBean.setFull_name(response.body().getData().getData().get(i).getFull_name());
dataBean.setGender(response.body().getData().getData().get(i).getGender());
dataBean.setId(response.body().getData().getData().get(i).getId());
dataBean.setPlace(response.body().getData().getData().get(i).getPlace());
dataBean.setProfile_pic(response.body().getData().getData().get(i).getProfile_pic());
globalSearchUsersModelList.add(dataBean);
/*BS if (!globalSearchUsersModelList.contains(response.body().getData().getData().get(i)))
globalSearchUsersModelList.add(response.body().getData().getData().get(i));*/
}
CURRENT_PAGE = response.body().getData().getPage();
isLoading = false;
if (response.body().getData().isNext() == false)
isLastPage = true;
else
isLastPage = false;
if (globalSearchUsersModelList.size() == 0) {
rv_GlobalsearchList.setVisibility(View.GONE);
rl_placeholderGSPeople.setVisibility(View.VISIBLE);
tv_placeholderGSPeople.setText(activity.getString(R.string.no_search_found) + " " + searchText);
} else {
rv_GlobalsearchList.setVisibility(View.VISIBLE);
rl_placeholderGSPeople.setVisibility(View.GONE);
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
globalSearchUserResultsAdapter.notifyDataSetChanged();
}
});
}
if (searchTextsList.size() > 0) {
String sText = searchTextsList.get(0);
searchTextsList.remove(0);
callSearchUserApi(sText, FIRST_PAGE, true);
} else
isApiCallInProcess = false;
}
#Override
public void onFailure(Call<SearchUsersModel> call, Throwable t) {
isApiCallInProcess = false;
}
});
}
This is my Adapter.
public class GlobalSearchUserResultsAdapter extends RecyclerView.Adapter<GlobalSearchUserResultsAdapter.SearchViewHolder> {
private Context context;
private List<SearchUsersModel.DataBeanX.DataBean> searchUserList;
public GlobalSearchUserResultsAdapter(Context context, List<SearchUsersModel.DataBeanX.DataBean> searchUserList){
this.context = context;
this.searchUserList = searchUserList;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Log.v("Search", "Adapter Activity : "+context);
View view = LayoutInflater.from(context).inflate(R.layout.global_search_row, parent, false);
return new GlobalSearchUserResultsAdapter.SearchViewHolder(view);
}
#Override
public void onBindViewHolder(GlobalSearchUserResultsAdapter.SearchViewHolder holder, int position) {
if ( searchUserList.get(position).getGender().equals("M")) {
holder.iv_userImage.setBackgroundResource(R.drawable.white_border_with_circle_appblue);
Common.setGlideImage((GlobalSearchActivity)context,
holder.iv_userImage,
/*searchUsersModel*/searchUserList.get(position).getProfile_pic(),
R.drawable.male,
true);
} else if (searchUserList.get(position).getGender().equals("F")) {
holder.iv_userImage.setBackgroundResource(R.drawable.white_border_with_circle_pink);
Common.setGlideImage((GlobalSearchActivity)context,
holder.iv_userImage,
searchUserList.get(position).getProfile_pic(),
R.drawable.female,
true);
} else {
Common.setGlideImage((GlobalSearchActivity)context,
holder.iv_userImage,
searchUserList.get(position).getProfile_pic(),
R.drawable.deafult_profilepic,
true);
}
holder.tv_userName.setText(searchUserList.get(position).getFull_name());
holder.tv_userName.setTypeface(Common
.getFontTypeface(context, GlobalConstants.FONT_AVENIR_MEDIUM));
holder.tv_place.setText(searchUserList.get(position).getPlace());
holder.tv_place.setTypeface(Common
.getFontTypeface(context, GlobalConstants.FONT_AVENIR_MEDIUM));
holder.designation.setText(searchUserList.get(position).getDesignation());
}
#Override
public int getItemCount() {
return searchUserList.size();
}
public class SearchViewHolder extends RecyclerView.ViewHolder{
private ImageView iv_userImage;
private TextView tv_userName;
private TextView tv_place;
private TextView designation;
public SearchViewHolder(View itemView) {
super(itemView);
this.iv_userImage = (ImageView) itemView.findViewById(R.id.imageSearch);
this.tv_userName = (TextView) itemView.findViewById(R.id.nameSearch);
tv_userName.setTypeface(Common.getFontTypeface(context,
GlobalConstants.FONT_AVENIR_MEDIUM));
this.designation = (TextView) itemView.findViewById(R.id.designation);
designation.setTypeface(Common.getFontTypeface(context,
GlobalConstants.FONT_AVENIR_MEDIUM));
this.tv_place = (TextView) itemView.findViewById(R.id.placeSearch);
tv_place.setTypeface(Common.getFontTypeface(context,
GlobalConstants.FONT_AVENIR_LIGHT));
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
context.startActivity(new Intent(context, ProfileActivity.class)//ThirdParty
.putExtra(GlobalConstants.KEY_THIRD_PARTY_ID, searchUserList.get(getAdapterPosition()).getId()));
}
});
}
}
}
You just had to clear the globalSearchUsersModelList list just before for loop, because API call is asynchronous.
globalSearchUsersModelList.clear();// Important one
for (int i = 0; i < response.body().getData().getData().size(); i++) {
// do your stuff
}
I think the issue come from your getItemId implementation. The way you implement it, the recycler view will identify an item according to its position in the list.
If you change this and use unique identification for instance searchUserList.get(position).id (if your User object has an unique ID) the problem should be fixed
You can also add in your activity adapter.setHasStableIds(true)
I am using parse-server to develop the app which uses RecyclerView to display image items.
but the problem is that the items displayed in the view changed every time I scrolled up and down.
I want to know what is the problem on my code.
if you see below images, you can find the items are changing their position.
I tried to make holder image become null before call the holder again. but it's not working. I guess that the item's position number is changed when I call the item again.but I can't find the cause of the situation
enter image description here
enter image description here
RecyclerParseAdapter.java
public class MyTimelineAdapter extends RecyclerParseAdapter {
private interface OnQueryLoadListener<ParseObject> {
public void onLoading();
public void onLoaded(List<ParseObject> objects, Exception e);
}
private static ParseQueryAdapter.QueryFactory<ParseObject> queryFactory;
private static List<OnQueryLoadListener<ParseObject>> onQueryLoadListeners;
private static List<List<ParseObject>> objectPages;
private static ArrayList<ParseObject> items;
private static int currentPage;
private static RequestManager requestManager;
public MyTimelineAdapter(Context context, RequestManager requestManager) {
super(context);
this.requestManager = requestManager;
this.onQueryLoadListeners = new ArrayList<>();
this.currentPage = 0;
this.objectPages = new ArrayList<>();
this.items = new ArrayList<>();
this.queryFactory = new ParseQueryAdapter.QueryFactory<ParseObject>() {
#Override
public ParseQuery<ParseObject> create() {
ParseQuery<ParseObject> query = ParseQuery.getQuery("ImageClassName");
query.setCachePolicy(ParseQuery.CachePolicy.CACHE_THEN_NETWORK);
query.whereEqualTo("status", true);
query.orderByDescending("createdAt");
return query;
}
};
loadObjects(currentPage);
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View timelineView;
timelineView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_timeline_item2, parent, false);
TimelineItemViewHolder timelineItemViewHolder = new TimelineItemViewHolder(timelineView);
return timelineItemViewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final ParseObject timelineOb = getItem(position);
FunctionPost functionPost = new FunctionPost(context);
functionPost.TimelineArtistPostAdapterBuilder( timelineOb, holder, requestManager);
//기능 추가
}
#Override
public int getItemCount() {
return items.size();
}
#Override
public ParseObject getItem(int position) {
return items.get(position);
}
#Override
public void loadObjects(final int page) {
final ParseQuery<ParseObject> query = this.queryFactory.create();
if (this.objectsPerPage > 0 && this.paginationEnabled) {
this.setPageOnQuery(page, query);
}
this.notifyOnLoadingListeners();
if (page >= objectPages.size()) {
objectPages.add(page, new ArrayList<ParseObject>());
}
query.findInBackground(new FindCallback<ParseObject>() {
#Override
public void done(List<ParseObject> foundObjects, ParseException e) {
if ((e != null) && ((e.getCode() == ParseException.CONNECTION_FAILED) || (e.getCode() != ParseException.CACHE_MISS))) {
hasNextPage = true;
} else if (foundObjects != null) {
// Only advance the page, this prevents second call back from CACHE_THEN_NETWORK to
// reset the page.
if (page >= currentPage) {
currentPage = page;
// since we set limit == objectsPerPage + 1
hasNextPage = (foundObjects.size() > objectsPerPage);
}
if (paginationEnabled && foundObjects.size() > objectsPerPage) {
// Remove the last object, fetched in order to tell us whether there was a "next page"
foundObjects.remove(objectsPerPage);
}
List<ParseObject> currentPage = objectPages.get(page);
currentPage.clear();
currentPage.addAll(foundObjects);
syncObjectsWithPages(items, objectPages);
// executes on the UI thread
notifyDataSetChanged();
}
notifyOnLoadedListeners(foundObjects, e);
}
});
}
public void loadNextPage() {
if (items.size() == 0) {
loadObjects(0);
} else {
loadObjects(currentPage + 1);
}
}
public void syncObjectsWithPages(ArrayList<ParseObject> items, List<List<ParseObject>> objectPages) {
items.clear();
for (List<ParseObject> pageOfObjects : objectPages) {
items.addAll(pageOfObjects);
}
}
protected void setPageOnQuery(int page, ParseQuery<ParseObject> query) {
query.setLimit(this.objectsPerPage + 1);
query.setSkip(page * this.objectsPerPage);
}
public void addOnQueryLoadListener(OnQueryLoadListener<ParseObject> listener) {
this.onQueryLoadListeners.add(listener);
}
public void removeOnQueryLoadListener(OnQueryLoadListener<ParseObject> listener) {
this.onQueryLoadListeners.remove(listener);
}
public void notifyOnLoadingListeners() {
for (OnQueryLoadListener<ParseObject> listener : this.onQueryLoadListeners) {
listener.onLoading();
}
}
public void notifyOnLoadedListeners(List<ParseObject> objects, Exception e) {
for (OnQueryLoadListener<ParseObject> listener : this.onQueryLoadListeners) {
listener.onLoaded(objects, e);
}
}
}
I did find the problem
I add overide method in the adapter then It works find.
#Override
public int getItemViewType(int position) {
return position;
}
I am not sure why it happens now. any one help me to know the cause of problem?
I has a similar problem the other day see this post. onBindViewHolder needs to know how to display the row when it's called. I returned two different view types depending on the need in getItemViewType, inflated the view type conditionally in onCreateViewHolder, then I was able to set the data on the ViewHolder as needed.
My adapter list is refreshing on broadcast receiver .
Everything is working fine if adapter list size is greater than 1 ,
means if my recyclerview has already one row shwoing then list refreshing just fine .
But if list size goes from 0 to 1 then my adapter notify dataset
Changed stop working . No data shows on recyclerview. I don't know why it is not working .
Recyclerview Class:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
v = inflater.inflate(R.layout.job_recyclerview, container, false);
getActivity());
initialise(v);
init();
showNoTaskMessage();
new loadListTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
mMyBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Here you can refresh your listview or other UI
SlidingTab.slidingTab.getTabAt(0).setText("New (" + SingleTon.getInstance().getNewjob() + ")");
SlidingTab.slidingTab.getTabAt(1).setText("In Progress (" + SingleTon.getInstance().getInprogressjob() + ")");;
SlidingTab.slidingTab.getTabAt(2).setText("Completed (" + SingleTon.getInstance().getCompletedjob() + ")");
}
};
try {
IntentFilter filter = new IntentFilter("newJob");
LocalBroadcastManager.getInstance(context).registerReceiver(mMyBroadcastReceiver,
filter);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return v;
}
Adapter class :
public JobAdapter(ArrayList<Info> myDataset, Context context) {
this.mDataset = myDataset;
this.mAct = context;
}
public void addApplications(ArrayList<Info> candidates) {
if (this.filterList == null) {
filterList = new ArrayList<>();
}
this.mDataset.clear();
this.mDataset.addAll(candidates);
this.filterList.addAll(mDataset);
this.notifyItemRangeInserted(0, candidates.size() - 1);
}
public void clearApplications() {
int size = this.mDataset.size();
if (size > 0) {
for (int i = 0; i < size; i++) {
mDataset.remove(0);
filterList.remove(0);
}
this.notifyItemRangeRemoved(0, size);
}
}
#Override
public int getItemViewType(int position) {
return VIEW_NORMAL;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.single_job_card, parent, false);
ViewHolder fh = new ViewHolder(v);
return fh;
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
// holder.jobPhone.setText(mDataset.get(position).mobileNo);
holder.jobNumber.setText(mDataset.get(position).jobNumber);
holder.jobTime.setText(mDataset.get(position).time);
holder.jobAddress.setText(mDataset.get(position).address);
// holder.jobInstructionText.setText(mDataset.get(position).spclInstruction);
if (mDataset.get(position).jobStatus != null && mDataset.get(position).jobStatus.equalsIgnoreCase("Completed")) {
holder.endsat.setText("Submitted at");
holder.jobTime.setText(mDataset.get(position).completedOnString);
holder.jobTimeLeft.setVisibility(View.INVISIBLE);
holder.timerImage.setVisibility(View.INVISIBLE);
} else {
if (mDataset.get(position).status.equalsIgnoreCase("Active")) {
holder.jobTimeLeft.setText(mDataset.get(position).appointmentTime);
} else {
holder.jobTimeLeft.setText("-" + mDataset.get(position).appointmentTime);
}
}
holder.jobLayout1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
SingleTon.getInstance().setWorkDescHolder(mDataset.get(position).descHolder);
FragmentManager fragmentManager = ((FragmentActivity) mAct).getSupportFragmentManager();
FragmentTransaction ft = ((FragmentActivity) mAct).getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.glide_fragment_horizontal_in, R.anim.glide_fragment_horizontal_out);
ft.replace(R.id.content_frame1, new DetailsFragment(), "persondetails");
ft.addToBackStack("persondetails");
// Start the animated transition.
ft.commit();
}
});
}
#Override
public int getItemCount() {
return mDataset.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView jobNumber, jobTimeLeft, jobStatus, jobAddress, jobEmail, jobPhone, timeTimer, jobInstructionText, jobTime, endsat;
private ImageView timerImage;
private FrameLayout frameLayout;
private CardView cardView;
private LayoutRipple jobLayout1;
public ViewHolder(View v) {
super(v);
this.jobNumber = (TextView) v.findViewById(R.id.job_number);
this.jobTime = (TextView) v.findViewById(R.id.job_time);
this.jobTimeLeft = (TextView) v.findViewById(R.id.job_timertext);
this.timerImage = (ImageView) v.findViewById(R.id.timerimage);
this.cardView = (CardView) v.findViewById(R.id.card_view);
// this.jobStatus = (TextView) v.findViewById(R.id.job_status);
this.jobAddress = (TextView) v.findViewById(R.id.job_addresstext);
// this.jobInstructionText = (TextView) v.findViewById(R.id.instruction_text);
// this.jobLayout = (LayoutRipple)v.findViewById(R.id.job_cardLayout);
this.jobLayout1 = (LayoutRipple) v.findViewById(R.id.cardLayout1);
this.endsat = (AppCompatTextView) v.findViewById(R.id.endsat);
this.jobNumber.setTypeface(Utils.RegularTypeface(mAct));
this.jobAddress.setTypeface(Utils.RegularTypeface(mAct));
this.jobTimeLeft.setTypeface(Utils.RegularTypeface(mAct));
this.jobTime.setTypeface(Utils.RegularTypeface(mAct));
}
}
}
Please help me finding the bug or some other approach . Thanks
Call the data loading task inside the onReceive() of BroadcastReceiver
mMyBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Here you can refresh your listview or other UI
new loadListTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
SlidingTab.slidingTab.getTabAt(0).setText("New (" + SingleTon.getInstance().getNewjob() + ")");
SlidingTab.slidingTab.getTabAt(1).setText("In Progress (" + SingleTon.getInstance().getInprogressjob() + ")");;
SlidingTab.slidingTab.getTabAt(2).setText("Completed (" + SingleTon.getInstance().getCompletedjob() + ")");
}
};
And also do following changes in your Adapter class.
public void addApplications(ArrayList<Info> candidates) {
if (this.filterList == null) {
filterList = new ArrayList<>();
}
this.mDataset.clear();
this.mDataset.addAll(candidates);
this.filterList.addAll(mDataset);
this.notifyItemRangeInserted(0, candidates.size());
}
public void clearApplications() {
int size = this.mDataset.size();
if (size > 0) {
for (int i = 0; i < size; i++) {
mDataset.remove(i);
filterList.remove(i);
}
this.notifyItemRangeRemoved(0, size);
}
}
Hope that works!
Change this:
mMyBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Here you can refresh your listview or other UI
recycleAdapter.addItems(SingleTon.getInstance()
.getInfoArrayList());
// I'm assuming your "SingleTon.getInstance().getInfoArrayList()" is the received data.
}
On your Adapter
public void addItems(List<Info> itemsList) {
// This check could be avoided if you declared your mDataset final
if(mDataset == null) {
mDataset = new ArrayList<>();
}
int prevSize = mDataset.size();
mDataset.addAll(itemsList);
notifyItemRangeInserted(prevSize, itemsList.size());
}
You shouldn't call notifyDataSetChanged() after this.notifyItemRangeInserted(0, candidates.size() - 1); try something like this:
put this method to your adapter class
public void setData(ArrayList<Info> infos) {
this.mDataset = infos;
notifyDataSetChanged();
}
and call it like this:
ArrayList<Info> list = SingleTon.getInstance().getInfoArrayList().isEmpty();
if (list != null && !list.isEmpty()) {
recycleAdapter.setData(list);
}
correct this method in your adapter
#Override
public int getItemCount() {
return mDataset != null && !mDataset.isEmpty() ? mDataset.size() : 0;
}
Base on your code, we need a variable list to store your info data.
//Declare the info list
private ArrayList<Info> mInfos = new ArrayList<Info>()
In your onCreateView(), set mInfos to your recycleAdapter
recycleAdapter = new JobAdapter(mInfos, getActivity());
recyclerView.setAdapter(recycleAdapter);
So, every time you want to set new info list. Just assign it to mInfos
and make sure, you clear your previous list data to avoid duplicate data.
mInfos.clear()
mInfos.addAll(SingleTon.getInstance().getInfoArrayList());
//refresh data
recycleAdapter.notifyDataSetChanged();
I am not sure where you are using clearApplications() in JobAdapter.class.
But, it seems to be wrong. In the for-loop, you are trying to remove the value at index 0 every time rather than index 'i'. Hope this helps.
when you are using custom adapter then notifyDatasetChange() not called from outside that adapter so make addItem function in adapter and add new List in Adapter list and call notifyDataSetChanged
public void addItem(List<Model> list) {
if (lit != null) {
clear();
adapterList.addAll(list);
}
notifyDataSetChanged();
}
Do Change In your recyclerview class.
//Change condition ">1" to "!=null"
if (SingleTon.getInstance().getInfoArrayList().size() != null) {
recycleAdapter.addApplications(SingleTon.getInstance().getInfoArrayList());
recycleAdapter.notifyDataSetChanged();
and then do change in your adapter.
public void addApplications(ArrayList<Info> candidates) {
if (this.filterList == null) {
filterList = new ArrayList<>();
}
this.mDataset.clear();
this.mDataset.addAll(candidates);
this.filterList.addAll(mDataset);
this.notifyItemRangeInserted(0, mDataset.size()); //notify to mDataset
}
hope this will work!