I have a problem with my Recyclerview and I can not find a solution to resolve it. Indeed, my Recyclerview is supposed to display data received from an external database. But sometimes, it displays the data, sometimes not.
This is the screenshots of my app :
When data are displayed
When data are not displayed
And this is my Adapter's code :
public class UpcomingLessonAdapter extends RecyclerView.Adapter<UpcomingLessonAdapter.ViewHolder> {
private List<Lesson> lessons;
private LayoutInflater inflater;
public UpcomingLessonAdapter(List<Lesson> lessons, Context context) {
this.lessons = lessons;
inflater = LayoutInflater.from(context);
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = inflater.inflate(R.layout.upcoming_lesson_list_view, parent, false);
itemView.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT));
return new UpcomingLessonAdapter.ViewHolder(itemView);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
final Lesson lesson = lessons.get(position);
String lessonTopic = "<font color='#E166E1'>" + lesson.getTopicTitle() + "</font>";
String userName = "<font color='#E166E1'>" + lesson.getUserName() + "</font>";
holder.lessonTopic.setText(Html.fromHtml(lesson.getTime(lesson.getTimeStart()) + " " + lessonTopic));
holder.lessonTeacher.setText(Html.fromHtml("avec " + userName));
holder.lessonDuration.setText(lesson.getDuration());
holder.lessonDay.setText(lesson.getDay(lesson.getTimeStart()));
holder.lessonMonth.setText(lesson.getMonth(lesson.getTimeStart()));
}
#Override
public int getItemCount() {
return lessons.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder{
TextView lessonTopic, lessonTeacher, lessonDuration, lessonDay, lessonMonth;
public ViewHolder(View itemView) {
super(itemView);
lessonTopic = (TextView) itemView.findViewById(R.id.lesson_topic_text_view);
lessonTeacher = (TextView) itemView.findViewById(R.id.lesson_teacher_text_view);
lessonDuration = (TextView) itemView.findViewById(R.id.lesson_duration_text_view);
lessonDay = (TextView) itemView.findViewById(R.id.lessson_day_text_view);
lessonMonth = (TextView) itemView.findViewById(R.id.lesson_month_text_view);
}
}
}
And this the method in my fragment when I create the recyclerview :
public void displayUpcomingLessonListView() {
upcomingLessonRecyclerView = (RecyclerView) view.findViewById(R.id.upcoming_lesson);
upcomingLessonAdapter = new UpcomingLessonAdapter(upcomingLessons, getContext());
upcomingLessonLayoutManager = new LinearLayoutManager(getContext());
upcomingLessonRecyclerView.setLayoutManager(upcomingLessonLayoutManager);
upcomingLessonRecyclerView.setItemAnimator(new DefaultItemAnimator());
upcomingLessonRecyclerView.setAdapter(upcomingLessonAdapter);
}
Is there anyone who can give me some advice ? Thanks in advance.
Best regards,
EDIT
This the method by which I fetch my data :
public void getLessonInfos(final int lessonId, final int index, final List<Lesson> lessons) {
Call<JsonResponse> call = service.getLessonInfos(lessonId, user.getEmail(), user.getToken());
call.enqueue(new Callback<JsonResponse>() {
#Override
public void onResponse(Call<JsonResponse> call, Response<JsonResponse> response) {
lessons.get(index).setUserName(response.body().getUserName());
lessons.get(index).setTopicTitle(response.body().getTopicTitle());
lessons.get(index).setTopicGroupTitle(response.body().getTopicGroupTitle());
lessons.get(index).setLevel(response.body().getLevelTitle());
lessons.get(index).setDuration(response.body().getDuration().getHours(), response.body().getDuration().getMinutes());
lessons.get(index).setStatus(response.body().getLessonStatus());
if (upcomingLessons.size() > 0) {
if (lessonId == upcomingLessons.get(upcomingLessons.size() - 1).getLessonId()) {
displayUpcomingLessonListView();
}
}
}
#Override
public void onFailure(Call<JsonResponse> call, Throwable t) {
}
});
}
Use ternary operator like
holder.lessonDuration.setText(lesson.getDuration()!=null?lesson.getDuration():"");
try {
ResponseBody response = call.execute().body();
} catch (IOException e) {
e.printStackTrace();
}
Alright when you invoke methods asynchronously , the network makes a request then , waits for a response. The latency or rather rather time will vary based on your connection speed(bytes per second) or rather your data-set(lots of data ,take time to process). Therefore the reason why your code sometime works is because some time the request and response time is fast enough to update your data in time.The snippet above remove the aforementioned method ,thus divorcing the concept of waiting for response. It makes a request synchronously and gets a response immediately. There is no wait time, the only time waited is the time to get the data from what ever source it is stored, process such data, transform it and send it off.
Related
Recently I started coding my really first android project by using Android Studio 3.1.2.
Inside on one of my fragments, I have a recyclerview, in which I want to show data from a JSON API. For the items I created a custom layout which is intended to be used as a CardView.
I proceeded that far, that I receive my data, but my recyclerview remains empty. Also, if the json object is empty, or the API deosn't respond, the idea was to let the recyclerview automatically add an item, that tells the user that there's no data or the API was not available (would be cool, if I could use the same layout here, I created). This is how my code looks so far:
The raw structure of report_compact_card.xml (embedded in android.support.v7.widget.CardView):
<?xml version="1.0" encoding="utf-8"?><android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp"
app:cardCornerRadius="2dp">
<android.support.constraint.ConstraintLayout
android:id="#+id/linearLayout"
...>
<TextView
android:id="#+id/report_header_textview"
... />
<TextView
android:id="#+id/report_body_textview"
... />
<ImageView
android:id="#+id/report_icon_imageview"
... />
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>
My ReportCompactAdapter:
public class ReportCompactAdapter extends RecyclerView.Adapter<ReportCompactAdapter.ReportCompactViewHolder> {
private Context context;
private ArrayList<Report> reports;
public ReportCompactAdapter(Context context, ArrayList<Report> reports) {
this.context = context;
this.reports = reports;
}
#Override
public ReportCompactViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.report_compact_card, parent, false);
return new ReportCompactViewHolder(view);
}
#Override
public void onBindViewHolder(ReportCompactViewHolder holder, int position) {
//this is where I want to set a "no data" card
if (reports.isEmpty()) {
holder.reportBodyTextView.setText("Keine Meldungen");
holder.reportBodyTextView.setText(":)");
holder.reportIconImageView.setImageResource(R.drawable.ic_report_ok_24dp);
} else {
//here I want to fill my cards with my json data
Report currentReport = reports.get(position);
String currentId = currentReport.getId();
String currentTest = currentReport.getTest();
String currentTOpen = currentReport.getTOpen();
Employee currentEmployee = currentReport.getEmployee();
holder.reportHeaderTextView.setText(currentTest);
holder.reportBodyTextView.setText(currentId + " " + currentTOpen + " " + currentEmployee.getName());
holder.reportIconImageView.setImageResource(R.drawable.ic_report_err_24dp);
}
}
#Override
public int getItemCount() {
return reports.size();
}
public class ReportCompactViewHolder extends RecyclerView.ViewHolder {
public TextView reportHeaderTextView;
public TextView reportBodyTextView;
public ImageView reportIconImageView;
//this is where I try to access my layout
public ReportCompactViewHolder(View itemView) {
super(itemView);
reportHeaderTextView = itemView.findViewById(R.id.report_header_textview);
reportBodyTextView = itemView.findViewById(R.id.report_body_textview);
reportIconImageView = itemView.findViewById(R.id.report_icon_imageview);
}
}
}
Additionally in may OverviewFragment, where I use my recyclerview i'm doing like so:
public class OverviewFragment extends Fragment {
private ArrayList<Report> reports;
private RecyclerView reportRecyclerView;
private ReportCompactAdapter reportCompactAdapter;
private RequestQueue requestQueue;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View fragmentView = inflater.inflate(R.layout.fragment_overview, container, false);
reports = new ArrayList<Report>();
//here I want to set up my recyclerview
reportRecyclerView = fragmentView.findViewById(R.id.report_recyclerview);
reportRecyclerView.setHasFixedSize(true);
reportRecyclerView.setLayoutManager(new LinearLayoutManager(this.getContext()));
//I already set the adapter here to avoid the warning that no adapter is attached
reportRecyclerView.setAdapter(new ReportCompactAdapter(this.getContext(), reports));
//I use volley for Request stuff
requestQueue = Volley.newRequestQueue(this.getContext());
//this guy is intended to fetch my json data
parseJSON();
return fragmentView;
}
private void parseJSON() {
JSONObjectRequest request = new JSONObjectRequest(Request.Method.GET, "myurl.com", null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
try {
JSONArray jsonArray = response.getJSONArray("reports");
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject report = jsonArray.getJSONObject(i);
reports.add(new Report(json));
}
//here I set my adapter after parsing my data
reportCompactAdapter = new ReportCompactAdapter(OverviewFragment.this.getContext(), reports);
reportRecyclerView.setAdapter(reportCompactAdapter);
} catch(JSONException e) {
e.printStackTrace();
}
}
}, new Response.OnErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
}
});
requestQueue.add(request);
}
}
Because of some reason I didn't even get my "no data" card into my recyclerview, neither my filled cards, although "myurl.com" is valid and doesn't throw any error. So my question is, where did I mis a step to successfully squeeze my cards into my recyclerview? Thanks in forward!
You need return atleast 1 item in getItemCount() like below
#Override
public int getItemCount() {
return reports.size()==0?1:report.size();
}
I am getting movie data from the internet like its name, poster etc, but for the genres of the movie, I need to fetch it again from the net. So here's my solution for this problem.
public class MoviesViewAllAdapter extends RecyclerView.Adapter<MoviesViewAllAdapter.MoviesViewHolder> {
private Context mContext;
private List<MovieBrief> mMovies;
public MoviesViewAllAdapter(Context context, List<MovieBrief> movies) {
mContext = context;
mMovies = movies;
}
#Override
public MoviesViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MoviesViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_movie_large,parent,false));
}
#Override
public void onBindViewHolder(MoviesViewHolder holder, int position) {
holder.movieGenreTextView.setText("");
setGenres(holder, mMovies.get(position).getId());
}
#Override
public int getItemCount() {
return mMovies.size();
}
public class MoviesViewHolder extends RecyclerView.ViewHolder {
public TextView movieGenreTextView;
public MoviesViewHolder(View itemView) {
super(itemView);
movieGenreTextView = (TextView) itemView.findViewById(R.id.text_view_genre_movie_card);
}
}
private void setGenres(final MoviesViewHolder holder, Integer movieId) {
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Call<Movie> call = apiService.getMovieDetails(movieId,mContext.getResources().getString(R.string.MOVIE_DB_API_KEY));
call.enqueue(new Callback<Movie>() {
#Override
public void onResponse(Call<Movie> call, Response<Movie> response) {
if(response.code() != 200) return;
List<Genre> genresList = response.body().getGenres();
String genres = "";
for (int i=0;i<genresList.size();i++) {
if(i == genresList.size()-1) {
genres = genres.concat(genresList.get(i).getGenreName());
}
else {
genres = genres.concat(genresList.get(i).getGenreName()+", ");
}
}
holder.movieGenreTextView.setText(genres);
}
#Override
public void onFailure(Call<Movie> call, Throwable t) {
}
});
}
}
But here problem is that when performing fling and going up the recyclerview the genres which are showing is not related to the movie, the genres which are loading are random.
May be because I am loading data on onBindViewHolder and when holder disappears from screen it loads into random holder. Is it so ?
I believe your problem is that you are getting the entire movie list every time you create a view in your list. I am not sure how the server is returning that data but my guess is that every time you call the server there is no guarantee that the data is in the same order. You are getting a random order every time but trying to extract a fixed position of it and that is why the genres are not related.
The problematic line is onBindViewHolder which is called every time a view is created and this function is calling setGenres which is getting a new movie list which is in random order.
You can do two things to fix this issue:
Search for the movie first, find its index and then use that to get the genre. But this is still a very bad design since for a list of N movies, you are calling the server N times.
Get the list first, store it as an ArrayList in your Adapter. Now iterate through it without having to call the server each time
public class MoviesViewAllAdapter extends RecyclerView.Adapter<MoviesViewAllAdapter.MoviesViewHolder> {
.
.
.
List<Movie> list = new ArrayList<>;
public void setList(List movies){
//get data from server before creating the adapter. call this on your adapter and store the data here
this.list = movies;
}
private void setGenres(final MoviesViewHolder holder, Integer movieId){
//iterate the field list instead of calling the server
}
.
.
.
}
The problem with the wrong genres being displayed is caused by the recycling of the view/holder preformed by the RecyclerView . When you scroll instances of MoviesViewHolder and the view are reused so when you trigger the loading of a Movie details a ViewHolder is associated to a MovieRef but by the time the call to get the movie details ends the holder is now assigned to a different movie.
The best thing to do in my opinion is to load the Movie details and cache them in a map for example HashMap<Integer, Movie> mMoviesDetails; when the call to the API ends you can store the Movie object in there.
public void onResponse(Call<Movie> call, Response<Movie> response) {
if(response.code() != 200) return;
mMoviesDetails.put(movieId, response.body());
notifyDataSetChanged();
}
Then in your adapter you can change the onBindViewHolder to something like the below :
#Override
public void onBindViewHolder(MoviesViewHolder holder, int position) {
holder.movieGenreTextView.setText("");
Movie movie = mMoviesDetails.get(mMovies.get(position).getId());
if(movie != null){
/// set data to holder
}else {
//load data from network
loadGenres(mMovies.get(position).getId());
}
}
This is just sample code based on your current implementation, generally speaking I would not load this data inside an Adapter but delegate this type of tasks to a dedicated API class that can store the data in a database like Realm for example instead of using a map.
I'm trying to get JSON data to display in a RecyclerView list, but whenever I try to make the call it seems that the RecyclerView comes up empty. I have it set up so that I get the API service/manager in onCreate prior to the configViews() method which I wrote. Am I making the call too early? I thought the problem would be to create the views/adapter prior to the call, but it doesn't seem to be making a difference.
This is the code for the Retrofit call:
listCall.enqueue(new Callback<List<Character>>() {
#Override
public void onResponse(Call<List<Character>> call, Response<List<Character>> response) {
if(response.isSuccessful()){
List<Character> characterList = response.body();
Log.v(TAG, response.toString());
for (int i = 0; i < characterList.size(); i++){
Character character = characterList.get(i);
character.setName(characterList.get(i).getName());
character.setDescription(characterList.get(i).getDescription());
characterAdapter.addCharacter(character);
}
configViews();
characterAdapter.notifyDataSetChanged();
}
else {
int sc = response.code();
}
}
#Override
public void onFailure(Call<List<Character>> call, Throwable t) {
}
});
And that configViews() method is here, albeit simpler than when I first started (I have been moving bits around to test whether they will affect inflation of the RecyclerView):
private void configViews() {
characterAdapter = new CharacterAdapter(this);
recyclerView.setAdapter(characterAdapter);
}
EDIT: Thank you all for your replies! As requested here is the Adapter class:
public class CharacterAdapter extends
RecyclerView.Adapter<CharacterAdapter.Holder> {
private static final String TAG = CharacterAdapter.class.getSimpleName();
private final CharacterClickListener clickListener;
private List<Character> characters;
//Constructor for CharacterAdapter.
public CharacterAdapter(CharacterClickListener listener){
characters = new ArrayList<>();
clickListener = listener;
}
//Inflates CardView layout
#Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
View row =
LayoutInflater.from(parent.getContext()).inflate(R.layout.row_item, parent,
false);
return new Holder(row);
}
#Override
public void onBindViewHolder(Holder holder, int position) {
Character currentCharacter = characters.get(position);
holder.name.setText(currentCharacter.getName());
holder.description.setText(currentCharacter.getDescription());
//Picasso loads image from URL
Picasso.with(holder.itemView.getContext())
.load("http://gateway.marvel.com/"+
currentCharacter.getThumbnail()).into(holder.thumbnail);
}
#Override
public int getItemCount() {
return characters.size();
}
public void addCharacter(Character character) {
//Log.d(TAG, character.getThumbnail());
characters.add(character);
notifyDataSetChanged();
}
public Character getSelectedCharacter(int position) {
return characters.get(position);
}
public class Holder extends RecyclerView.ViewHolder implements
View.OnClickListener{
//Holder class created to be implemented by adapter.
private ImageView thumbnail;
private TextView name, description;
public Holder(View itemView) {
super(itemView);
thumbnail = (ImageView)
itemView.findViewById(R.id.character_thumbnail);
name = (TextView) itemView.findViewById(R.id.character_name);
description = (TextView)
itemView.findViewById(R.id.character_description);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
clickListener.onClick(getLayoutPosition());
}
}
public interface CharacterClickListener {
void onClick(int position);
}
}
In principle you have done the right thing. You can instantiate the views before and fire a fetch request. Once you receive the response, you can notify the adapter that the datatset has changed and the adapter will refresh your views.
The only correction here is, you need to call your configViews(); before the for loop. So your code should be like -
configViews();
for (int i = 0; i < characterList.size(); i++){
Character character = characterList.get(i);
character.setName(characterList.get(i).getName());
character.setDescription(characterList.get(i).getDescription());
characterAdapter.addCharacter(character);
}
characterAdapter.notifyDataSetChanged();
Because right now what is happening is you add all your data using add character and then after that you again call config views which reinitialises your adapter at
characterAdapter = new CharacterAdapter(this);
Hence the empty views.
PS: I don't know your adapter's addCharacter method but i am hoping it is doing the right job. If it still doesn't work let me know and then add your addCharacter code as well.
Based on code the snippets, in configViews() method you are creating new instance of CharacterAdapter. The following code snippets will work.
private void configViews() {
//characterAdapter = new CharacterAdapter(this);
recyclerView.setAdapter(characterAdapter);
}
You should leave the call configViews() call in onCreate but move the rest of it into onResume. It'll achieve following:
Ensure views are ready to display data
Refresh data if your app was brought to background and re-opened
I have a recycler and inside of it there are cardviews where I fetch information from a REST service, I'm trying to implement an endless scroll, It's supposed that user will see 10 cardviews every time he scrolls down until there are no more cardviews to show, How can I achieve that?
I've seen a few examples but none of them really helped me about how to do it. I don't even know what I need to put in adapter.class or in my Fragment.class because I don't understand how to implement that, it would be great if someone could tell me the correct way to implement the infinite scroll in my code...
Thanks in advance.
MainAdapter.class
public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder> implements View.OnClickListener
{
private ArrayList<Business> businessList;
private Activity activity;
private int layoutMolde,idb;
public MainAdapter(Activity activity, ArrayList<Business> list, int layout)
{
this.activity = activity;
this.businessList = list;
layoutMolde = layout;
}
#Override
public MainAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.main_row, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position)
{
holder.mTitle.setText(businessList.get(position).getBusiness_name());
holder.number_rating.setText(businessList.get(position).getRating().toString());
Glide.with(activity).load(businessList.get(position).getLogo_url_string()).into(holder.mImg);
}
#Override
public int getItemCount() {
return businessList.size();
}
#Override
public void onClick(View v)
{
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTitle;
public ImageView mImg;
public ImageView logo;
public RatingBar main_rating;
public TextView number_rating;
public ViewHolder( View itemView)
{
super(itemView);
mTitle = (TextView) itemView.findViewById(R.id.nom_business_main);
number_rating = (TextView) itemView.findViewById(R.id.number_rating);
mImg = (ImageView) itemView.findViewById(R.id.img_main);
main_rating=(RatingBar) itemView.findViewById(R.id.rating_main);
main_rating.setRating((float)1);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v)
{
Intent in = new Intent(v.getContext(), BusinessPremium.class);
int position = getAdapterPosition();
idb = businessList.get(position).getId();
in.putExtra("no", idb);
v.getContext().startActivity(in);
}
});
}
}
}
FeedsFragment.class
public class FeedsFragment extends Fragment
{
private ArrayList<Business> arrayBusiness,arrayBasics;
private Gson gson;
private static final Type BUSINESS_TYPE = new TypeToken<ArrayList<Business>>() {}.getType();
private RecyclerView.LayoutManager mLayoutManager;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View android = inflater.inflate(R.layout.fragment_feeds, container, false);
if (!internetConnectionCheck(FeedsFragment.this.getActivity()))
{
Toast.makeText(getApplicationContext(), "Error de Conexión", Toast.LENGTH_LONG).show();
}
new RequestBase(getActivity()) {
#Override
public JsonObject onHttpOk(JsonObject response) throws JSONException {
JsonObject objeto, pagination_details = null, details, premium_img;
JsonArray data;
if (getActivity() == null)
return response;
if (response.get("pagination") == null)
{
objeto = response;
} else {
objeto = response;
pagination_details = response.get("pagination").getAsJsonObject();
data = objeto.get("data").getAsJsonArray();
gson = new Gson();
arrayBusiness = gson.fromJson(data, BUSINESS_TYPE);
Log.d("size", String.valueOf(arrayBusiness.size()));
FeedsFragment.this.getActivity().runOnUiThread(new Runnable()
{
#Override
public void run()
{
RecyclerView recycler = (RecyclerView) FeedsFragment.this.getActivity().findViewById(R.id.recycler_main);
MainAdapter adapter = new MainAdapter(getActivity(), arrayBusiness, R.layout.main_row);
recycler.setNestedScrollingEnabled(false);
mLayoutManager = new GridLayoutManager(FeedsFragment.this.getActivity(), 2);
recycler.setLayoutManager(mLayoutManager);
recycler.setAdapter(adapter);
GifTextView loading = (GifTextView)FeedsFragment.this.getActivity().findViewById(R.id.loading);
TextView loadingText = (TextView)FeedsFragment.this.getActivity().findViewById(R.id.loadingText);
loading.setVisibility(View.GONE);
loadingText.setVisibility(View.GONE);
}
});
}
if (pagination_details.isJsonNull()) {
Log.d("Paginacion", pagination_details.toString());
}
return objeto;
}
#Override
public void onHttpCreate(JsonObject response) throws JSONException
{
}
#Override
public void onHttpUnprocessableEntity(JsonObject response) throws JSONException
{
this.cancel(true);
final String error = response.get("errors").toString();
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(getActivity().getApplicationContext(), error, Toast.LENGTH_LONG).show();
}
});
}
}.execute("businesses/premiums", "GET");
return android;
}
}
you can refresh using SwipeRefreshLayout in android to refresh and in the on refresh override method call your api
note:put your API call request in a method and call that method inyour onRefresh method of SwipeRefreshLayout
When writing RecyclerView.Adapter, you anyway need to provide the getItemCount method that returns the correct number of items (may be large). RecyclerView will call on its own initiative the onBindViewHolder(holder, position) method of this adapter. All you need is to provide functionality of retrieving data, relevant to this position. There is no difference at all, if your list is smaller than screen, slightly larger than screen or Integer.MAX_VALUE size. RecyclerView will take care not to fetch/allocate too much extra items.
You do not need to implement scroll listeners or otherwise explicitly handle the scrolling.
The only tricky part is that you may need to take a long action like server call to get some items. Then just return uninitialized holder (empty view) on the first invocation and start fetching the needed row in the background thread. When you have it, call notifyDataSetChanged or notifyItemRangeChanged, and RecyclerView will take care to update itself.
For performance reasons I would strongly recommend to update content in chunks of the fixed size rather than sending individual server request per every row displayed. For some public servers like Google Books this is clearly a requirement, as they have quota limits per request.
If you need to view the full source code on how this possibly could be implemented, there is an open source project here in GitHub.
Make a static boolean variable named "ready" and initialize it to false.
Add the if ready condition in the onLoadMore method as below.
public boolean onLoadMore(int page, int totalItemsCount) {
if (ready) {
//load more from API
}
return false;
}
set ready to true in onBindViewHolder when the position of item is last.
Here is a way that a colleague of mine introduced. we worked in it together and i implemented it successfully with no issues. I wanted to give back to anyone having this issue.
in your adapter you need to set the count to be infinite size and then when you want the position of an item you should use val loopPos = position % dataSource.size anytime you need the position. lets take a look how this can be done in a recyclerView adapter but could also be applied to FragmentStatePagerAdapter.
class InfiniteLoopingHorizontalRecyclerViewAdapter(var dataSource: ArrayList<String>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflatedView: View = LayoutInflater.from(parent.context)
.inflate(R.layout.your_finite_layout, parent, false)
return ItemHolder(inflatedView)
}
override fun getItemCount(): Int {
return Integer.MAX_VALUE //***** this should be high enough - wink wink ******
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
//****** this is critical here when you need the position use the loopPos ****/
val loopPos = position % dataSource.size
(holder as? ItemHolder)?.bind(dataSource[loopPos], loopPos)
}
inner class ItemHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(myString: String, position: Int) = with(itemView) {
myTextView.setText(myString)
}
}
}
how it works:
lets say your dataSource size is 50 but your position is at 51 that means the following: 51%50 . which gives you position 1. and lets say again your position is 57 and again your dataSource size is still 50. that means your position is 7. so to be clear, anytime you need a infinite affect you can use the modules of the position and the dataSource size.
ps:
lets go crazy and say we scrolled to position 11323232323214 then that means 11323232323214%50 = 14 so its position 14 in your datasource that will be used. you can then polish off the affect with wrapping your recyclerview in a SnapHelper class
You can add a scrollListener to your recyclerview.
Check a similar answer here
And the main SO post here
Where, the scrollListener will check where exactly are you in the recyclerview and based on some logic (which you can flexibly write) make a second call!
I am populating OrderHistory by retrieving data from Server in my app. Here I need to populate listitem depends on OrderId. If OrderId repeats I need to hide the LinearLayout which is marked in green in screenshot. Otherwise the Layout has to be usual view.Please help me.
MyAdapter Class:
public class MyOrderAdapter extends RecyclerView.Adapter<MyOrderAdapter.ViewHolder> {
Context mContext;
List<CartRes> mOrderList = new ArrayList<>();
private ImageLoader imageLoader;
public MyOrderAdapter(Context context, List<CartRes> orderList) {
mContext = context;
mOrderList = orderList;
Log.e("Json Adapter", "" + mOrderList);
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_order_history, parent, false);
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.cartResOrder = mOrderList.get(position);
imageLoader = CustomVolleyRequest.getInstance(mContext).getImageLoader();
String IMAGE_URL = "http://" + Config.IMAGE_URL + holder.cartResOrder.ORDER_IMAGE;
Log.e("Image URL", IMAGE_URL + " " + holder.cartResOrder.ORDER_IMAGE);
imageLoader.get(IMAGE_URL, ImageLoader.getImageListener(holder.orderImage, 0, 0));
holder.orderImage.setImageUrl(IMAGE_URL, imageLoader);
holder.txtOrderId.setText(holder.cartResOrder.ORDER_ID);
holder.txtOrderProduct.setText(holder.cartResOrder.ORDER_PRODUCT);
holder.txtorderedDate.setText(holder.cartResOrder.ORDER_DATE);
holder.txtDeliveredDate.setText(holder.cartResOrder.ORDER_DELIVERY_DATE);
}
#Override
public int getItemCount() {
return mOrderList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView txtOrderId, txtOrderProduct, txtorderedDate, txtDeliveredDate;
Button butDetail, butRemove;
NetworkImageView orderImage;
CartRes cartResOrder;
public ViewHolder(View itemView) {
super(itemView);
txtOrderId = (TextView) itemView.findViewById(R.id.orderId);
txtOrderProduct = (TextView) itemView.findViewById(R.id.orderProduct);
txtorderedDate = (TextView) itemView.findViewById(R.id.orderedDate);
txtDeliveredDate = (TextView) itemView.findViewById(R.id.orderDeliveredDate);
butDetail = (Button) itemView.findViewById(R.id.orderViewDetail);
butRemove = (Button) itemView.findViewById(R.id.orderRemove);
orderImage = (NetworkImageView) itemView.findViewById(R.id.OrderproductImage);
}
}
}
UPDATE:
public void onBindViewHolder(ViewHolder holder, int position) {
holder.cartResOrder = mOrderList.get(position);
imageLoader = CustomVolleyRequest.getInstance(mContext).getImageLoader();
String IMAGE_URL = "http://" + Config.IMAGE_URL + holder.cartResOrder.ORDER_IMAGE;
Log.e("Image URL", IMAGE_URL + " " + holder.cartResOrder.ORDER_IMAGE);
imageLoader.get(IMAGE_URL, ImageLoader.getImageListener(holder.orderImage, 0, 0));
holder.orderImage.setImageUrl(IMAGE_URL, imageLoader);
holder.txtOrderId.setText(holder.cartResOrder.ORDER_ID);
holder.txtOrderProduct.setText(holder.cartResOrder.ORDER_PRODUCT);
holder.txtorderedDate.setText(holder.cartResOrder.ORDER_DATE);
holder.txtDeliveredDate.setText(holder.cartResOrder.ORDER_DELIVERY_DATE);
if (position > 0 && mOrderList.get(position).ORDER_ID == mOrderList.get(position - 1).ORDER_ID) {
//make sure it is not the first one, and make sure it has the same ID as previous.
holder.OrderLinear.setVisibility(View.GONE); //hide it, you need to set the reference first.
holder.txtOrderId.setVisibility(View.GONE);
}
}
One way you could do it:
Have a new holder variable that refers to your LinearLayout you want to hide.
In your onBindViewHolder()
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
//setup everything else above...
if (position > 0 && mOrderList.get(position).ORDER_ID == mOrderList.get(position - 1).ORDER_ID){
//make sure it is not the first one, and make sure it has the same ID as previous.
holder.thelinearlayout.setVisibility(View.GONE); //hide it, you need to set the reference first.
}else holder.thelinearlayout.setVisibility(View.VISIBLE);
//if somehow order gets changed.(e.g. previous row is deleted).
}
EDIT:
I'm assuming that the data inside is sorted by ORDER_ID, so there won't be a case where there are two elements with the same ORDER_ID that is not next to each other.
You might want to sort them based on ORDER_ID before passing the list into adapter.
EDIT2:
Added else condition as suggested in the comment.
The screenshot does not tell that but if your view is RecyclerView or ListView then I'd preprocess the data prior feeding it to the adapter (i.e. by wrapping your model into another class with additional attributes).
Alternatively you can try checking previous row (by fetching it from your data source or adapter) while populating current one and adjust the view
maybe just add field boolean repeatOrderId to your CartRes class and in constructor do some for loop and flag all items? you may check also in runtime, but you have pretty fixed list, preparing in construtor this flag is better for performance
for(int i=0; i<mOrderList.getCount(); i++){
CartRes cr = mOrderList.get(i);
cr.repeatOrderId=true;
for(int j=0; j<i; j++){
if(cr.ORDER_ID==mOrderList.get(j).ORDER_ID){
cr.repeatOrderId=false; //default true
break;
}
}
}
then inside onBindViewHolder check this flag and setVisibility(cr.repeatOrderId); for desired layout