Strict mode in recycler view adapter -> inflate method - android

I've activated strict mode in my APP, and I find a strange thing:
when I create a new adapter for my recycler view, the system logs a [StrictMode policy violation; ~duration=352 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=1114143 violation=2].
This happens on my onCreateViewHolder method, that inflates the view, in this way:
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull final ViewGroup parent, final int viewType)
{
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.cliente_centro_list_item, parent, false));
}
and my view holder is:
private static class ViewHolder extends RecyclerView.ViewHolder
{
private final View viewCompleta;
private final TextView tvMissioni;
private final TextView tvUltimoUsato;
private final TextView tvOrdiniCarico;
private final TextView tvInfoSecondaria;
private final TextView tvRagioneSociale;
private final LinearLayout boxContatori;
private final TextView tvOrdiniSpedizione;
private final TextView tvOrdiniProduzione;
private final TextView tvOrdiniTrasferimento;
/**
* Costruttore.
* #param view gestore view.
*/
private ViewHolder(final View view)
{
super(view);
viewCompleta = view;
tvMissioni = view.findViewById(R.id.tvMissioni);
tvUltimoUsato = view.findViewById(R.id.tvUltimo);
boxContatori = view.findViewById(R.id.boxContatori);
tvOrdiniCarico = view.findViewById(R.id.tvOrdiniCarico);
tvRagioneSociale = view.findViewById(R.id.tvRagioneSociale);
tvInfoSecondaria = view.findViewById(R.id.tvInfoSecondaria);
tvOrdiniSpedizione = view.findViewById(R.id.tvOrdiniSpedizione);
tvOrdiniProduzione = view.findViewById(R.id.tvOrdiniProduzione);
tvOrdiniTrasferimento = view.findViewById(R.id.tvOrdiniTrasferimento);
}
}
how should i create my view if not like this? I also checked the android manual, and the recommended way is this.
final expected result: no strict violation detected!

I think that your code is perfectly fine and nothing can be optimized, at the end there are some cases in which some data needs be loaded by the system and it's perfectly ok. There is also a documentation note about that:
But don't feel compelled to fix everything that StrictMode finds.In
particular, many cases of disk access are often necessary during the
normal activity lifecycle. Use StrictMode to find things you did by
accident. Network requests on the UI thread are almost always a
problem, though.
https://developer.android.com/reference/android/os/StrictMode

Related

Can I make a recyclerview without an xml layout for the ViewHolder?

I'm putting a RecyclerView in my android app. So, like in all the documentation I've seen for recycler views, I have an Adapter class with a ViewHolder class defined inside it. That code is shown below (in PointChangeAdapter.java).
However, there is one difference between my code and most of the tutorials I've seen;
I do not have an xml file defining a layout for my ViewHolder. At least that's what I'm trying to get working.
It seems I shouldn't need to define a custom layout, because the elements of my RecyclerView are just TextViews. This could probably be done with a ListView, but I like the flexibility of RecyclerView, and also just want to understand if it is possible to get what I have here working. That is, with the recycler and without the xml layout.
To accommodate the lack of xml layout, I have modified my Adapter's onCreateViewHolder method.
Where a typical example would use a layout inflater, e.g.
#Override
public PointChangeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final TextView textView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_viewholder_xml_layout, parent, false);
return new PointChangeViewHolder(textView);
}
I instead have
#Override
public PointChangeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final TextView textView = new TextView(parent.getContext());
parent.addView(textView);
return new PointChangeViewHolder(textView);
}
That is, instead of calling inflate on an xml layout and specifying the RecyclerView parent,
I create a new TextView and add it directly to the RecyclerView parent via addView.
Only problem is this does not work.
It gives me a "ViewHolder views must not be attached when created" error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mherzl.pointcounter, PID: 5253
java.lang.IllegalStateException: ViewHolder views must not be attached when created. Ensure that you are not passing 'true' to the
attachToRoot parameter of LayoutInflater.inflate(..., boolean
attachToRoot)
at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7080)
...
I have also tried removing the parent.addView(textView); line entirely,
making my example almost identical to this example,
which, like I am trying to do, does not specify an xml layout.
When I run my code without the addView line, I do not get the error,
but in that case the text still does not show up in the recycler views.
So something appears wrong then as well.
Also, I feel that the addView line should be needed;
since the inflater appears to attach the TextView to the RecyclerView parent,
it seems that what I replace the inflater with should attach to it as well.
Is it possible to make a RecyclerView using TextViews as elements
instead of an element defined by a custom xml layout?
Here is my code:
PointChangeAdapter.java:
package com.mherzl.pointcounter;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class PointChangeAdapter extends RecyclerView.Adapter<PointChangeAdapter.PointChangeViewHolder> {
private List<Integer> recentChanges;
public PointChangeAdapter(List<Integer> recentChanges) {
this.recentChanges = recentChanges;
}
#Override
public int getItemCount() {
if (recentChanges == null) {
return 0;
}
return recentChanges.size();
}
#Override
public PointChangeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final TextView textView = new TextView(parent.getContext());
parent.addView(textView);
return new PointChangeViewHolder(textView);
}
#Override
public void onBindViewHolder(PointChangeViewHolder holder, int position) {
final int pointChange = recentChanges.get(position);
String text = Integer.toString(pointChange);
holder.setText(text);
}
public static class PointChangeViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
public PointChangeViewHolder(TextView textView) {
super(textView);
this.textView = textView;
}
public void setText(String text) {
textView.setText(text);
}
}
}
And here's what runs it -- in the onCreateView of the fragment using the RecyclerView:
RecyclerView pointChangeRecycler =
(RecyclerView) view.findViewById(R.id.point_changes_recycler);
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
pointChangeRecycler.setLayoutManager(layoutManager);
List<Integer> myList = new ArrayList<>();
myList.add(1); myList.add(2); myList.add(3);
PointChangeAdapter pointChangeAdapter = new PointChangeAdapter(myList);
pointChangeRecycler.setAdapter(pointChangeAdapter);
Yes. onCreateViewHolder just returns a ViewHolder with a ViewGroup (possibly even just a View) as the root view.
Your problem is that you added it to a parent. You don't want to do that. The RecyclerView will end up being its parent. You should just be creating it and returning a holder with that view as the root view.
Yes, even though the parent passed in is probably (although not necessarily) also the RecyclerView. The layout manager of the recycler view does the adding. That's why the last parameter of your inflate() call is false- to prevent inflate from adding it to a parent.

how to change specific items in recycler view?

I want to remove textview from specific items in recycler view. I wrote the below code to do that
#Override
public void onBindViewHolder(#NonNull final Myholder holder, final int possion) {
final String n = names.get(possion);
if(possion==3){
holder.textView.setVisibility(View.GONE);}}
but this changes all items and i want to make gone only position number 3 item view.
this is my view holder
public static class Myholder extends RecyclerView.ViewHolder {
TextView textView;
public Myholder(#NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textfolder);
}
}
what should i do?
Always remember that RecyclerView recycles view holders. That is, the same ViewHolder instance will be re-used for different views. This means that it is almost always a bad idea to have an if statement that modifies a view withouth having a corresponding else.
So, try this instead:
if (possion == 3) {
holder.textView.setVisibility(View.GONE);
} else {
holder.textView.setVisibility(View.VISIBLE);
}
Note also that just checking the position argument is not necessarily a good idea. If you use notifyItemInserted() or notifyItemRemoved(), this can lead to problems.
It would be better to set something about the item at that position to indicate that its text should not be shown.

Accessing navigation header elements using ButterKnife

I have a class which handles character selection from a RecyclerView and everything works, but I want to update text of the elements in the NavigationView header with the right information. So far I've been trying to use ButterKnife to solve this, but with no success. However, I've been able to make it work in this way:
private ImageView mImageView;
private TextViewTitle mTextViewName;
private TextViewRegular mTextViewTitle;
private static View mHeaderView;
public void setHeaderView(View headerView) {
mHeaderView = headerView;
selectedCharacterInstance.setHeaderViewElements();
}
private void setHeaderViewElements() {
mImageView = mHeaderView.findViewById(R.id.selected_character_info1);
mTextViewName = mHeaderView.findViewById(R.id.selected_character_info2);
mTextViewTitle = mHeaderView.findViewById(R.id.selected_character_info3);
}
I pass the headerView from the MainActivity. I don't like this approach, but I might be wrong since I am fairly new to Android programming. Is this the right approach? Is there a way to solve this using ButterKnife? (I tried ButterKnife but the ImageView and the TextViews were always null)
I use also Butter Knife for my navigation header. For the header, I create a view holder:
protected static class HeaderViewHolder {
#BindView(R.id.user_name)
protected TextView mUserNameTxt;
#BindView(R.id.user_email)
protected TextView mUserEmailTxt;
HeaderViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
Then in my activity's onCreate method:
View header = mNavigationView.getHeaderView(0);
mHeaderViewHolder = new HeaderViewHolder(header);
mHeaderViewHolder.mUserEmailTxt.setText(userEmail);
This allows me to use mHeaderViewHolder like any other RecyclerView holder.

RecyclerView adapter taking wrong values during scrolling

I have read several questions on stackoverflow addressing this problem but I could not solve my problem. The recyclerview shows webviews. My issue is that the adapter, initially, load the right values (below image):
but when I scroll down, and scroll up to first position again, wrong values are displayed in webview (below image):
I think this problem may originate from previously started loads into the webview, but I don't have any idea about handling this behavior. Here is related parts of my code:
/* in my notation, pg is equivalent to page.*/
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_ebook_list, container, false);
mEbookRecyclerView = (RecyclerView) view.findViewById(R.id.ebook_recycler_view);
mLayoutManager = new LinearLayoutManager(getActivity());
mEbookRecyclerView.setLayoutManager(mLayoutManager);
updateUI();
return view;
}
#Override
public void onResume() {
super.onResume();
updateUI();
}
private void updateUI() {
if (mAdapter == null) {
EbookLab ebookLab = EbookLab.get(getActivity());
List<String> pgs = ebookLab.getPgs();
mAdapter = new EbookAdapter(pgs);
mEbookRecyclerView.setAdapter(mAdapter);
}
}
/* ******codes for preparation of menus which I have ignored *******/
private class EbookHolder extends RecyclerView.ViewHolder {
private String mPg;
public EbookHolder(LayoutInflater inflater, ViewGroup container) {
super(inflater.inflate(R.layout.list_item_ebook, container, false));
mListWebView = (WebView) itemView.findViewById(R.id.list_web_View);
mListWebView.getSettings().setLoadWithOverviewMode(true);
}
public void bindEbook(String pg) {
mPg = pg;
mListWebView.loadDataWithBaseURL("file:///android_asset/", "Stack Over Flow " + String.valueOf(glbPos), "text/html", "UTF-8", null);
}
}
private class EbookAdapter extends RecyclerView.Adapter<EbookHolder> {
private List<String> mPgs;
public EbookAdapter(List<String> pgs) {
mPgs = pgs;
}
#Override
public EbookHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
return new EbookHolder(layoutInflater, parent);
}
#Override
public void onBindViewHolder(EbookHolder holder, int position) {
String pg = mPgs.get(position);
glbPos = position;//A global variable for position
holder.bindEbook(pg);
}
#Override
public int getItemCount() {
return mPgs.size();
}
public void setPgs(List<String> pgs) {
mPgs.clear();
mPgs.addAll(pgs);
notifyDataSetChanged();
}
}
Any helps is appreciated.
Update:In my code , I defined mListWebView as a global variable. But it should be defined inside private class EbookHolder extends RecyclerView.ViewHolder.
Then I removed bindEbook(String pg) method and directly updated mListWevView
inside public void onBindViewHolder(EbookHolder holder, int position):
holder.mListWebView.loadDataWithBaseURL("file:///android_asset/", "Stack Over Flow " + String.valueOf(glbPos), "text/html", "UTF-8", null);
My Question >> Does calling bindEbook(String pg) takes longer time compared to scrolling recyclerview or it is related to Android Architecture??
Please try few things:
1) put log after String pg = mPgs.get(position); and see, which pg value is after that line;
In bindEbook():
2) put log to see which pg value is incoming;
3) put mListWebView.clearHistory() or mListWebView.loadUrl("about:blank") before mListWebView.loadDataWithBaseURL() just to see is your webview really loading new page
Also - try to replace webview with simple textView - you will see, is problem in webview or in adapter.
I had a very similar problem which I solved after extensive debugging. I hope this can help you:
In my project, I am showing a horizontal RecyclerView within a vertical RecyclerView. The code can be seen here and here and I explained what I do on my website here.
I noticed that when scrolling down the outer (vertical) RecyclerView, the values of the inner (horizontal) RecyclerView where messed up. That is similar to your problem. Set some console log commands (Log.v) here and there in my inner RecyclerView adapter (called MeasurementsAdapter), one specifically in the inner view holder class, where the data is bound:
public class MeasurementsViewHolder extends RecyclerView.ViewHolder {
[code omitted here]
public MeasurementsViewHolder (View itemView) {
super(itemView);
[code omitted here]
}
public void bindMeasurement (String name, double value, String unit, int color) {
mNameView.setText(name);
mValueView.setText(String.valueOf(Math.round(value)));
mUnitsView.setText(unit);
mBoxLayout.setBackgroundColor(color);
// Log.v(TAG, name + " " + value); <-- here I was logging
}
I was observing that when I was scrolling at some point this "bindMeasurement" method was no longer executed.
My guess is that RecyclerView was "recycling" some random data. This is not what I wanted and my result looked like yours' very much.
So, after I read the notifyDataSetChanged-method in the RecyclerView documentation. In the adapter of my outer RecyclerView (called Stationsadapter) I modified the binding method in the inner ViewHolder class and execute the adapter.notifyDataHasChanged method there (see line 84 in 1)
public class StationsViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {public TextView mStationInfoView;
private MeasurementsAdapter mMeasurementsAdapter;
/**
* Constructor of inner class StationsViewHolder
* #param itemView
*/
public StationsViewHolder(View itemView) {
super(itemView);
[code omitted here]
}
/**
* Bind weather station to view
* #param station is a class containing data of one weather station
*/
public void bindStation (Station station) {
[code omitted here]
mMeasurementsAdapter.notifyDataSetChanged();
}
Now my RecyclerViews show the right data. I very much hope this helps you and others.
Cheers
Ben

Is there a way to use a ViewStub inside a RecyclerView?

i'm new to android,
I've been working on a project, and in my news feeds page, I'm trying to include a modular feed RecyclerView, which shows a question with different answer forms, varrying according to the Question type. The way I was doing it so far was by using the include and turning the forms visible when needed. recently since i added more modules, the app started to slowdown segnificantly, so i'm trying to implement ViewStubs.
This is my RecyclerView adapter:
public class ReQuestionAdapter extends RecyclerView.Adapter<FeedItem> {
private ArrayList<Question> myQuestions;
public ReQuestionAdapter(Context context, ArrayList<Question> questions) {
myQuestions = questions ;
}
#Override
public FeedItem onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_re_question, parent, false);
return new FeedItem(view);
}
#Override
public void onBindViewHolder(FeedItem holder, int position) {
Question q = myQuestions.get(position);
holder.bindQuestion(q);
}
#Override
public int getItemViewType(int position) {
return 0;
}
#Override
public int getItemCount() {
return myQuestions.size();
}
}
And this is the ViewHolder class for the adapter:
public class FeedItem extends RecyclerView.ViewHolder{
private Question mQuestion;
public TextView tvName;
public TextView tvTime;
public TextView tvContent;
public ProfilePictureView profilePictureView;
public ViewStub moduleView;
private int moduleType;
public FeedItem(View itemView) {
super(itemView);
}
public void bindQuestion(Question question) {
mQuestion = question;
tvTime = (TextView) itemView.findViewById(R.id.li_q_date);
tvContent = (TextView) itemView.findViewById(R.id.li_q_content);
moduleView = (ViewStub) itemView.findViewById(R.id.module_viewstub);
tvTime.setText(TimeHandler.When(mQuestion.publish_time));
tvContent.setText(mQuestion.content);
moduleType = question.type;
switch (moduleType) {
case Question.TYPE_YN:
moduleView.setLayoutResource(R.layout.module_yes_no);
moduleView.inflate();
break;
case Question.TYPE_CUSTOM:
moduleView.setLayoutResource(R.layout.module_custom);
moduleView.inflate();
break;
default:
break;
}
}
}
Now, the problem is that the ViewStub which contains a certain layout, cannot be reinflated with a new one, the reason for that is that it gets removed from the view hirarchy as soon as it leaves the screen, the symptoms:
When scrolling down the RecyclerView, the first list items that fill the screen are working perfect, but others to load when the previous leave the screen cause the FeedItem binding to bring a NullPointerException. (It canno't find it in the list item layout).
I'm looking for a solution as efficiant as ViewStubs, or a way to make them work properly, since I got many modules and inflating them all in each item as invisible would make my app slow.
In your bindQuestion() method you are referencing two different layouts to inflate, so in essence you have two different view types.
Adapter views have an efficient way way to handle this built right in.
Start by overriding getItemViewType(). When the item at position gets the module_yes_no layout, return 0. When it gets the module_custom layout, return 1.
Then in onCreateViewHolder(), when the viewType parameter is 0, inflate a list_item_re_question view complete with the module_yes_no layout. When viewType == 1, inflate the module_custom version of the view.
Now when you get a view in onBindViewHolder(), it will already have the correct subview, so you proceed to fill out that view as needed. By using getItemViewType(), the RecyclerView is working with you to recycle the exact view you need.
You can even have two FeedItem subclasses, one for module_yes_no and one for module_custom, so in onBindViewHolder(), you just check the class of the ViewHolder and branch accordingly.
That should help improve the performance of your app.

Categories

Resources