In my application I receive some data from a web server. This data is sent to a RecyclerView. Firstly, all data is displayed fine. However, when I try to refresh it, the data no-longer shows. The data arrives from the web service (as verified using Logcat).
Setting the data
ArrayList<FoodItem> allFood = response.body();
if(foodAdapter != null) {
foodAdapter.swap(allFood);
} else {
foodAdapter = new FoodAdapter(FoodViewForCustomerActivity.this, allFood);
}
photoCollectionView.setAdapter(foodAdapter);
My RecyclerView Adapter
public class FoodAdapter extends RecyclerView.Adapter<FoodAdapter.ViewHolder> {
Context mContext;
ArrayList<FoodItem> foodItems;
public FoodAdapter(Context context, ArrayList<FoodItem> foodItems) {
this.mContext = context;
this.foodItems = foodItems;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.food_item_row, parent, false);
return new ViewHolder(view);
}
public void swap(ArrayList<FoodItem> newFoods) {
foodItems.clear();
foodItems.addAll(newFoods);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
notifyDataSetChanged();
}
}, 500);
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
FoodItem foodItem = foodItems.get(position);
String stringPicture = foodItem.getPhoto();
byte[] decodedPictureStringArray = Base64.decode(stringPicture, Base64.DEFAULT);
Bitmap bitmapPhoto = BitmapFactory.decodeByteArray(decodedPictureStringArray, 0, decodedPictureStringArray.length);
Uri photoUri = getImageUri(mContext, bitmapPhoto);
Picasso.with(mContext).load(photoUri).resize(300, 300)
.centerCrop().into(holder.foodImage);
holder.foodItemNameText.setText(foodItem.getFoodItemName());
holder.foodPriceText.setText(foodItem.getUnitPrice() + " / " + foodItem.getUnitType());
}
public Uri getImageUri(Context inContext, Bitmap inImage) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
inImage.compress(Bitmap.CompressFormat.JPEG, 10, bytes);
String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
return Uri.parse(path);
}
#Override
public int getItemCount() {
return foodItems.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView foodImage;
public TextView foodItemNameText;
public TextView foodPriceText;
public ViewHolder(View itemView) {
super(itemView);
foodImage = (ImageView) itemView.findViewById(R.id.foodPhoto);
foodItemNameText = (TextView) itemView.findViewById(R.id.foodItemNameText);
foodPriceText = (TextView) itemView.findViewById(R.id.foodPriceText);
}
}
}
The RelativeLayout that contains my Toolbar and RecyclerView
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".FoodViewForCustomerActivity">
<include
android:id="#+id/toolbar"
layout="#layout/toolbar"
/>
<android.support.v7.widget.RecyclerView
android:layout_below="#id/toolbar"
android:id="#+id/photoCollectionView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f8f8f8"
android:divider="#null"
android:listSelector="#android:color/transparent"/>
</RelativeLayout>
Sorry, for answering my own question. I solved my question by following solution provided here.Main cause of my problem was that RecyclerView was not calling onCreateViewHolder or onBindView, when i was trying to refresh it.
foodAdapter=new FoodAdapter(FoodViewForCustomerActivity.this,allFood);
Change above line.
Send foodItems arraylist reference in adapter constructor instead of allFood
Correct line is
foodAdapter=new FoodAdapter(FoodViewForCustomerActivity.this,foodItems);
Diagnosis #1
Judging from the comment discussion. It seems like your RecyclerView is being fed the correct data from your source.
However, when the data count is 1, it is hidden underneath other UI elements.
You need to make sure that your RecyclerView is not obscured by other UI elements, such as the Toolbar.
There are multiple ways you can do this.
You could add a marginTop of ?attr/actionBarSize to your RecyclerView.
You could add a layout rule to ConstraintLayout similar to android:layout_constraintTop_toBottomOf ="#id/toolbar".
You could add a layout rule to RelativeLayout similar to android:layout_below="#id/toolbar".
Diagnosis #2
Your code for refreshing data is a little strange:
ArrayList<FoodItem> allFood = response.body();
if(foodAdapter != null) {
foodAdapter.swap(allFood);
} else {
foodAdapter = new FoodAdapter(FoodViewForCustomerActivity.this, allFood);
}
photoCollectionView.setAdapter(foodAdapter);
Normally, you do not set the Adapter every time your data has changed. You set it once on initialization, and then refresh the data with the notify methods.
Instead of what you have, please separate the structure as follows:
Member variable of your FoodViewForCustomerActivity:
private ArrayList<FoodItem> mAllFood = new ArrayList<>();
In onCreate():
foodAdapter = new FoodAdapter(FoodViewForCustomerActivity.this, mAllFood);
photoCollectionView.setAdapter(foodAdapter);
In your data collection method:
ArrayList<FoodItem> food = response.body();
if(foodAdapter != null) {
Log.e(TAG, "Data retrieved (size="+food.size()+"), sending it to Adapter.");
foodAdapter.swap(food);
} else {
Log.e(TAG, "Data retrieved (size="+food.size()+"), but Adapter is null!");
}
Change code like this
public void swap(ArrayList<FoodItem> newFoods){
foodItems.clear();
foodItems.addAll(newFoods);
foodAdapter.notifyDataSetChanged();
}
Fine, you can change too
allFood.clear();
allFood.addAll(newFoods);
notifyDataSetChanged();
Related
I have implemented a RecyclerView and customer Adapter many times, but for some reason I cannot get this one to display any data. I am feeding in data from JSON using retrofit and calling notifyDataSetChanged() once this has been loaded, yet it still remains blank. I have stripped this back to just one text view to try and simplify but still not getting anything. Can anyone see where I am going wrong here?
When I debug, I am getting the List to contain data so I am definitely parsing the data correctly, I just cant get it display in the recycler view. I have even checked the list.size() in the loadTrailerList method and it has data.
My Activity onCreate method:
trailerAdapter = new TrailerAdapter(this);
trailerRecyclerView = findViewById(R.id.trailer_recycler_view);
trailerRecyclerView.setLayoutManager(new LinearLayoutManager(this));
trailerRecyclerView.setAdapter(trailerAdapter);
Retrofit onResponse method:
if (response.body() != null) {
trailers = response.body().getTrailers();
}
trailerAdapter.loadTrailerList(response.body().getTrailers());
My custom adapter:
public class TrailerAdapter extends RecyclerView.Adapter<TrailerAdapter.TrailerViewHolder> {
private final List<Trailer> trailerList = new ArrayList<>();
private final TrailerClickListener listener;
public TrailerAdapter(TrailerClickListener listener) {
this.listener = listener;
}
#NonNull
#Override
public TrailerViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.trailer_list_item, viewGroup, false);
return new TrailerViewHolder(itemView, this);
}
#Override
public void onBindViewHolder(#NonNull TrailerViewHolder trailerViewHolder, int i) {
trailerViewHolder.trailerTitle.setText(trailerList.get(i).getName());
}
#Override
public int getItemCount() {
return trailerList.size();
}
public void loadTrailerList(List<Trailer> trailers) {
this.trailerList.clear();
if (trailers != null) {
trailers.addAll(trailers);
}
notifyDataSetChanged();
}
class TrailerViewHolder extends RecyclerView.ViewHolder {
final TrailerAdapter trailerAdapter;
private final TextView trailerTitle;
private TrailerViewHolder(#NonNull View itemView, TrailerAdapter trailerAdapter) {
super(itemView);
this.trailerAdapter = trailerAdapter;
trailerTitle = itemView.findViewById(R.id.text_view_trailer_title);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
listener.onTrailerClicked(trailerList.get(getAdapterPosition()));
}
});
}
}
}
My List Item XML
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="#+id/text_view_trailer_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play Trailer" />
</LinearLayout>
the recycler view in my activity XML:
<android.support.v7.widget.RecyclerView
android:id="#+id/trailer_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorPrimary"
app:layout_constraintTop_toBottomOf="#+id/trailer_divider">
</android.support.v7.widget.RecyclerView>
I am grateful for anyone that can point me in the right direction
It is because you are sending a list to adapter but, you are not initializing your list which is used in the adapter.
try this.
public void loadTrailerList(List<Trailer> trailers) {
this.trailerList.clear();
if (trailers != null) {
trailerList = trailers;
}
notifyDataSetChanged();
}
Doh! I just realised what I was doing wrong:
In my loadTrailerList() method in my adapter, I was calling:
trailers.addAll(trailers);
instead of:
trailerList.addAll(trailers);
to load the list of items into the actual ArrayList! whoops!
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 have an Activity. I am creating a File which has some data when activity is created. All the files are properly created and data is correctly written.
Following is my code
public class MyRecordingsActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private RecordingsAdapter recordingsAdapter;
private ArrayList<Recording> recordingArrayList;
private File userRecordingFile;
private static final String USER_MIX_DIR = "UserMix";
private String lines[]=new String[]{};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_recordings);
for(int i=0;i<5;i++){
try{
userRecordingFile = new File(createRecordingFiles(), "Recording"+i+".txt");
FileWriter writer = new FileWriter(userRecordingFile);
writer.append("DEF"+i+"\nHIJ "+i);
writer.flush();
writer.close();
}
catch (Exception e){
e.printStackTrace();
}
}
getSupportActionBar().hide();
recordingArrayList=new ArrayList<>();
recyclerView=findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
recyclerView.setHasFixedSize(true);
readFiles();
Toast.makeText(getApplicationContext(),lines[0],Toast.LENGTH_SHORT).show();
Toast.makeText(getApplicationContext(),lines[1],Toast.LENGTH_SHORT).show();
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[0],lines[1]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[2],lines[3]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[4],lines[5]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[6],lines[7]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[8],lines[9]));
recordingsAdapter=new RecordingsAdapter(recordingArrayList);
recyclerView.setAdapter(recordingsAdapter);
}
public File createRecordingFiles() {
File dirRoot = getExternalCacheDir();
File workDir = new File(dirRoot, USER_MIX_DIR);
//Toast.makeText(getApplicationContext(), "HI", Toast.LENGTH_SHORT).show();
if (!workDir.exists()) {
workDir.mkdirs();
File recordingFile = new File(workDir, "Recording File ");
try {
recordingFile.createNewFile();
} catch (IOException e) {
}
}
return workDir;
}
public void readFiles(){
StringBuilder text = new StringBuilder();
BufferedReader br=null;
try {
File dirRoot = getExternalCacheDir();
File workDir = new File(dirRoot, USER_MIX_DIR);
for(int i=0;i<5;i++){
File file = new File(workDir,"Recording"+i+".txt");
br = new BufferedReader(new FileReader(file));
String line;
while ((line = br.readLine()) != null) {
text.append(line);
text.append('\n');
lines=text.toString().split("\n");
}
}
br.close() ;
}catch (IOException e) {
e.printStackTrace();
}
}
}
The Toast which i have written correcly displays DEF0 and HIJ 0, but they are not displayed in the recyclerview. Following is screenshot of the screen
Following is my Adapter Class
public class RecordingsAdapter extends RecyclerView.Adapter<RecordingsAdapter.RecyclerViewHolder> {
public static final int TYPE_HEAD=0;
public static final int TYPE_LIST=1;
private ArrayList<Recording> recordingArrayList;
public RecordingsAdapter(ArrayList<Recording> recordingArrayList) {
this.recordingArrayList = recordingArrayList;
}
#Override
public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
RecyclerViewHolder recyclerViewHolder;
if(viewType == TYPE_LIST){
view= LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_my_recordings,parent,
false);
recyclerViewHolder=new RecyclerViewHolder(view,viewType);
return recyclerViewHolder;
}else if(viewType == TYPE_HEAD){
view= LayoutInflater.from(parent.getContext()).inflate(R.layout.head_layout,parent,
false);
recyclerViewHolder=new RecyclerViewHolder(view,viewType);
return recyclerViewHolder;
}
return null;
}
#Override
public void onBindViewHolder(RecyclerViewHolder holder, int position) {
Recording recording;
if(holder.view_type==TYPE_LIST){
recording=recordingArrayList.get(position);
holder.imageView.setImageResource(recording.getImage_id());
holder.typeTextView.setText(recording.getTitle());
holder.dateTimeTextView.setText(recording.getDatetime());
}else if (holder.view_type == TYPE_HEAD){
holder.typeHeaderTextView.setText("TYPE");
holder.titleHeaderTextView.setText("TITLE");
holder.dateTimeHeaderTextView.setText("DATE/TIME");
}
}
#Override
public int getItemCount() {
return recordingArrayList.size();
}
#Override
public int getItemViewType(int position) {
if(position==0){
return TYPE_HEAD;
}
return TYPE_LIST;
}
public static class RecyclerViewHolder extends RecyclerView.ViewHolder{
int view_type;
ImageView imageView;
TextView typeTextView,dateTimeTextView;
TextView typeHeaderTextView,titleHeaderTextView,dateTimeHeaderTextView;
public RecyclerViewHolder(View itemView, int viewType) {
super(itemView);
if(viewType==TYPE_LIST){
typeTextView=itemView.findViewById(R.id.tv_cell_recording_recording_name);
imageView=itemView.findViewById(R.id.iv_cell_recordings);
dateTimeTextView=itemView.findViewById(R.id.tv_cell_recording_date_time);
view_type=1;
}else if(viewType==TYPE_HEAD){
typeHeaderTextView=itemView.findViewById(R.id.tv_type_head_layout);
titleHeaderTextView=itemView.findViewById(R.id.tv_title_head_layout);
dateTimeHeaderTextView=itemView.findViewById(R.id.tv_date_time_head_layout);
view_type=0;
}
}
}
}
DEf0 and HIJ 0 are not displayed in the recyclerview. I am not able to understand why they are not displaying in my recyclerview. I have no errors in my log. Any help would be greatly appreciated
You are doing it in wrong way....
You are adding five items in recyclerview, but consider first is header.
So this takes your first item as header, and you see other four items.
What you need to do is, pass dummy item as first item and then add your 5 items.
this way you can solve your problem.
You can try this way:
recordingArrayList.add(new Recording(R.drawable.ic_launcher,"", ""));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[0],lines[1]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[2],lines[3]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[4],lines[5]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[6],lines[7]));
recordingArrayList.add(new Recording(R.drawable.ic_launcher,lines[8],lines[9]));
or you can also do it as below:
change Adapter code
#Override
public int getItemCount() {
return recordingArrayList.size() + 1;
}
#Override
public void onBindViewHolder(RecyclerViewHolder holder, int position) {
Recording recording;
if(holder.view_type==TYPE_LIST){
recording=recordingArrayList.get(position - 1);
...
}else if (holder.view_type == TYPE_HEAD){
holder.typeHeaderTextView.setText("TYPE");
holder.titleHeaderTextView.setText("TITLE");
holder.dateTimeHeaderTextView.setText("DATE/TIME");
}
}
but you need to verify for empty list in this way
Your recycler view might be hidden under the toolbar. Give a top margin of 56dp.
android:layout_marginTop="56dp"
Try this your first position is set your Title.
Use this
#Override
public int getItemViewType(int position) {
return TYPE_LIST;
}
Instead of this
#Override
public int getItemViewType(int position) {
if(position==0){
return TYPE_HEAD;
}
return TYPE_LIST;
}
What worked for me is that first I remove from the array any null value (since the first element could be null) and then I add null value again. That way I'm avoiding of duplicating null value. I don't use "if" or any adapter implemented methods so I made my app more efficient.
My code is as follow:
myArray.remove(null); // here I remove any preview added null value as first (0 position) element
myArray.add(0, null); //now I add null as first element
//this way I am avoiding showing content with a null values
I hope this helps someone
The toast is displayed because your card is there, but it's hidden by the toolbar. You can add some margin to the RecyclerView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="schemas.android.com/apk/res/android"
xmlns:app="schemas.android.com/apk/res-auto"
xmlns:tools="schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:layout_marginTop:"?android:attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/recyclerView" />
</RelativeLayout>
If you wants to add Header without passing first blank data from activity, than you need to add blank entry at first position inside your Adapter like this.
Replace this code inside your Adapter:
public RecordingsAdapter(ArrayList<Recording> recordingArrayList) {
this.recordingArrayList = recordingArrayList;
this.recordingArrayList.add(0,null);
}
This question has been asked a few times but those answers doesn't apply to me. I would like a more general answer about what causes this issue generally.
I have a recyclerview in my activity layout. Rows of the recyclerview is a constraint layout with one imageview and textview:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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="50dp"
android:clickable="true">
<ImageView
android:id="#+id/file_icon"
android:layout_width="50dp"
android:layout_height="50dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:text="File"
android:id="#+id/file_name"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toRightOf="#+id/file_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>
I have several items to show in this row structure. I set up the adapter properly. However, when I run my application, textviews that are on the screen doesn't get shown, until I scroll-down and scroll-up again. Basically, in order for those textviews to be shown, they need to be discarded from display and enter again. Here is my adapter code:
public class FileViewAdapter extends RecyclerView.Adapter<FileViewAdapter.Viewholder> {
private Context context;
private List<File> files;
public FileViewAdapter(Context context, List<File> files) {
this.context = context;
this.files = files;
}
public FileViewAdapter(Context context){
this.context = context;
}
#Override
public Viewholder onCreateViewHolder(ViewGroup viewGroup, int i) {
LayoutInflater inflater = LayoutInflater.from(context);
View layout = inflater.inflate(R.layout.file_list_item, viewGroup, false);
return new Viewholder(layout);
}
#Override
public void onBindViewHolder(Viewholder viewholder, int i) {
File file = files.get(i);
if (file.isDirectory()) {
viewholder.fileIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.ic_folder_black_24dp));
viewholder.wholeThing.setOnClickListener(null);
} else {
viewholder.fileIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.ic_insert_drive_file_black_24dp));
viewholder.wholeThing.setOnClickListener(null);
}
viewholder.fileName.setText(file.getName());
}
#Override
public int getItemCount() {
return files.size();
}
public void clear() {
files.clear();
notifyDataSetChanged();
}
public void addAll(List<File> newFiles) {
files.addAll(newFiles);
notifyDataSetChanged();
}
class Viewholder extends RecyclerView.ViewHolder {
View wholeThing;
ImageView fileIcon;
TextView fileName;
public Viewholder(View itemView) {
super(itemView);
wholeThing = itemView;
fileIcon = (ImageView) itemView.findViewById(R.id.file_icon);
fileName = (TextView) itemView.findViewById(R.id.file_name);
}
}
}
EDIT: How I am calling the constructor of adapter on activity.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_file_viewer);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
}
#Override
public void onResume() {
adapter = new Adapter(this, /*A list of File items*/);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
}
The problem from what i see is that the notifyDataSetChanged doesnt ocurr in the UiThread so if run ur setData method like this it will work.
this.runOnUiThread(new Runnable() {
public void run() {
mAdapter.setNewData(newDataListForAdapter);
mAdapter.notifyDataSetChanged();
}
});
As stupid as it might sound, calling this line of code after setting data for recyclerView, helped me for this issue:
recyclerView.smoothScrollToPosition(0)
PS: technologies that I was using that may have something to do with this were: RJava, Retrofit2, NavigationUI, Fragments, LiveData, and Databinding.
Please try this. I hope it will be helpful.
RecyclerView recyclerView;
FileViewAdapter adapter;
List<File> files = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_file_viewer);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
adapter = new FileViewAdapter(this, files);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
}
#Override
public void onResume() {
super.onResume();
files = getFiles();//fetch files
adapter.notifyDataSetChanged();//update UI
}
public List<File> getFiles() {
//fetch files
//...
return files;
}
By the way, I did not find the situation happened to you. There may be some other errors in you code you did not show to us.
if you're setting data on the constructor, it might be that the adapter never gets notified about data inserted. Try to add a notifyDataSetChanged () either at the bottom of the constructor or externally (or call your setData method)
My adapter code:
public class BrandAdapter extends RecyclerView.Adapter<BrandAdapter.BrandViewHolder> {
private static final String TAG = BrandAdapter.class.getSimpleName();
private List<BrandItem> brands;
private Context context;
public BrandAdapter(Context context, List<BrandItem> data) {
this.context = context;
this.brands = data;
}
public void setData(List<BrandItem> dataDownload) {
this.brands = dataDownload;
}
#Override
public BrandViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_item_brand, null);
BrandViewHolder holder = new BrandViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(BrandViewHolder holder, int position) {
BrandItem brandItem = brands.get(position);
String name = brandItem.getName();
int count = brandItem.getCountArticles();
holder.tvName.setText(name);
if (count > 0) {
holder.tvCount.setText("" + count);
} else {
holder.tvCount.setVisibility(View.GONE);
}
}
#Override
public int getItemCount() {
return brands.size();
}
public static class BrandViewHolder extends RecyclerView.ViewHolder {
TextView tvName;
TextView tvCount;
public BrandViewHolder(View itemView) {
super(itemView);
tvName = (TextView) itemView.findViewById(R.id.tv_brand_name);
tvCount = (TextView) itemView.findViewById(R.id.tv_count_article);
}
}
}
Fragment code :
recyclerView = (RecyclerView) view.findViewById(R.id.recycleView);
linearLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayoutManager);
adapter = new BrandAdapter(getActivity(), brands);
recyclerView.setAdapter(adapter);
Data for brands is downloaded from server. After downloaded finished, I just set new data for adapter by this code :
brands = downloadedBrands();
adapter.setData(brands);
adapter.notifyDataSetChanged();
Everything is Ok when data loaded for first time after the download finish. But when I scroll down the recycleview and scroll up again, data for each item is wrong now, all textview tvCount is gone. I do not know why.
Is there any problem from my code ?
Greenrobo's answer is correct but here is an explanation as to WHY you are having this issue.
You are assuming that your view is always set to the default values in your onBindViewHolder method.
The RecyclerView re-uses views that have scrolled off screen and therefore the view you are binding to may have already been previously used (and changed).
You onBindViewHolder method should always set EVERYTHING up. i.e all views reset to the exact values you want and do not assume that because you default an item to visible, it will always be so.
Please make tvCount visible when setting a non-zero count.
if (count > 0) {
holder.tvCount.setText("" + count);
holder.tvCount.setVisibility(View.VISIBLE);
} else {
holder.tvCount.setVisibility(View.GONE);
}
See if this helps.
You told that if count is less than 0, hide the view. What if count is greater than zero ? You are not making the view visible again. So simply make the below changes in your if condition:
if (count > 0) {
holder.tvCount.setText("" + count);
holder.tvCount.setVisibility(View.VISIBLE);
} else {
holder.tvCount.setVisibility(View.GONE);
}