4

I'm trying to set a text to an EditText inside my custom adapter and I'm getting this stack:

java.lang.NullPointerException: Attempt to read from field 'android.support.v7.widget.ViewInfoStore android.support.v7.widget.RecyclerView.mViewInfoStore' on a null object reference
    at android.support.v7.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:8194)
    at android.support.v7.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8180)
    at android.support.v7.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8168)
    at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1573)
    at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1519)
    at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:614)
    at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3812)
    at android.support.v7.widget.RecyclerView.onMeasure(RecyclerView.java:3225)
    at android.view.View.measure(View.java:17547)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5535)
    at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1436)
    at android.widget.LinearLayout.measureVertical(LinearLayout.java:722)
    at android.widget.LinearLayout.onMeasure(LinearLayout.java:613)
    at android.view.View.measure(View.java:17547)
    at android.widget.ScrollView.measureChildWithMargins(ScrollView.java:1260)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:436)
    at android.widget.ScrollView.onMeasure(ScrollView.java:337)
    at android.view.View.measure(View.java:17547)
    at android.support.constraint.ConstraintLayout.onMeasure(ConstraintLayout.java:1676)
    at android.view.View.measure(View.java:17547)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5535)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:436)
    at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:141)
    at android.view.View.measure(View.java:17547)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5535)
    at android.support.v7.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:400)
    at android.view.View.measure(View.java:17547)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5535)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:436)
    at android.view.View.measure(View.java:17547)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5535)
    at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1436)
    at android.widget.LinearLayout.measureVertical(LinearLayout.java:722)
    at android.widget.LinearLayout.onMeasure(LinearLayout.java:613)
    at android.view.View.measure(View.java:17547)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5535)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:436)
    at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2615)
    at android.view.View.measure(View.java:17547)
    at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2015)
    at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1173)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1379)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1061)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5885)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
    at android.view.Choreographer.doCallbacks(Choreographer.java:580)
    at android.view.Choreographer.doFrame(Choreographer.java:550)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5254)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)


at com.andro
02-21 14:14:29.702 1707-1707/com.android.inputmethod.latin E/RichInputConnection: Unable to connect to the editor to retrieve text.
02-21 14:14:31.705 1707-1707/com.android.inputmethod.latin E/RichInputConnection: Unable to connect to the editor to retrieve text.
02-21 14:14:33.706 1707-1707/com.android.inputmethod.latin E/RichInputConnection: Unable to connect to the editor to retrieve text.
02-21 14:14:35.709 1707-1707/com.android.inputmethod.latin E/RichInputConnection: Unable to connect to the editor to retrieve text.

I'm not sure what is wrong, I am setting text to a TextView exactly the same way and it works fine. I've looked over forums and articles and I've seen people with similar issues but It seems they are either not binding the view with I's ViewHolder property correctly or trying to access the

Here It is my custom Adapter class:

public class ProductListAdapter extends RecyclerView.Adapter<ProductListViewHolder>{
    private List<Product> productList;
    private ProductItemManager productItemManager;

    public ProductListAdapter(List<Product> productList, ProductItemManager productItemManager) {
        this.productList = productList;
        this.productItemManager = productItemManager;
    }

    @NonNull
    @Override
    public ProductListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View productListView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.adapter_product_list, parent, false);
        return new ProductListViewHolder(productListView, productItemManager);
    }

    @Override
    public void onBindViewHolder(@NonNull ProductListViewHolder productListViewHolder, final int position) {
        Product product = productList.get(position);

        Double finalPrice = product.getSellingPrice() * product.getQuantity();

        productListViewHolder.productDescription.setText(product.getDescription());
        productListViewHolder.productSellingPrice.setText("un. " + Utils.doubleToReal(product.getSellingPrice()));
        productListViewHolder.productFinalPrice.setText(Utils.doubleToReal(finalPrice));
        productListViewHolder.productQuantity.setText(Double.toString(product.getQuantity()));
    }

    @Override
    public int getItemCount() {
        return productList.size();
    }
}

And here It is my ViewHolder

public class ProductListViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    public TextView productDescription, productSellingPrice, productFinalPrice;
    public ImageView removeProductIcon;
    public EditText productQuantity;
    private ProductItemManager productItemManager;

    public ProductListViewHolder(View itemView, ProductItemManager productItemManager) {
        super(itemView);

        productDescription = itemView.findViewById(R.id.productDescription);
        productSellingPrice = itemView.findViewById(R.id.productSellingPrice);
        productFinalPrice = itemView.findViewById(R.id.productFinalPrice);
        removeProductIcon = itemView.findViewById(R.id.removeProductIcon);
        productQuantity = itemView.findViewById(R.id.productQuantity);

        this.productItemManager = productItemManager;

        removeProductIcon.setOnClickListener(this);

        this.productChangeListener();
    }

    private void productChangeListener() {
        productQuantity.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                Double quantity = Double.parseDouble(productQuantity.getText().toString());
                productItemManager.setProductQuantity(getAdapterPosition(), quantity);
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });
    }

    @Override
    public void onClick(View v) {
        productItemManager.removeProduct(getAdapterPosition());
    }
}
Ahmad Sabeh
  • 476
  • 3
  • 14
  • Possible duplicate of [What is a NullPointerException, and how do I fix it?](https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it) – Tamir Abutbul Feb 21 '19 at 13:51
  • post full stacktrace not only error – Vygintas B Feb 21 '19 at 14:11
  • I've edited the question adding the full stack. – Guilherme Ramalho Feb 21 '19 at 14:15
  • So I was just testing the app and I found out that the problem only seems to occur when I have a TextWatcher listener inside the ProductListViewHolder's constructor. I'd appreciate any thoughts on that. – Guilherme Ramalho Feb 27 '19 at 12:56
  • It would be helpfull if you post the ProductItemManager class code because this is where this bug looks like is coming from. – Nikos Feb 27 '19 at 14:23
  • @GuilhermeRamalho can you please check my answer and see if that helps? Thanks :) – Reaz Murshed Mar 04 '19 at 18:26
  • @GuilhermeRamalho From the stack trace it seem that you are nesting 2 recycler View, the hole inside a ScrollView... could you please post the source code of R.layout.adapter_product_list ? the second recyclerView seem to be missing an adapter and a layoutManager... – Anis BEN NSIR Mar 06 '19 at 10:05

5 Answers5

4

You should call listener set on TextWatch, once you view bound. In onBindViewHolder call your registration.

......
@Override
public void onBindViewHolder(@NonNull ProductListViewHolder productListViewHolder, final int position) {
......
productListViewHolder.productChangeListener();
....

And I also suggest to change function with registration listeners. Without creation hundred of items, during RecyclerView creation.

Zumbarlal Saindane
  • 1,121
  • 11
  • 22
GensaGames
  • 4,757
  • 1
  • 17
  • 43
  • According to an article I've read It is a bad practice to declare listeners outside the view holder. Is that true? The author recommended to implement the listener on the viewholder and then create a interface of my own which will then be extended by my activity and there my interface's method should be overridden. That's what I did on the ViewHolder for the click event and It worked fine. – Guilherme Ramalho Feb 27 '19 at 14:58
3

There are a few things that need your attention:

  1. Having EditText in RecyclerViews (and lists in general) in generally not a good idea, because of their UX. You either have to stop the user from scrolling the list, or do some action based on the scroll, to make things smoother (since they can be in progress of inputting something).

  2. There's a very important issue in the code and that is a reference to the "TextWatcher" of the edit text that is being recycled. You need to remove the text watcher while a view is being recycled. What happens If you don't remove the TextWatcher? Since it is not like OnClickListener (It is not a set function and is an add function), Everytime that your cells are being recreated, A new text watcher get's added to the EditText with it's references to the old ViewHolder.

To fix this, you need to add a method to your ViewHolder to unbind any stuff that it holds:

public class ProductListViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        public TextView productDescription, productSellingPrice, productFinalPrice;
        public ImageView removeProductIcon;
        public EditText productQuantity;
        private ProductItemManager productItemManager;
        private TextWatcher textWatcher;

        public ProductListViewHolder(View itemView, ProductItemManager productItemManager) {
            super(itemView);

            productDescription = itemView.findViewById(R.id.productDescription);
            productSellingPrice = itemView.findViewById(R.id.productSellingPrice);
            productFinalPrice = itemView.findViewById(R.id.productFinalPrice);
            removeProductIcon = itemView.findViewById(R.id.removeProductIcon);
            productQuantity = itemView.findViewById(R.id.productQuantity);

            this.productItemManager = productItemManager;

            removeProductIcon.setOnClickListener(this);

            this.productChangeListener();
        }

        private void productChangeListener() {
            textWatcher = new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {

                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    Double quantity = Double.parseDouble(productQuantity.getText().toString());
                    productItemManager.setProductQuantity(getAdapterPosition(), quantity);
                }

                @Override
                public void afterTextChanged(Editable s) {

                }
            };
            productQuantity.addTextChangedListener(textWatcher);
        }

        public void unbind(){
            productQuantity.removeTextChangedListener(textWatcher);
        }
        @Override
        public void onClick(View v) {
            productItemManager.removeProduct(getAdapterPosition());
        }
    }

Then, you can use RecyclerView's onViewRecycled method to know when your view is being recycled and remove the TextWatcher:

@Override
    public void onViewRecycled(ProductListViewHolder productListViewHolder) {
        super.onViewRecycled(holder);
        productListViewHolder.unbind();
    }
Adib Faramarzi
  • 2,723
  • 3
  • 23
  • 36
1

The TextWatcher is going to be called each and every time the text it watches changes. This not only means that user edits will invoke the watcher (good), but each time a view holder is rebound the watcher will also be invoked (not so good). This also means that productItemManager.setProductQuantity() will be called unnecessarily. (I say "unnecessarily" but, maybe, this is something that you intend to happen.)

One way to avoid the text watcher when a view holder is bound is to set a View.OnFocusChangeListener() on productQuantity that will apply and remove the text watcher as focus is gained or lost for the field. In other words, the text watcher will be active only while the EditText is being edited.

productQuantity.setOnFocusChangeListener(new View.OnFocusChangeListener() {
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if (hasFocus) {
            productQuantity.addTextChangedListener(textWatcher);
        } else {
            productQuantity.removeTextChangedListener(textWatcher);
        }
    }
});

This may not solve the problem that you are having, but it will eliminate unnecessary work that may be a contributing factor.

I think that posting setProductQuantity() will help someone get to the root of the problem.

Cheticamp
  • 50,205
  • 8
  • 64
  • 109
0

I would like to agree with the implementation proposed by @Cheticamp. Based on my experience, the text change listener might trigger in different situations where it was not supposed to be called. The EditText are often involved in auto-correcting words and providing suggestions and can be invoked during initialization. Hence I would like to recommend you to put the following in your EditText declaration in the layout file.

android:inputType="textNoSuggestions"

And instead of using setOnFocusChangeListener you might consider having a null check in your text watcher like the following.

private void productChangeListener() {
    productQuantity.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            String editTextContent = productQuantity.getText().toString();
            if(editTextContent != null && editTextContent.trim().length() > 0) {
                Double quantity = Double.parseDouble(productQuantity.getText().toString());
                productItemManager.setProductQuantity(getAdapterPosition(), quantity);
            }
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    });
}

Hope that helps!

Reaz Murshed
  • 21,071
  • 12
  • 69
  • 87
0
 //update this line in your code, you will get your answer :) 

productListViewHolder.productQuantity.setText(Double.toString(productList.get(position).getQuantity()));
  • Can you explain that further? What was wrong with the original line, what did you do to make it work? Keep in mind that the OP should be able to learn from your improvement and not simply copy it – Nico Haase Mar 06 '19 at 08:44
  • recently i had faced this same problem, and i have solved it by accessing directly the Main List poisition. – Akshay Dobariya Mar 06 '19 at 08:49
  • Please add all explanation to the answer, not to the comment section. Additionally, explain more deep **what** you've changed and why - what was wrong with the original code? – Nico Haase Mar 06 '19 at 08:51