i am chinese coder.i am not good at english,so just try hard to describe the question. thank you very much for your answer.
when i first into activity with RecyclerView and scroll,it is not smooth especially showing next item next ,but when i have scrolled the Recyclerview,it is work,and smooth . some coder say we can't do much work on onCreateViewHolder() and onBindViewHolder() , but i can not get it.
here is my code:
fragment
public class HomeFragment extends RefreshableFragment implements IHomeView {
#Bind(R.id.refresh_view)
PullToRefreshRecyclerView refresh_view;
private RecyclerView refreshableView;
private HomePresenter homePresenter;
#Override
public View initSuccessView() {
View view = View.inflate(getContext(), R.layout.fragment_refreshable_success_recyclerview, null);
ButterKnife.bind(this, view);
initRefreshLayout();
initRecyclerView();
return view;
}
/**
* 初始化可刷新控件
*/
private void initRefreshLayout() {
refresh_view.setMode(PullToRefreshBase.Mode.BOTH);
refresh_view.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener2<RecyclerView>() {
#Override
public void onPullDownToRefresh(PullToRefreshBase<RecyclerView> refreshView) {
homePresenter.refresh();
}
#Override
public void onPullUpToRefresh(PullToRefreshBase<RecyclerView> refreshView) {
homePresenter.loadMore();
}
});
}
/**
* 初始化RecyclerView控件
*/
private void initRecyclerView() {
refreshableView = refresh_view.getRefreshableView();
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext(),LinearLayoutManager.VERTICAL,false);
SpaceItemDecoration decor = new SpaceItemDecoration(2);
refreshableView.addItemDecoration(decor);
refreshableView.setLayoutManager(layoutManager);
refreshableView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int childCount = recyclerView.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = recyclerView.getChildAt(i);
RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(view);
switch (holder.getItemViewType()){
case HomeAdapter.TYPE_TOPIC:
HomeTopicViewHolder topicViewHolder = (HomeTopicViewHolder) holder;
topicViewHolder.setParentHeight(recyclerView.getHeight());
topicViewHolder.setOffset();
}
}
}
});
}
#Override
protected void requestNetwork() {
homePresenter = new HomePresenter(getContext(),this,null);
homePresenter.init();
}
#Override
public RecyclerView getRecyclerView() {
return refreshableView;
}
#Override
public RefreshView getRefreshView() {
return getParentRefreshView();
}
#Override
public void onRefreshFinish() {
if(refresh_view.isRefreshing()){
refresh_view.onRefreshComplete();
}
}
#Override
public void onLordMoreFinish() {
}
}
presenter
public class HomePresenter extends BasePresenter<IHomeView> implements IHomePresenter, StringLoaderCallback {
private Gson gson;
private HomeAdapter homeAdapter;
private final Pagination pagination;
public HomePresenter(Context context,IHomeView homeView,Bundle bundle) {
super(context,homeView,bundle);
gson = new Gson();
homeAdapter = new HomeAdapter();
getPresenterView().getRecyclerView().setAdapter(homeAdapter);
pagination = new Pagination();
pagination.setLimit(10);
pagination.setOffset(0);
}
#Override
public void init() {
String bannerUrl = ServerAPI.Home.buildHomeBannerUrl();
NetworkStringLoader.getInstance(context).sendGetStringRequest(bannerUrl, null, bannerUrl, this);
String topicUrl = ServerAPI.Home.buildHomeTopicUrl();
NetworkStringLoader.getInstance(context).sendGetStringRequest(topicUrl, null, topicUrl, this);
String routeUrl = ServerAPI.Home.buildHomeRouteUrl();
NetworkStringLoader.getInstance(context).sendGetStringRequest(routeUrl, pagination.getMap(), REFRESH, this);
}
#Override
public void loadMore() {
String routeUrl = ServerAPI.Home.buildHomeRouteUrl();
pagination.setOffset(pagination.getTotal());
pagination.setLimit(10);
NetworkStringLoader.getInstance(context).sendGetStringRequest(routeUrl, pagination.getMap(), LOADMORE, this);
}
#Override
public void refresh() {
pagination.setOffset(0);
pagination.setLimit(10);
pagination.clearTotal();
init();
}
#Override
public void onLoadFinished(String url,Map<String,String> params, String data,Object tag) {
if(url.equals(ServerAPI.Home.buildHomeBannerUrl())){
Log.i(url,data);
HomeBanner.HomeBannerLists homeBannerLists = gson.fromJson(data,HomeBanner.HomeBannerLists.class);
homeAdapter.setBanners(homeBannerLists.list);
}
if(url.equals(ServerAPI.Home.buildHomeTopicUrl())){
Log.i(url,data);
HomeTopic.HomeTopicLists homeTopicLists = gson.fromJson(data,HomeTopic.HomeTopicLists.class);
homeAdapter.setTopics(homeTopicLists.list);
}
if(url.equals(ServerAPI.Home.buildHomeRouteUrl())){
Log.i(url,data);
int mode = (int) tag;
HomeRoute.HomeRouteLists homeRouteLists = gson.fromJson(data, HomeRoute.HomeRouteLists.class);
if (mode == REFRESH){
homeAdapter.setRoutes(homeRouteLists.list);
} else if(mode == LOADMORE){
homeAdapter.appendRoutes(homeRouteLists.list);
}
pagination.appendTotal(homeRouteLists.list.size());
}
getPresenterView().getRefreshView().setCurrentState(RefreshView.STATE_SUCCESS);
getPresenterView().onRefreshFinish();
}
#Override
public void onLoadError(VolleyError error) {
Log.i("error",error.getMessage());
}
}
adapter
public class HomeAdapter extends RecyclerView.Adapter {
public static final int TYPE_BANNER = 0;
public static final int TYPE_TOPIC = 1;
public static final int TYPE_ROUTE = 2;
private List<HomeBanner> banners = new ArrayList<>();
private List<HomeTopic> topics = new ArrayList<>();
private List<HomeRoute> routes = new ArrayList<>();
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType){
case TYPE_BANNER:
View bannerView = HomeBannerViewHolder.getView(parent.getContext());
return new HomeBannerViewHolder(bannerView);
case TYPE_TOPIC:
View topicView = View.inflate(parent.getContext(), R.layout.item_home_topic,null);
// View topicView = HomeTopicViewHolder.getView(parent.getContext());
return new HomeTopicViewHolder(topicView);
case TYPE_ROUTE:
View routeView = View.inflate(parent.getContext(), R.layout.item_home_route,null);
// View routeView = HomeRouteViewHolder.getView(parent.getContext());
return new HomeRouteViewHolder(routeView);
default:
// View defaultView = new View(parent.getContext());
View view = EmptyViewHolder.getView(parent.getContext());
return new EmptyViewHolder(view);
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(position == 0){
onBindBannerViewHolder(holder);
}else if(position>0 && position<topics.size()+1){
onBindTopicViewHolder(holder,position-1);
}else {
onBindRouteViewHolder(holder,position-topics.size()-1);
}
}
private void onBindRouteViewHolder(RecyclerView.ViewHolder holder, int position) {
HomeRouteViewHolder routeViewHolder = (HomeRouteViewHolder) holder;
routeViewHolder.setData(routes.get(position));
}
private void onBindTopicViewHolder(RecyclerView.ViewHolder holder,int position) {
HomeTopicViewHolder topicViewHolder = (HomeTopicViewHolder) holder;
topicViewHolder.setData(topics.get(position));
}
private void onBindBannerViewHolder(RecyclerView.ViewHolder holder) {
HomeBannerViewHolder bannerViewHolder = (HomeBannerViewHolder) holder;
bannerViewHolder.setData(banners);
}
#Override
public int getItemCount() {
return 1 + topics.size()+routes.size();
}
#Override
public int getItemViewType(int position) {
if(position == 0){
return TYPE_BANNER;
}else if(position<topics.size()+1){
return TYPE_TOPIC;
}else {
return TYPE_ROUTE;
}
}
public void setBanners(List<HomeBanner> list){
banners.clear();
banners.addAll(list);
notifyDataSetChanged();
}
public void setTopics(List<HomeTopic> list) {
topics.clear();
topics.addAll(list);
notifyDataSetChanged();
}
public void setRoutes(List<HomeRoute> list) {
routes.clear();
routes.addAll(list);
notifyDataSetChanged();
}
public void appendRoutes(List<HomeRoute> list) {
routes.addAll(routes.size(),list);
notifyItemRangeInserted(getItemCount(),list.size());
}
}
holder
public class HomeTopicViewHolder extends BaseViewHolder<HomeTopic> {
private static SimpleDraweeView content_iv ;
private static TextView home_topic_title ;
private static TextView home_topic_discription ;
private int height;
public static View getView(Context context) {
View topicView = View.inflate(context, R.layout.item_home_topic,null);
return topicView;
}
public HomeTopicViewHolder(View itemView) {
super(itemView);
content_iv = (SimpleDraweeView) itemView.findViewById(R.id.home_topic_iv);
if(content_iv!=null){
content_iv = (SimpleDraweeView) itemView.findViewById(R.id.home_topic_iv);
}
if(home_topic_title!=null){
home_topic_title = (TextView) itemView.findViewById(R.id.home_topic_title);
}
if(home_topic_discription!=null){
home_topic_discription = (TextView) itemView.findViewById(R.id.home_topic_discription);
}
if(height==0){
SharedPreferences sp = SharedPreferenceHelper.getInstance().getSharedPreference(itemView.getContext(), SharedPreferenceHelper.EnumSPName.CONFIG);
int scale = sp.getInt(SharedPreferenceHelper.EnumSPName.windowWidth, 0);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(scale,scale);
content_iv.setLayoutParams(layoutParams);
}
}
#Override
protected void inflateView(HomeTopic data) {
NetworkImageLoader loader = NetworkImageLoader.getInstance();
loader.setUrlToSimpleDraweeView(data.getImage(),content_iv);
home_topic_title.setText(data.getName());
home_topic_discription.setText(String.format(getResources().getString(R.string.guider_route),data.getGuide(), data.getRoute()));
}
public void setOffset() {
int itemViewHeight = itemView.getHeight();
int imageViewHeight = content_iv.getHeight();
int parentOffsetMax = (getParentHeight() + itemViewHeight) / 2;
int offsetMax = imageViewHeight - itemViewHeight;
float y = itemView.getY();
float location = y + itemViewHeight/2;
float v = location - getParentHeight() / 2;
float percent = v/parentOffsetMax;
int margin = (int) ((offsetMax/2)*(percent + 1));
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) content_iv.getLayoutParams();
layoutParams.setMargins(0,-margin,0,0);
content_iv.setLayoutParams(layoutParams);
}
public void setParentHeight(int height) {
this.height = height;
}
public int getParentHeight() {
return height;
}
}
Related
I was trying to implement Admob native ads inside my recyclerView, And i ended with below error & i have also attached my code. i even followed Google Code Lab. Still i ended with multiple error.
How do i add native ad's between <Arraylist>.
FYI: i have minimized code to be readable.
MainActivity:
public class MainActivity extends AppCompatActivity {
public static final int NUMBER_OF_ADS = 5;
AdLoader adLoader;
#BindView(R.id.recyclerView) RecyclerView recyclerView;
ArrayList<VideoModel> arrayListVideos;
ArrayList<UnifiedNativeAd> nativeAds = new ArrayList<>();
private VideoAdapter videoAdapter;
private AdView mAdView;
private ShimmerFrameLayout shimmerFrameLayout;
private GridLayoutManager mLayoutManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int autoCardWith = DisplayUtils.calculateNoOfColumns(getApplicationContext());
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
adMob();
arrayListVideos = new ArrayList<>();
mLayoutManager = new GridLayoutManager(this, autoCardWith);
//mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
// #Override
//public int getSpanSize(int position) { if (position % MainActivity.ITEMS_PER_AD == 0) { return autoCardWith; } return 1; }
//});
recyclerView.setHasFixedSize(true);
//recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setLayoutManager(new GridLayoutManager(this, autoCardWith));
recyclerView.setHasFixedSize(true);
shimmerFrameLayout = findViewById(R.id.shimmer_view);
shimmerFrameLayout.setVisibility(View.VISIBLE);
shimmerFrameLayout.startShimmer();
recyclerView.setVisibility(View.INVISIBLE);
askPermission();
displayAllVideos();
loadNativeAds();
}
private void loadNativeAds() {
AdLoader.Builder builder = new AdLoader.Builder(this,getResources().getString(R.string.native_ad_id));
adLoader = builder.forUnifiedNativeAd(new UnifiedNativeAd.OnUnifiedNativeAdLoadedListener() {
#Override
public void onUnifiedNativeAdLoaded(UnifiedNativeAd unifiedNativeAd) {
nativeAds.add(unifiedNativeAd);
if (!adLoader.isLoading()) {
insertAdsinMenuItem();
}
}
}).withAdListener(new AdListener() {
#Override
public void onAdFailedToLoad(int i) {
super.onAdFailedToLoad(i);
if (!adLoader.isLoading()) {
insertAdsinMenuItem();
}
}
}).build();
adLoader.loadAds(new AdRequest.Builder().build(),NUMBER_OF_ADS);
}
private void insertAdsinMenuItem() {
if (nativeAds.size() < 0 ){
return;
}
int offset = (arrayListVideos.size() / nativeAds.size() + 1 );
int index = 0;
for (UnifiedNativeAd ad:nativeAds){
arrayListVideos.add(index,ad);
index = index + offset;
}
}
private void adMob() {
MobileAds.initialize(this, new OnInitializationCompleteListener() {
#Override
public void onInitializationComplete(InitializationStatus initializationStatus) {
}
});
mAdView = findViewById(R.id.main_banner);
AdRequest adRequest = new AdRequest.Builder().build();
mAdView.loadAd(adRequest);
}
private void askPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)){
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:"+getPackageName()));
startActivityForResult(intent,2084);
}
}
private void displayAllVideos() {
Uri uri;
Cursor cursor;
int column_index_data,thum;
String absolutePathThumb;
uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
String[] projection = {MediaStore.MediaColumns.DATA, MediaStore.Video.Media.BUCKET_DISPLAY_NAME, MediaStore.Video.Media._ID,MediaStore.Video.Thumbnails.DATA};
final String orderBy = MediaStore.Video.Media.DEFAULT_SORT_ORDER;
cursor = getApplicationContext().getContentResolver().query(uri,projection,null,null,orderBy);
column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
thum = cursor.getColumnIndexOrThrow(MediaStore.Video.Thumbnails.DATA);
while (cursor.moveToNext()){
Log.d("VIDEO", cursor.getString(0));
absolutePathThumb = cursor.getString(column_index_data);
Uri thumbUri = Uri.fromFile(new File(absolutePathThumb));
//String cursorThumb = cursor.getString(thum);
String fileName = FilenameUtils.getBaseName(absolutePathThumb);
String extension = FilenameUtils.getExtension(absolutePathThumb);
String duration = getDuration(absolutePathThumb);
VideoModel videoModel = new VideoModel();
videoModel.setDuration(duration);
videoModel.setVideo_uri(thumbUri.toString());
videoModel.setVideo_path(absolutePathThumb);
videoModel.setVideo_name(fileName);
videoModel.setVideo_thumb(cursor.getString(thum));
if (extension!=null){
videoModel.setVideo_extension(extension);
}else {
videoModel.setVideo_extension("mp4");
}
if (duration!=null){
videoModel.setDuration(duration);
}else {
videoModel.setDuration("00:00");
}
arrayListVideos.add(videoModel);
}
videoAdapter = new VideoAdapter(this,arrayListVideos);
shimmerFrameLayout.setVisibility(View.INVISIBLE);
shimmerFrameLayout.stopShimmer();
recyclerView.setVisibility(View.VISIBLE);
recyclerView.setAdapter(videoAdapter);
//set adapter
}
}
VideoAdapter:
public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoAdapterViewHolder> implements Filterable {
private final int MENU_ITEM_VIEW_TYPE = 0;
private final int UNIFIED_NATIVE_AD_VIEW_TYPE = 1;
Context context;
ArrayList<VideoModel> arrayListVideo;
ArrayList<VideoModel> arrayListVideoAll;
private Activity mainActivity;
public VideoAdapter(Context context, ArrayList<VideoModel> arrayListVideos){
this.context = context;
this.arrayListVideo = arrayListVideos;
arrayListVideoAll = new ArrayList<>(arrayListVideo);
mainActivity = (Activity) context;
}
#NonNull
#Override
public VideoAdapterViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
switch (i){
case UNIFIED_NATIVE_AD_VIEW_TYPE:
View unifiedNativeLayoutView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.ad_unified,viewGroup,false);
return new UnifiedNativeAdViewHolder(unifiedNativeLayoutView);
case MENU_ITEM_VIEW_TYPE:
default:
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_videos,viewGroup,false);
return new VideoAdapterViewHolder(view);
}
}
#Override
public void onBindViewHolder(#NonNull VideoAdapterViewHolder videoAdapterViewHolder, int i) {
int viewType = getItemViewType(i);
switch (viewType){
case UNIFIED_NATIVE_AD_VIEW_TYPE:
UnifiedNativeAd unifiedNativeAd = (UnifiedNativeAd)arrayListVideo.get(i);
populateNativeAdView(unifiedNativeAd,((UnifiedNativeAdViewHolder)videoAdapterViewHolder).getAdView());
break;
case MENU_ITEM_VIEW_TYPE:
Glide.with(context).load(arrayListVideo.get(i).getVideo_thumb()).into(videoAdapterViewHolder.image);
videoAdapterViewHolder.name.setText(arrayListVideo.get(i).getVideo_name() + "." + arrayListVideo.get(i).getVideo_extension());
videoAdapterViewHolder.duration.setText(arrayListVideo.get(i).getDuration());
}
}
#Override
public int getItemCount() {
return arrayListVideo.size();
}
public class VideoAdapterViewHolder extends RecyclerView.ViewHolder{
public ImageView image;
public TextView name,duration;
public ImageView videoOption;
public VideoAdapterViewHolder(#NonNull View itemView) {
super(itemView);
if (!(itemView instanceof AdView)) {
image = (ImageView)itemView.findViewById(R.id.vid_image);
name = (TextView)itemView.findViewById(R.id.vid_name);
duration = (TextView)itemView.findViewById(R.id.vid_duration);
videoOption = (ImageView) itemView.findViewById(R.id.video_option);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
VideoModel videoModel = arrayListVideo.get(getAdapterPosition());
((MainActivity)context).openVideoPlayerActivity(videoModel);
}
});
videoOption.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
VideoModel videoModel = arrayListVideo.get(getAdapterPosition());
((MainActivity)context).VideoMoreOption(videoModel);
}
});
}
}
}
private void populateNativeAdView(UnifiedNativeAd unifiedNativeAd, UnifiedNativeAdView adView) {
((TextView)adView.getHeadlineView()).setText(unifiedNativeAd.getHeadline());
((TextView)adView.getBodyView()).setText(unifiedNativeAd.getBody());
((TextView)adView.getCallToActionView()).setText(unifiedNativeAd.getCallToAction());
NativeAd.Image icon = unifiedNativeAd.getIcon();
if (icon==null){
adView.getIconView().setVisibility(View.INVISIBLE);
}else {
((ImageView)adView.getIconView()).setImageDrawable(icon.getDrawable());
adView.getIconView().setVisibility(View.VISIBLE);
}
if(unifiedNativeAd.getPrice()==null){
adView.getPriceView().setVisibility(View.INVISIBLE);
}else {
adView.getPriceView().setVisibility(View.VISIBLE);
((TextView)adView.getPriceView()).setText(unifiedNativeAd.getPrice());
}
if(unifiedNativeAd.getStarRating()==null){
adView.getStarRatingView().setVisibility(View.INVISIBLE);
}else {
adView.getStarRatingView().setVisibility(View.VISIBLE);
((RatingBar)adView.getStarRatingView()).setRating(unifiedNativeAd.getStarRating().floatValue());
}
if(unifiedNativeAd.getAdvertiser()==null){
adView.getAdvertiserView().setVisibility(View.INVISIBLE);
}else {
adView.getAdvertiserView().setVisibility(View.VISIBLE);
((TextView)adView.getAdvertiserView()).setText(unifiedNativeAd.getAdvertiser());
}
adView.setNativeAd(unifiedNativeAd);
}
}
public class VideoAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements Filterable {
private final int MENU_ITEM_VIEW_TYPE = 0;
private final int UNIFIED_NATIVE_AD_VIEW_TYPE = 1;
Context context;
ArrayList<Object> arrayListVideo;
ArrayList<Object> arrayListVideoAll;
private Activity mainActivity;
public VideoAdapter(Context context, ArrayList<Object> arrayListVideos) {
this.context = context;
this.arrayListVideo = arrayListVideos;
arrayListVideoAll = new ArrayList<>(arrayListVideo);
mainActivity = (Activity) context;
}
#Override
public int getItemViewType(int position) {
if(arrayListVideo.get(position) instanceof VideoModel){
return MENU_ITEM_VIEW_TYPE;
} else{
return UNIFIED_NATIVE_AD_VIEW_TYPE;
}
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
switch (i) {
case UNIFIED_NATIVE_AD_VIEW_TYPE:
View unifiedNativeLayoutView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.ad_unified, viewGroup, false);
return new UnifiedNativeAdViewHolder(unifiedNativeLayoutView);
case MENU_ITEM_VIEW_TYPE:
default:
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_videos, viewGroup, false);
return new VideoAdapterViewHolder(view);
}
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder viewHolder, int i) {
switch (viewHolder.getItemViewType()) {
case UNIFIED_NATIVE_AD_VIEW_TYPE:
UnifiedNativeAd unifiedNativeAd = (UnifiedNativeAd) arrayListVideo.get(i);
populateNativeAdView(unifiedNativeAd, ((UnifiedNativeAdViewHolder) viewHolder).getAdView());
break;
case MENU_ITEM_VIEW_TYPE:
final VideoAdapterViewHolder videoAdapterViewHolder = (VideoAdapterViewHolder) viewHolder;
final VideoModel item = (VideoModel) arrayListVideo.get(i);
Glide.with(context).load(item.getVideo_thumb()).into(videoAdapterViewHolder.image);
videoAdapterViewHolder.name.setText(item.getVideo_name() +
"." + item.getVideo_extension());
videoAdapterViewHolder.duration.setText(item.getDuration());
}
}
#Override
public int getItemCount() {
return arrayListVideo.size();
}
#Override
public Filter getFilter() {
return null;
}
public class VideoAdapterViewHolder extends RecyclerView.ViewHolder {
public ImageView image;
public TextView name, duration;
public ImageView videoOption;
public VideoAdapterViewHolder(#NonNull View itemView) {
super(itemView);
if (!(itemView instanceof AdView)) {
image = (ImageView) itemView.findViewById(R.id.vid_image);
name = (TextView) itemView.findViewById(R.id.vid_name);
duration = (TextView) itemView.findViewById(R.id.vid_duration);
videoOption = (ImageView) itemView.findViewById(R.id.video_option);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
VideoModel videoModel = (VideoModel) arrayListVideo.get(getAdapterPosition());
((MainActivity) context).openVideoPlayerActivity(videoModel);
}
});
videoOption.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
VideoModel videoModel = (VideoModel) arrayListVideo.get(getAdapterPosition());
((MainActivity) context).VideoMoreOption(videoModel);
}
});
}
}
}
private void populateNativeAdView(UnifiedNativeAd unifiedNativeAd, UnifiedNativeAdView adView) {
((TextView) adView.getHeadlineView()).setText(unifiedNativeAd.getHeadline());
((TextView) adView.getBodyView()).setText(unifiedNativeAd.getBody());
((TextView) adView.getCallToActionView()).setText(unifiedNativeAd.getCallToAction());
NativeAd.Image icon = unifiedNativeAd.getIcon();
if (icon == null) {
adView.getIconView().setVisibility(View.INVISIBLE);
} else {
((ImageView) adView.getIconView()).setImageDrawable(icon.getDrawable());
adView.getIconView().setVisibility(View.VISIBLE);
}
if (unifiedNativeAd.getPrice() == null) {
adView.getPriceView().setVisibility(View.INVISIBLE);
} else {
adView.getPriceView().setVisibility(View.VISIBLE);
((TextView) adView.getPriceView()).setText(unifiedNativeAd.getPrice());
}
if (unifiedNativeAd.getStarRating() == null) {
adView.getStarRatingView().setVisibility(View.INVISIBLE);
} else {
adView.getStarRatingView().setVisibility(View.VISIBLE);
((RatingBar) adView.getStarRatingView()).setRating(unifiedNativeAd.getStarRating().floatValue());
}
if (unifiedNativeAd.getAdvertiser() == null) {
adView.getAdvertiserView().setVisibility(View.INVISIBLE);
} else {
adView.getAdvertiserView().setVisibility(View.VISIBLE);
((TextView) adView.getAdvertiserView()).setText(unifiedNativeAd.getAdvertiser());
}
adView.setNativeAd(unifiedNativeAd);
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
public static final int NUMBER_OF_ADS = 5;
AdLoader adLoader;
#BindView(R.id.recyclerView)
RecyclerView recyclerView;
ArrayList<Object> arrayListVideos;
ArrayList<UnifiedNativeAd> nativeAds = new ArrayList<>();
private VideoAdapter videoAdapter;
private AdView mAdView;
private ShimmerFrameLayout shimmerFrameLayout;
private GridLayoutManager mLayoutManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int autoCardWith = DisplayUtils.calculateNoOfColumns(getApplicationContext());
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
adMob();
arrayListVideos = new ArrayList<>();
mLayoutManager = new GridLayoutManager(this, autoCardWith);
//mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
// #Override
//public int getSpanSize(int position) { if (position % MainActivity.ITEMS_PER_AD == 0) { return autoCardWith; } return 1; }
//});
recyclerView.setHasFixedSize(true);
//recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setLayoutManager(new GridLayoutManager(this, autoCardWith));
recyclerView.setHasFixedSize(true);
shimmerFrameLayout = findViewById(R.id.shimmer_view);
shimmerFrameLayout.setVisibility(View.VISIBLE);
shimmerFrameLayout.startShimmer();
recyclerView.setVisibility(View.INVISIBLE);
askPermission();
displayAllVideos();
loadNativeAds();
}
private void loadNativeAds() {
AdLoader.Builder builder = new AdLoader.Builder(this,getResources().getString(R.string.native_ad_id));
adLoader = builder.forUnifiedNativeAd(new UnifiedNativeAd.OnUnifiedNativeAdLoadedListener() {
#Override
public void onUnifiedNativeAdLoaded(UnifiedNativeAd unifiedNativeAd) {
nativeAds.add(unifiedNativeAd);
if (!adLoader.isLoading()) {
insertAdsinMenuItem();
}
}
}).withAdListener(new AdListener() {
#Override
public void onAdFailedToLoad(int i) {
super.onAdFailedToLoad(i);
if (!adLoader.isLoading()) {
insertAdsinMenuItem();
}
}
}).build();
adLoader.loadAds(new AdRequest.Builder().build(),NUMBER_OF_ADS);
}
private void insertAdsinMenuItem() {
if (nativeAds.size() < 0 ){
return;
}
int offset = (arrayListVideos.size() / nativeAds.size() + 1 );
int index = 0;
for (UnifiedNativeAd ad:nativeAds){
arrayListVideos.add(index,ad);
index = index + offset;
}
}
private void adMob() {
MobileAds.initialize(this, new OnInitializationCompleteListener() {
#Override
public void onInitializationComplete(InitializationStatus initializationStatus) {
}
});
mAdView = findViewById(R.id.main_banner);
AdRequest adRequest = new AdRequest.Builder().build();
mAdView.loadAd(adRequest);
}
private void askPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)){
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:"+getPackageName()));
startActivityForResult(intent,2084);
}
}
private void displayAllVideos() {
Uri uri;
Cursor cursor;
int column_index_data,thum;
String absolutePathThumb;
uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
String[] projection = {MediaStore.MediaColumns.DATA, MediaStore.Video.Media.BUCKET_DISPLAY_NAME, MediaStore.Video.Media._ID,MediaStore.Video.Thumbnails.DATA};
final String orderBy = MediaStore.Video.Media.DEFAULT_SORT_ORDER;
cursor = getApplicationContext().getContentResolver().query(uri,projection,null,null,orderBy);
column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
thum = cursor.getColumnIndexOrThrow(MediaStore.Video.Thumbnails.DATA);
while (cursor.moveToNext()){
Log.d("VIDEO", cursor.getString(0));
absolutePathThumb = cursor.getString(column_index_data);
Uri thumbUri = Uri.fromFile(new File(absolutePathThumb));
//String cursorThumb = cursor.getString(thum);
String fileName = FilenameUtils.getBaseName(absolutePathThumb);
String extension = FilenameUtils.getExtension(absolutePathThumb);
String duration = getDuration(absolutePathThumb);
VideoModel videoModel = new VideoModel();
videoModel.setDuration(duration);
videoModel.setVideo_uri(thumbUri.toString());
videoModel.setVideo_path(absolutePathThumb);
videoModel.setVideo_name(fileName);
videoModel.setVideo_thumb(cursor.getString(thum));
if (extension!=null){
videoModel.setVideo_extension(extension);
}else {
videoModel.setVideo_extension("mp4");
}
if (duration!=null){
videoModel.setDuration(duration);
}else {
videoModel.setDuration("00:00");
}
arrayListVideos.add(videoModel);
}
videoAdapter = new VideoAdapter(this,arrayListVideos);
shimmerFrameLayout.setVisibility(View.INVISIBLE);
shimmerFrameLayout.stopShimmer();
recyclerView.setVisibility(View.VISIBLE);
recyclerView.setAdapter(videoAdapter);
//set adapter
}
}
Add some fragment continuously then RecyclerView scroll lagging
I am remove image load library but not any improvement i am face same
problem.
I am also check this link click
add fragment then facing this problem. but replace fragment then not facing this problem
more add fragment then RecyclerView lagging. i think fragment memory issue
add more then three fragment continuously then face this problem
fragment xml
<FrameLayout
android:id="#+id/frameLayout_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/toolbar_main"
android:layout_above="#+id/linearLayout_adView_main" />
call fragment data
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
RelatedFragment relatedFragment = new RelatedFragment();
Bundle bundle = new Bundle();
bundle.putString("type", "related");
bundle.putString("post_id", statusLists.getId());
bundle.putString("cat_id", statusLists.getCid());
bundle.putString("typeLayout", "Landscape");
relatedFragment.setArguments(bundle);
getActivity().getSupportFragmentManager().beginTransaction().add(R.id.frameLayout_main, relatedFragment, string).addToBackStack(string).commitAllowingStateLoss();
}
});
xml file
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView_sub_category"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_marginTop="5dp" />
Fragment
public class RelatedFragment extends Fragment {
private Method method;
private OnClick onClick;
private String type, post_id, cat_id, typeLayout;
private ProgressBar progressBar;
private MaterialTextView textView_noData;
private RecyclerView recyclerView;
private List<SubCategoryList> relatedLists;
private SubCategoryAdapter subCategoryAdapter;
private LayoutAnimationController animation;
private Boolean isOver = false;
private int pagination_index = 1;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = LayoutInflater.from(getActivity()).inflate(R.layout.portrait_fragment, container, false);
GlobalBus.getBus().register(this);
MainActivity.toolbar.setTitle(getResources().getString(R.string.related_status));
relatedLists = new ArrayList<>();
onClick = new OnClick() {
#Override
public void position(int position, String title, String type, String status_type, String id, String tag) {
SCDetailFragment scDetailFragment = new SCDetailFragment();
Bundle bundle = new Bundle();
bundle.putString("id", id);
bundle.putString("type", type);
bundle.putString("status_type", status_type);
bundle.putInt("position", position);
scDetailFragment.setArguments(bundle);
getActivity().getSupportFragmentManager().beginTransaction().add(R.id.frameLayout_main, scDetailFragment, title).addToBackStack(title).commitAllowingStateLoss();
}
};
method = new Method(getActivity(), onClick);
assert getArguments() != null;
type = getArguments().getString("type");
post_id = getArguments().getString("post_id");
cat_id = getArguments().getString("cat_id");
typeLayout = getArguments().getString("typeLayout");
int resId = R.anim.layout_animation_fall_down;
animation = AnimationUtils.loadLayoutAnimation(getActivity(), resId);
progressBar = view.findViewById(R.id.progressbar_portrait_fragment);
textView_noData = view.findViewById(R.id.textView_portrait_fragment);
recyclerView = view.findViewById(R.id.recyclerView_portrait_fragment);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
recyclerView.addOnScrollListener(new EndlessRecyclerViewScrollListener(layoutManager) {
#Override
public void onLoadMore(int page, int totalItemsCount) {
if (!isOver) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
pagination_index++;
callData();
}
}, 1000);
} else {
subCategoryAdapter.hideHeader();
}
}
});
callData();
setHasOptionsMenu(true);
return view;
}
#Override
public void onCreateOptionsMenu(#NonNull Menu menu, #NonNull MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.search_menu, menu);
MenuItem searchItem = menu.findItem(R.id.ic_searchView);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setOnQueryTextListener((new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
if (method.isNetworkAvailable()) {
backStackRemove();
SearchFragment searchFragment = new SearchFragment();
Bundle bundle = new Bundle();
bundle.putString("search_menu", query);
bundle.putString("typeLayout", "Landscape");
searchFragment.setArguments(bundle);
getActivity().getSupportFragmentManager()
.beginTransaction().replace(R.id.frameLayout_main, searchFragment, query).commitAllowingStateLoss();
return false;
} else {
method.alertBox(getResources().getString(R.string.internet_connection));
}
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
return true;
}
}));
super.onCreateOptionsMenu(menu, inflater);
}
private void backStackRemove() {
for (int i = 0; i < getActivity().getSupportFragmentManager().getBackStackEntryCount(); i++) {
getActivity().getSupportFragmentManager().popBackStack();
}
}
#Subscribe
public void getNotify(Events.FavouriteNotify favouriteNotify) {
for (int i = 0; i < relatedLists.size(); i++) {
if (relatedLists.get(i).getId().equals(favouriteNotify.getId())) {
if (relatedLists.get(i).getStatus_type().equals(favouriteNotify.getStatus_type())) {
relatedLists.get(i).setIs_favourite(favouriteNotify.getIs_favourite());
subCategoryAdapter.notifyItemChanged(i);
}
}
}
}
#Subscribe
public void getMessage(Events.InfoUpdate infoUpdate) {
if (subCategoryAdapter != null) {
for (int i = 0; i < relatedLists.size(); i++) {
if (relatedLists.get(i).getId().equals(infoUpdate.getId())) {
if (relatedLists.get(i).getStatus_type().equals(infoUpdate.getStatus_type())) {
switch (infoUpdate.getType()) {
case "all":
relatedLists.get(i).setTotal_viewer(infoUpdate.getView());
relatedLists.get(i).setTotal_likes(infoUpdate.getTotal_like());
relatedLists.get(i).setAlready_like(infoUpdate.getAlready_like());
break;
case "view":
relatedLists.get(i).setTotal_viewer(infoUpdate.getView());
break;
case "like":
relatedLists.get(i).setTotal_likes(infoUpdate.getTotal_like());
relatedLists.get(i).setAlready_like(infoUpdate.getAlready_like());
break;
}
subCategoryAdapter.notifyItemChanged(i);
}
}
}
}
}
private void callData() {
if (getActivity() != null) {
if (method.isNetworkAvailable()) {
if (method.pref.getBoolean(method.pref_login, false)) {
related(method.pref.getString(method.profileId, null), post_id, cat_id, typeLayout);
} else {
related("0", post_id, cat_id, typeLayout);
}
} else {
method.alertBox(getResources().getString(R.string.internet_connection));
}
}
}
private void related(String userId, String post_id, String cat_id, String typeLayout) {
if (subCategoryAdapter == null) {
relatedLists.clear();
progressBar.setVisibility(View.VISIBLE);
}
if (getActivity() != null) {
AsyncHttpClient client = new AsyncHttpClient();
RequestParams params = new RequestParams();
JsonObject jsObj = (JsonObject) new Gson().toJsonTree(new API(getActivity()));
jsObj.addProperty("method_name", "related_status");
jsObj.addProperty("post_id", post_id);
jsObj.addProperty("cat_id", cat_id);
jsObj.addProperty("user_id", userId);
jsObj.addProperty("page", pagination_index);
jsObj.addProperty("filter_value", typeLayout);
jsObj.addProperty("lang_ids", method.pref.getString(method.language_ids, ""));
params.put("data", API.toBase64(jsObj.toString()));
client.post(Constant_Api.url, params, new AsyncHttpResponseHandler() {
#Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
if (getActivity() != null) {
String res = new String(responseBody);
try {
JSONObject jsonObject = new JSONObject(res);
if (jsonObject.has(Constant_Api.STATUS)) {
String status = jsonObject.getString("status");
String message = jsonObject.getString("message");
if (status.equals("-2")) {
method.suspend(message);
} else {
method.alertBox(message);
}
} else {
JSONArray jsonArray = jsonObject.getJSONArray(Constant_Api.tag);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject object = jsonArray.getJSONObject(i);
String id = object.getString("id");
String status_type = object.getString("status_type");
String status_title = object.getString("status_title");
String status_layout = object.getString("status_layout");
String status_thumbnail_b = object.getString("status_thumbnail_b");
String status_thumbnail_s = object.getString("status_thumbnail_s");
String total_likes = object.getString("total_likes");
String total_viewer = object.getString("total_viewer");
String category_name = object.getString("category_name");
String already_like = object.getString("already_like");
String quote_bg = object.getString("quote_bg");
String quote_font = object.getString("quote_font");
String is_favourite = object.getString("is_favourite");
relatedLists.add(new SubCategoryList("", id, status_type, status_title, status_layout, status_thumbnail_b, status_thumbnail_s, total_viewer, total_likes, already_like, category_name, quote_bg, quote_font, is_favourite, "", ""));
}
if (jsonArray.length() == 0) {
if (subCategoryAdapter != null) {
isOver = true;
subCategoryAdapter.hideHeader();
}
}
if (subCategoryAdapter == null) {
if (relatedLists.size() == 0) {
textView_noData.setVisibility(View.VISIBLE);
} else {
textView_noData.setVisibility(View.GONE);
subCategoryAdapter = new SubCategoryAdapter(getActivity(), relatedLists, onClick, type);
recyclerView.setAdapter(subCategoryAdapter);
recyclerView.setLayoutAnimation(animation);
}
} else {
subCategoryAdapter.notifyDataSetChanged();
}
}
} catch (JSONException e) {
e.printStackTrace();
method.alertBox(getResources().getString(R.string.failed_try_again));
}
}
progressBar.setVisibility(View.GONE);
}
#Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
progressBar.setVisibility(View.GONE);
method.alertBox(getResources().getString(R.string.failed_try_again));
}
});
}
}
#Override
public void onDestroyView() {
super.onDestroyView();
// Unregister the registered event.
GlobalBus.getBus().unregister(this);
}
}
Adapter
public class SubCategoryAdapter extends RecyclerView.Adapter {
private Activity activity;
private Method method;
private int columnWidth;
private String type;
private Animation myAnim;
private List<SubCategoryList> subCategoryLists;
private final int VIEW_TYPE_LOADING = 0;
private final int VIEW_TYPE_ITEM = 1;
private final int VIEW_TYPE_QUOTES = 2;
public SubCategoryAdapter(Activity activity, List<SubCategoryList> subCategoryLists, OnClick interstitialAdView, String type) {
this.activity = activity;
this.type = type;
this.subCategoryLists = subCategoryLists;
method = new Method(activity, interstitialAdView);
columnWidth = (method.getScreenWidth());
myAnim = AnimationUtils.loadAnimation(activity, R.anim.bounce);
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_ITEM) {
View view = LayoutInflater.from(activity).inflate(R.layout.sub_category_adapter, parent, false);
return new ViewHolder(view);
} else if (viewType == VIEW_TYPE_QUOTES) {
View v = LayoutInflater.from(activity).inflate(R.layout.quotes_adapter, parent, false);
return new Quotes(v);
} else if (viewType == VIEW_TYPE_LOADING) {
View v = LayoutInflater.from(activity).inflate(R.layout.layout_loading_item, parent, false);
return new ProgressViewHolder(v);
}
return null;
}
#Override
public void onBindViewHolder(#NonNull final RecyclerView.ViewHolder holder, #SuppressLint("RecyclerView") final int position) {
if (holder.getItemViewType() == VIEW_TYPE_ITEM) {
final ViewHolder viewHolder = (ViewHolder) holder;
if (status_type.equals("gif")) {
Glide.with(activity)
.asBitmap()
.load(subCategoryLists.get(position).getStatus_thumbnail_s())
.placeholder(R.drawable.placeholder_landscape).into(viewHolder.imageView);
} else {
Glide.with(activity)
.load(subCategoryLists.get(position).getStatus_thumbnail_s())
.placeholder(R.drawable.placeholder_landscape).into(viewHolder.imageView);
}
viewHolder.imageView.setLayoutParams(new RelativeLayout.LayoutParams(columnWidth, columnWidth / 2 - 60));
viewHolder.textView_title.setText(subCategoryLists.get(position).getStatus_title());
viewHolder.relativeLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
method.onClickData(position, subCategoryLists.get(position).getStatus_title(), type, subCategoryLists.get(position).getStatus_type(), subCategoryLists.get(position).getId(), "");
}
});
} else if (holder.getItemViewType() == VIEW_TYPE_QUOTES) {
final Quotes quotes = (Quotes) holder;
quotes.relativeLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, columnWidth / 2));
Typeface typeface = Typeface.createFromAsset(activity.getAssets(), "text_font/" + subCategoryLists.get(position).getQuote_font());
quotes.textView.setTypeface(typeface);
quotes.textView.setText(subCategoryLists.get(position).getStatus_title());
quotes.textView.post(new Runnable() {
#Override
public void run() {
ViewGroup.LayoutParams params = quotes.textView.getLayoutParams();
if (params == null) {
params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
final int widthSpec = View.MeasureSpec.makeMeasureSpec(quotes.textView.getWidth(), View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(quotes.textView.getHeight(), View.MeasureSpec.UNSPECIFIED);
quotes.textView.measure(widthSpec, heightSpec);
quotes.textView.setMaxLines(heightSpec / quotes.textView.getLineHeight());
quotes.textView.setEllipsize(TextUtils.TruncateAt.END);
}
});
quotes.linearLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
method.onClickData(position, subCategoryLists.get(position).getStatus_title(), type, subCategoryLists.get(position).getStatus_type(), subCategoryLists.get(position).getId(), "");
}
});
}
}
#Override
public int getItemCount() {
return subCategoryLists.size() + 1;
}
public void hideHeader() {
ProgressViewHolder.progressBar.setVisibility(View.GONE);
}
#Override
public int getItemViewType(int position) {
if (position != subCategoryLists.size()) {
if (subCategoryLists.get(position).getStatus_type().equals("quote")) {
return VIEW_TYPE_QUOTES;
} else {
return VIEW_TYPE_ITEM;
}
} else {
return VIEW_TYPE_LOADING;
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
private RelativeLayout relativeLayout;
private ImageView imageView;
private MaterialTextView textView_title;
public ViewHolder(View itemView) {
super(itemView);
relativeLayout = itemView.findViewById(R.id.relativeLayout_subCat_adapter);
imageView = itemView.findViewById(R.id.imageView_subCat_adapter);
textView_title = itemView.findViewById(R.id.textView_title_subCat_adapter);
}
}
public class Quotes extends RecyclerView.ViewHolder {
private LinearLayout linearLayout;
private RelativeLayout relativeLayout;
private MaterialTextView textView;
public Quotes(#NonNull View itemView) {
super(itemView);
linearLayout = itemView.findViewById(R.id.ll_quotes_adapter);
textView = itemView.findViewById(R.id.textView_quotes_adapter);
relativeLayout = itemView.findViewById(R.id.rel_quotes_adapter);
}
}
public static class ProgressViewHolder extends RecyclerView.ViewHolder {
public static ProgressBar progressBar;
public ProgressViewHolder(View v) {
super(v);
progressBar = v.findViewById(R.id.progressBar);
}
} }
I am currently working on an app, that finds all MP3s on a users phone and then puts them into a list. This works very fine and is very quick, even with many songs. Now I populate a new list with an object for each item of the list to then display it inside my recyclerview. The problem is, that I have 700+ songs on my phone and this blocks the UI thread quite some time.
Now, I want to use the recyclerview to not load all items from the list into the objects all at once but rather only when they are about to be displayed - but I have NO clue over how to do this. Right now, all objects are build and then displayed in a very long scrollview from the recyclerview after the UI thread has been blocked for a good 30 seconds. Can please anyone help me? Here is my code:
namespace Media_Player
{
[Activity(Label = "Media_Player", MainLauncher = true)]
public class MainActivity : Activity
{
static public MediaPlayer mediaPlayer;
List<MP3object> mp3;
MediaMetadataRetriever reader;
public static Button btn_StartOrPause, btn_Stop;
public static TextView txt_CurrentSong;
public static bool stopIsActive = false, firstStart = true;
public static Android.Net.Uri CurrentActiveSongUri;
RecyclerView mRecyclerView;
RecyclerView.LayoutManager mLayoutManager;
PhotoAlbumAdapter mAdapter;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.test);
reader = new MediaMetadataRetriever();
PopulateMP3List(ReturnPlayableMp3(true));
mediaPlayer = new MediaPlayer();
InitRecView();
}
private void InitRecView()
{
// Instantiate the adapter and pass in its data source:
mAdapter = new PhotoAlbumAdapter(mp3);
// Get our RecyclerView layout:
mRecyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
// Plug the adapter into the RecyclerView:
mRecyclerView.SetAdapter(mAdapter);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.SetLayoutManager(mLayoutManager);
}
private void PopulateMP3List(List<string> content)
{
mp3 = new List<MP3object>();
foreach (string obj in content)
{
WriteMetaDataToFileList(obj);
}
}
void WriteMetaDataToFileList(string obj)
{
reader.SetDataSource(obj);
//Write Mp3 as object to global list
MP3object ob = new MP3object();
{
if(reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle) != null)
{
ob.SongName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle);
}
else
{
ob.SongName = Resources.GetString(Resource.String.Unknown);
}
if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist) != null)
{
ob.ArtistName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist);
}
else
{
ob.ArtistName = Resources.GetString(Resource.String.Unknown);
}
if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyAlbum) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyAlbum) != null)
{
ob.AlbumName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyAlbum);
}
else
{
ob.AlbumName = Resources.GetString(Resource.String.Unknown);
}
if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != null)
{
ob.Year = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear);
}
else
{
ob.Year = Resources.GetString(Resource.String.Unknown);
}
if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != null)
{
ob.Year = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear);
}
else
{
ob.Year = Resources.GetString(Resource.String.Unknown);
}
ob.Mp3Uri = obj; // can never be unknown!
ob.DurationInSec = int.Parse(reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyDuration)) / 1000; // can never be unknown, div by 1000 to get sec not millis
}
mp3.Add(ob);
}
public List<string> ReturnPlayableMp3(bool sdCard)
{
List<string> res = new List<string>();
string phyle;
string path1 = null;
if(sdCard) // get mp3 from SD card
{
string baseFolderPath = "";
try
{
bool getSDPath = true;
Context context = Application.Context;
Java.IO.File[] dirs = context.GetExternalFilesDirs(null);
foreach (Java.IO.File folder in dirs)
{
bool IsRemovable = Android.OS.Environment.InvokeIsExternalStorageRemovable(folder);
bool IsEmulated = Android.OS.Environment.InvokeIsExternalStorageEmulated(folder);
if (getSDPath ? IsRemovable && !IsEmulated : !IsRemovable && IsEmulated)
baseFolderPath = folder.Path;
}
}
catch (Exception ex)
{
Console.WriteLine("GetBaseFolderPath caused the following exception: {0}", ex);
}
string xy = baseFolderPath.Remove(18); // This is result after this, but this hard coded solution could be a problem on different phones.: "/storage/05B6-2226/Android/data/Media_Player.Media_Player/files"
path1 = xy;
// path to SD card and MUSIC "/storage/05B6-2226/"
}
else // get Mp3 from internal storage
{
path1 = Android.OS.Environment.ExternalStorageDirectory.ToString();
}
var mp3Files = Directory.EnumerateFiles(path1, "*.mp3", SearchOption.AllDirectories);
foreach (string currentFile in mp3Files)
{
phyle = currentFile;
res.Add(phyle);
}
return res;
}
}
public class PhotoViewHolder : RecyclerView.ViewHolder
{
public ImageView Image { get; private set; }
public TextView Caption { get; private set; }
public PhotoViewHolder(View itemView) : base(itemView)
{
// Locate and cache view references:
Image = itemView.FindViewById<ImageView>(Resource.Id.imageView);
Caption = itemView.FindViewById<TextView>(Resource.Id.textView);
}
}
public class PhotoAlbumAdapter : RecyclerView.Adapter
{
public List<MP3object> mp3;
public PhotoAlbumAdapter(List<MP3object> mp3)
{
this.mp3 = mp3;
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.From(parent.Context).
Inflate(Resource.Layout.lay, parent, false);
PhotoViewHolder vh = new PhotoViewHolder(itemView);
return vh;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
PhotoViewHolder vh = holder as PhotoViewHolder;
vh.Caption.Text = mp3[position].SongName;
}
public override int ItemCount
{
get { return mp3.Count(); }
}
}
}
So getting the list of strings with the locations of the Mp3 works very quickly, but then "WriteMetaDataToFileList(obj)" kicks in, comming from "PopulateMP3List(List content)" and this is what takes so long. What I think I need is for the recyclerview to only build the first 20 objects, and when the user starts scrolling, builds the next 20 objects and attaches them to list for them to also be scrolled. Please help me out here :)
Here is an abstract class:
public abstract class PaginationScrollListener extends RecyclerView.OnScrollListener {
private LinearLayoutManager linearLayoutManager;
protected PaginationScrollListener(LinearLayoutManager linearLayoutManager) {
this.linearLayoutManager = linearLayoutManager;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = linearLayoutManager.getChildCount();
int totalItemCount = linearLayoutManager.getItemCount();
int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
if (!isLoading() && !isLastPage()) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount && firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
}
protected abstract void loadMoreItems();
public abstract boolean isLastPage();
public abstract boolean isLoading();
}
and In your adapter you must follow this pattern:
public class ConsultancyAdapter extends RecyclerView.Adapter<ConsultancyAdapter.ConsultancyVH> {
private static final int ITEM = 0;
private static final int LOADING = 1;
private boolean isLoadingAdded = false;
public ConsultancyAdapter(List<Consultancy> consultancies, ConsultancyAdapterListener listener) {
}
#NonNull
#Override
public ConsultancyVH onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder = null;
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
switch (viewType) {
case ITEM:
viewHolder = getViewHolder(parent, layoutInflater);
break;
case LOADING:
View v2 = layoutInflater.inflate(R.layout.item_progress, parent, false);
viewHolder = new ConsultancyVH(v2);
break;
}
return (ConsultancyVH) viewHolder;
}
#NonNull
private RecyclerView.ViewHolder getViewHolder(ViewGroup parent, LayoutInflater inflater) {
RecyclerView.ViewHolder viewHolder;
View v1 = inflater.inflate(R.layout.item_consultancy, parent, false);
viewHolder = new ConsultancyVH(v1);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull ConsultancyVH holder, int position) {
Consultancy consultancy = consultancies.get(position);
switch (getItemViewType(position)) {
case ITEM:
ConsultancyVH mySingeCounseller = holder;
holder.title.setText(consultancy.getTitle()); // set cardTitle
holder.fieldArea.setText(consultancy.getField_filedoctorskills());
break;
case LOADING:
break;
}
}
#Override
public int getItemCount() {
return consultancies.size();
}
#Override
public int getItemViewType(int position) {
return (position == consultancies.size() - 1 && isLoadingAdded) ? LOADING : ITEM;
}
public void add(Consultancy mc) {
consultancies.add(mc);
notifyItemInserted(consultancies.size() - 1);
}
public void addAll(List<Consultancy> mcList) {
for (Consultancy mc : mcList) {
add(mc);
}
}
public void remove(Consultancy city) {
int position = consultancies.indexOf(city);
if (position > -1) {
consultancies.remove(position);
notifyItemRemoved(position);
}
}
public Consultancy getItem(int position) {
return consultancies.get(position);
}
public void clear() {
isLoadingAdded = false;
while (getItemCount() > 0) {
remove(getItem(0));
}
}
public boolean isEmpty() {
return getItemCount() == 0;
}
public void addLoadingFooter() {
isLoadingAdded = true;
add(new Consultancy());
}
public void removeLoadingFooter() {
isLoadingAdded = false;
int position = consultancies.size() - 1;
Consultancy item = getItem(position);
if (item != null) {
consultancies.remove(position);
notifyItemRemoved(position);
}
}
public interface ConsultancyAdapterListener {
void onCaseClicked(int position, String nid, String fieldArea, String title);
}
protected class ConsultancyVH extends RecyclerView.ViewHolder {
private TextView title, fieldArea;
private CircleImageView iconProfile;
private MaterialRippleLayout caseButtonRipple;
public ConsultancyVH(View itemView) {
super(itemView);
caseButtonRipple = itemView.findViewById(R.id.case_button_ripple);
this.title = itemView.findViewById(R.id.docName);
this.fieldArea = itemView.findViewById(R.id.fieldArea);
this.iconProfile = itemView.findViewById(R.id.icon_profile);
}
}
}
and in your activity:
private void setScrollListener() {
recyclerView.addOnScrollListener(new PaginationScrollListener(linearLayoutManager) {
#Override
protected void loadMoreItems() {
isLoading = true;
currentPage += 1;
loadNextPage();
}
#Override
public boolean isLastPage() {
return isLastPage;
}
#Override
public boolean isLoading() {
return isLoading;
}
});
loadFirstPage();
}
and in my loadFirstPage i talk to a API and you need some your code:
private void loadFirstPage() {
CallData().enqueue(new DefaultRetrofitCallback<List<Consultancy>>() {
#Override
protected void onFailure(Throwable t) {
super.onFailure(t);
}
#Override
protected void onSuccess(List<Consultancy> response) {
swipeRefreshLayout.setRefreshing(false);
dataList = response;
adapter.addAll(dataList);
recyclerView.setAdapter(adapter);
if (!checkLast(response)) adapter.addLoadingFooter();
else isLastPage = true;
}
#Override
protected void onOtherStatus(Response<List<Consultancy>> response) {
super.onOtherStatus(response);
}
#Override
protected void always() {
super.always();
}
});
}
and loadNextPage:
private void loadNextPage() {
CallData().enqueue(new DefaultRetrofitCallback<List<Consultancy>>() {
#Override
protected void onFailure(Throwable t) {
super.onFailure(t);
}
#Override
protected void onSuccess(List<Consultancy> response) {
swipeRefreshLayout.setRefreshing(false);
adapter.removeLoadingFooter();
isLoading = false;
swipeRefreshLayout.setRefreshing(false);
adapter.addAll(response);
if (!checkLast(response)) adapter.addLoadingFooter();
else isLastPage = true;
}
#Override
protected void onOtherStatus(Response<List<Consultancy>> response) {
super.onOtherStatus(response);
}
#Override
protected void always() {
super.always();
}
});
}
I have implemented recyclerview and it loads 10 tasks at a time. Once every task is completed, I am removing the task with a completed button. It loads the 11th item if I scroll to the bottom at one condition there has to be atleast one uncompleted task present on the page. If I clear all 10 items it just shows me loading animation and nothing happens. I have to close the app and open again to load the tasks again.
I have implemented a PaginationActivity, PaginationAdapter and scroll listener for the page. It loads data from a local database whenever you have reached the end of the page and you have got more tasks to load.
PaginationActivity.java
public class PaginationActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
PaginationAdapter adapter;
LinearLayoutManager linearLayoutManager;
public static ArrayList<String> reasonsdata = new ArrayList<String>();
public static String rslt="";
public static String[] reasons;
public static boolean chk = true;
public static String payTypeSelected;
public static String reasonSelected;
public static HashMap<String, EditText> drivernotesMap = new HashMap<String, EditText>();
public static HashMap<String, EditText> paynoteMap = new HashMap<String, EditText>();
public static HashMap<String, String> paytypeMap = new HashMap<String, String>();
RecyclerView rv;
ProgressBar progressBar;
private static final int PAGE_START = 0;
private boolean isLoading = false;
private boolean isLastPage = false;
private int TOTAL_PAGES;
private int currentPage = PAGE_START;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pagination_main);
fillspinner();
rv = (RecyclerView) findViewById(R.id.main_recycler);
progressBar = (ProgressBar) findViewById(R.id.main_progress);
adapter = new PaginationAdapter(this);
linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
rv.setLayoutManager(linearLayoutManager);
rv.setItemAnimator(new DefaultItemAnimator());
rv.setAdapter(adapter);
rv.addOnScrollListener(new PaginationScrollListener(linearLayoutManager) {
#Override
protected void loadMoreItems() {
isLoading = true;
currentPage += 1;
// mocking network delay for API call
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
loadNextPage();
}
}, 1000);
}
#Override
public int getTotalPageCount() {
return TOTAL_PAGES;
}
#Override
public boolean isLastPage() {
return isLastPage;
}
#Override
public boolean isLoading() {
return isLoading;
}
});
// mocking network delay for API call
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
loadFirstPage();
}
}, 1000);
}
private void fillspinner() {
reasonsdata.clear();
try
{
rslt="START";
CallReasons cr = new CallReasons();
cr.join();
cr.start();
while(rslt=="START") {
try {
Thread.sleep(10);
}catch(Exception ex) {
}
}
}
catch (Exception ex) {
ex.printStackTrace();
}
if(rslt == "Success"){
reasonsdata.add("<--- Select Reason --->");
for(int i =0; i< reasons.length;i++){
reasonsdata.add(reasons[i]);
}
}
}
public void loadFirstPage() {
Log.d(TAG, "loadFirstPage: ");
TOTAL_PAGES = Run.getTotalPages();
//List<Run> runs = Run.createRuns(adapter.getItemCount());
List<Run> runs = Run.createRuns(adapter.getMaxID());
progressBar.setVisibility(View.GONE);
adapter.addAll(runs);
if (currentPage <= TOTAL_PAGES) adapter.addLoadingFooter();
else isLastPage = true;
}
public void loadNextPage() {
Log.d(TAG, "loadNextPage: " + currentPage);
//List<Run> runs = Run.createRuns(adapter.getItemCount());
List<Run> runs = Run.createRuns(adapter.getMaxID());
adapter.removeLoadingFooter();
isLoading = false;
adapter.addAll(runs);
if (currentPage != TOTAL_PAGES) adapter.addLoadingFooter();
else isLastPage = true;
}
}
PaginationAdapter.java
public class PaginationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int ITEM = 0;
private static final int LOADING = 1;
private List<Run> runs = new ArrayList<>();
private Context context;
private boolean isLoadingAdded = false;
private String cash = "CASH";
private String cheque = "CHEQUE";
SQLiteDatabase db;
//PaginationActivity func_call;
public PaginationAdapter(Context context) {
this.context = context;
runs = new ArrayList<>();
//func_call = new PaginationActivity();
}
public List<Run> getRuns() {
return runs;
}
public void setRuns(List<Run> runs) {
this.runs = runs;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder = null;
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (viewType) {
case ITEM:
viewHolder = getViewHolder(parent, inflater);
break;
case LOADING:
View v2 = inflater.inflate(R.layout.item_progress, parent, false);
viewHolder = new LoadingVH(v2);
//viewHolder = getViewHolder(parent, inflater);
//func_call.loadNextPage();
break;
}
return viewHolder;
}
#NonNull
private RecyclerView.ViewHolder getViewHolder(ViewGroup parent, LayoutInflater inflater) {
RecyclerView.ViewHolder viewHolder;
View v1 = inflater.inflate(R.layout.activity_main, parent, false);
viewHolder = new RunVH(v1);
return viewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final Run run = runs.get(position);
switch (getItemViewType(position)) {
case ITEM:
final RunVH runVH = (RunVH) holder;
//adding text areas to show data
//Completed task button
runVH.btnSave.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(context, "Mark as Completed", Toast.LENGTH_LONG).show();
remove(run);
}
}
} catch (Exception ex) {
//pbbar.setVisibility(View.GONE);
ex.printStackTrace();
}
}
});
break;
case LOADING:
break;
}
}
#Override
public int getItemCount() {
return runs == null ? 0 : runs.size();
}
//#Override
public int getMaxID() {
if(runs == null || runs.size() == 0){
return 0;
}else{
Run test = runs.get(runs.size()-2);
int maxid = (int) test.getProperty(0);
return maxid;
}
}
#Override
public int getItemViewType(int position) {
return (position == runs.size() - 1 && isLoadingAdded) ? LOADING : ITEM;
}
public void add(Run run) {
runs.add(run);
notifyItemInserted(runs.size() - 1);
}
public void addAll(List<Run> runList) {
for (Run run : runList) {
add(run);
}
}
public void remove(Run run) {
int position = runs.indexOf(run);
if (position > -1) {
runs.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, runs.size());
}
/*if(runs.size() < 0){
clear();
func_call.loadNextPage();
}*/
}
public void clear() {
isLoadingAdded = false;
while (getItemCount() > 0) {
remove(getItem(0));
}
}
public boolean isEmpty() {
return getItemCount() == 0;
}
public void addLoadingFooter() {
isLoadingAdded = true;
add(new Run());
}
public void removeLoadingFooter() {
isLoadingAdded = false;
int position = runs.size() - 1;
Run item = getItem(position);
if (item != null) {
runs.remove(position);
notifyItemRemoved(position);
}
}
public Run getItem(int position) {
return runs.get(position);
}
protected class RunVH extends RecyclerView.ViewHolder {
private TextView textEsky;
private TextView textAdjusted;
private TextView textAddress;
private TextView textNew;
private TextView textTrusted;
private TextView textName;
private TextView textMobile;
private TextView textHome;
private TextView textWork;
private TextView payType;
private EditText textPayNote;
private EditText textDeliveryInst;
private EditText textDriverNotes;
private final Spinner reasons;
private RadioGroup rg;
private Button btnSave;
private CheckBox IsDriverData;
public RunVH(View itemView) {
super(itemView);
textEsky = (TextView) itemView.findViewById(R.id.idEsky);
textAdjusted = (TextView) itemView.findViewById(R.id.idAdjusted);
textAddress = (TextView) itemView.findViewById(R.id.idAddress);
textNew = (TextView) itemView.findViewById(R.id.idNew);
textTrusted = (TextView) itemView.findViewById(R.id.idTrusted);
textName = (TextView) itemView.findViewById(R.id.idName);
textMobile = (TextView) itemView.findViewById(R.id.idMobile);
textHome = (TextView) itemView.findViewById(R.id.idHome);
textWork = (TextView) itemView.findViewById(R.id.idWork);
payType = (TextView) itemView.findViewById(R.id.idPayType);
textPayNote = (EditText) itemView.findViewById(R.id.idPayNoteText);
textDeliveryInst = (EditText) itemView.findViewById(R.id.idDeliInsText);
textDriverNotes = (EditText) itemView.findViewById(R.id.idDriverNotesText);
IsDriverData = (CheckBox) itemView.findViewById(R.id.idCheckBox);
reasons = (Spinner) itemView.findViewById(R.id.idReason);
rg = (RadioGroup) itemView.findViewById(R.id.Radiogroup);
btnSave = (Button) itemView.findViewById(R.id.button);
}
}
protected class LoadingVH extends RecyclerView.ViewHolder {
public LoadingVH(View itemView) {
super(itemView);
}
}
}
PaginationScrollListener.java
public abstract class PaginationScrollListener extends RecyclerView.OnScrollListener {
LinearLayoutManager layoutManager;
public PaginationScrollListener(LinearLayoutManager layoutManager) {
this.layoutManager = layoutManager;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
if (!isLoading() && !isLastPage()) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
&& firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
}
protected abstract void loadMoreItems();
public abstract int getTotalPageCount();
public abstract boolean isLastPage();
public abstract boolean isLoading();
}
I could be wrong, but it looks to me like the only time you call loadNextPage() is when the RecyclerView gets a scroll event. Removing ViewHolders doesn't cause scroll events iirc.
If you register an AdapterDataObserver on the PaginationAdapter, you can listen in on removal/add events and trigger loads when there aren't any tasks left. You could probably do the same thing with some small changes to that commented-out code in remove(Run):
if (runs.isEmpty()) {
func_call.loadNextPage();
}
EDIT:
Btw, you see where you called adapter = new PaginationAdapter(this); in the PaginationActivity.onCreate method? The this here is your PaginationActivity, which means you can assign it to a PaginationAdapter field like so:
/**
* #param context the activity this adapter will appear in
*/
public PaginationAdapter(Context context) {
this.context = context;
runs = new ArrayList<>();
func_call = (PaginationActivity)context;
}
That should be enough to prevent crashes. For better separation of concerns, you'll probably want to switch over to AdapterDataObserver-based logic (I think this is called the open/closed principle? Maybe?).
I have been very meticulous in following this guide to implement pagination in my RecyclerView. Only difference is I'm using Retrofit to retrieve data from the API.
When I open my activity it stops after a while, and when I check Logcat, it says: IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{31a22aa position=4 id=-1, oldPos=2,... This is what my RecyclerView adapter looks like:
public class CuratedSectionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static int VIEW_TYPE_HEADER = 0;
private static int VIEW_TYPE_ITEM = 1;
private static int VIEW_TYPE_LOADING = 2;
private RecyclerView mRecyclerView;
private OnLoadMoreListener mOnLoadMoreListener;
private boolean isLoading;
private int visibleThreshold = 2;
private int lastVisibleItem, totalItemCount;
private List<Object> itemList;
public CuratedSectionAdapter(List<Object> itemList, RecyclerView recyclerView, LinearLayoutManager layoutManager) {
this.itemList = itemList;
this.mRecyclerView = recyclerView;
final LinearLayoutManager linearLayoutManager = layoutManager;
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
totalItemCount = linearLayoutManager.getItemCount();
lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
if (mOnLoadMoreListener != null) {
mOnLoadMoreListener.onLoadMore();
}
isLoading = true;
}
}
});
}
public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) {
this.mOnLoadMoreListener = mOnLoadMoreListener;
}
private class ItemViewHolder extends RecyclerView.ViewHolder {
RecyclerView itemRecyclerView;
CuratedSectionNestedAdapter nestedAdapter;
LinearLayoutManager layoutManager;
ItemViewHolder(View view) {
super(view);
itemRecyclerView = view.findViewById(R.id.recyclerView_nested);
layoutManager = new LinearLayoutManager(view.getContext(), LinearLayoutManager.HORIZONTAL, false);
}
}
private class HeaderViewHolder extends RecyclerView.ViewHolder {
TextView textViewHeader;
Typeface montserratMedium = Typeface.createFromAsset(getApplicationContext().getAssets(), "fonts/Montserrat-Medium.ttf");
HeaderViewHolder(View view) {
super(view);
textViewHeader = view.findViewById(R.id.textView_header);
textViewHeader.setTypeface(montserratMedium);
}
}
private class LoadingViewHolder extends RecyclerView.ViewHolder {
ProgressBar progressBar;
LoadingViewHolder(View itemView) {
super(itemView);
progressBar = itemView.findViewById(R.id.progress_bar);
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == VIEW_TYPE_HEADER) {
HeaderViewHolder viewHolder = (HeaderViewHolder) holder;
CuratedSectionHeader header = (CuratedSectionHeader) itemList.get(position);
viewHolder.textViewHeader.setText(header.getHeaderName());
} else if (holder.getItemViewType() == VIEW_TYPE_ITEM){
ItemViewHolder viewHolder = (ItemViewHolder) holder;
List<CuratedSectionItem> items = (List<CuratedSectionItem>) itemList.get(position);
if (viewHolder.nestedAdapter != null) {
viewHolder.nestedAdapter.setItems(items);
} else {
viewHolder.nestedAdapter = new CuratedSectionNestedAdapter(items);
viewHolder.itemRecyclerView.setLayoutManager(viewHolder.layoutManager);
viewHolder.itemRecyclerView.setAdapter(viewHolder.nestedAdapter);
}
} else if (holder.getItemViewType() == VIEW_TYPE_LOADING){
LoadingViewHolder viewHolder = (LoadingViewHolder) holder;
viewHolder.progressBar.setIndeterminate(true);
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_HEADER) {
return new HeaderViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.main_explore_header_row, parent, false));
} else if (viewType == VIEW_TYPE_ITEM) {
return new ItemViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.main_explore_row, parent, false));
} else if (viewType == VIEW_TYPE_LOADING) {
return new LoadingViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_loading_item, parent, false));
}
throw new RuntimeException("Adapter " + viewType + "not found");
}
#Override
public int getItemCount() {
return itemList.size();
}
#Override
public int getItemViewType(int position) {
if(isHeader(position)) {
return VIEW_TYPE_HEADER;
} else if(isLoading(position)) {
return VIEW_TYPE_LOADING;
} else {
return VIEW_TYPE_ITEM;
}
}
public boolean isHeader(int position) {
return itemList.get(position) instanceof CuratedSectionHeader;
}
public boolean isLoading(int position) {
return position > itemList.size();
}
public void setLoaded() {
isLoading = false;
}
}
It may have something to do with how I constructed LoadingViewHolder, but I think I did that one correctly. On the other hand, this is what the activity looks like:
public class ExploreFeedActivity extends Activity {
private RecyclerView recyclerView;
private CuratedSectionAdapter curatedSectionAdapter;
private LinearLayoutManager layoutManager;
private Sections curated;
int mStart = 0;
int mLimit = 2;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_explore);
curated = new Sections();
curated.loadSections(mStart, mLimit);
recyclerView = findViewById(R.id.recycler_view_main);
layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
}
private class Sections {
ArrayList<CuratedSection> sections = new ArrayList<>();
public Thread[] thread;
private Sections() {}
public void setSections(ArrayList<CuratedSection> sections) {
this.sections = sections;
}
public void setSectionStories(String sectionId, List<CuratedSectionItem> stories) {
for(CuratedSection s : sections){
if(s.id != null && s.id.equals(sectionId)) {
s.stories = stories;
}
}
}
public void loadStories(String sessionKey) {
thread = new Thread[sections.size()];
for( int s = 0; s < sections.size(); s++) {
thread[s] = new Thread(new LoadStories(sessionKey, sections.get(s)));
thread[s].start();
}
for( int f = 0; f < sections.size(); f++) {
try {
thread[f].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
curatedSectionAdapter = new CuratedSectionAdapter(this.getAdapterInfo(), recyclerView, layoutManager);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(curatedSectionAdapter);
curatedSectionAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {
#Override
public void onLoadMore() {
Log.e("haint", "Load More");
curated.sections.add(null);
curatedSectionAdapter.notifyItemInserted(curated.sections.size() - 1);
// Load more data for recyclerview
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
Log.e("haint", "Load Further");
// Remove loading item
curated.sections.remove(curated.sections.size() - 1);
curatedSectionAdapter.notifyItemRemoved(curated.sections.size());
// Load data
curated.loadSections(2, 2);
curatedSectionAdapter.notifyDataSetChanged();
curatedSectionAdapter.setLoaded();
}
}, 5000);
}
});
}
public void loadSections(int start, int limit) {
swipeRefreshLayout.setRefreshing(false);
LoadSections load = new LoadSections(start, limit);
load.run();
}
public List<Object> getAdapterInfo() {
List<Object> list = new ArrayList<>();
for (int i = 0; i < sections.size(); i++) {
CuratedSection section = sections.get(i);
CuratedSectionHeader header = new CuratedSectionHeader();
header.setHeaderName(section.header);
list.add(header);
list.add(section.stories);
}
return list;
}
}
private class LoadSections implements Runnable {
int start, limit;
LoadSections(int start, int limit) {
this.start = start;
this.limit = limit;
}
#Override
public void run() {
SharedPreferences prefs = getSharedPreferences("user_session", MODE_PRIVATE);
final String sessionKey = prefs.getString("session_key", null);
Call<JsonArray> call;
call = TravelersApi.endpoint().getCuratedSections(sessionKey);
call.enqueue(new Callback<JsonArray>() {
#Override
public void onResponse(Call<JsonArray> call, Response<JsonArray> response) {
if(response.code() != 200) {
Toast.makeText(getApplicationContext(), "Cannot load page as of the moment.", Toast.LENGTH_SHORT).show();
return;
}
JsonArray rawSections = response.body();
if(rawSections.size() == 0) {
//TODO: show placeholder
return;
}
for(int i = start; i < start+limit; i++) {
JsonObject jSection = rawSections.get(i).getAsJsonObject();
final CuratedSection section = new CuratedSection();
section.id = jSection.get("id").getAsString();
section.header = jSection.get("section_header").getAsString();
section.isShown = jSection.get("is_shown").getAsBoolean();
section.stories = new ArrayList<>();
curated.sections.add(section);
}
curated.setSections(curated.sections);
curated.loadStories(sessionKey);
}
#Override
public void onFailure(Call<JsonArray> call, Throwable t) {
Log.d("ERROR!", t.toString());
t.printStackTrace();
}
});
}
}
private class LoadStories implements Runnable {
String sessionKey;
CuratedSection section;
LoadStories(String sessionKey, CuratedSection section) {
this.sessionKey = sessionKey;
this.section = section;
}
#Override
public void run() {
Call<JsonArray> subCall;
subCall = TravelersApi.endpoint().getCuratedSectionTopics(sessionKey, section.id);
try {
Response<JsonArray> response = subCall.execute();
if(response.code() != 200) {
Toast.makeText(getApplicationContext(), "Cannot load page as of the moment.", Toast.LENGTH_SHORT).show();
return;
}
JsonArray rawStories = response.body();
if(rawStories.size() == 0) {
//TODO: show placeholder
return;
}
ArrayList<CuratedSectionItem> stories = new ArrayList<>();
for(int i = 0; i < rawStories.size(); i++) {
JsonObject jStories = rawStories.get(i).getAsJsonObject();
JSONObject temp = new JSONObject(jStories.toString());
JsonObject author = jStories.get("author").getAsJsonObject();
CuratedSectionItem story = new CuratedSectionItem();
story.title = jStories.get("title").getAsString();
story.avatar = author.get("profile_photo").getAsString();
stories.add(story);
}
section.stories = stories;
} catch (IOException e) {
Log.d("ERROR!", e.toString());
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
}
It's interesting to note that my RecyclerView adapter curatedSectionAdapter was instantiated in the method loadStories inside the private class Sections. I have tried moving that part to onCreate but then the activity won't display anything. I also suspect that the way I constructed the for loop in the callback in LoadSections has something to do with the crash.