AutoCompleteTextView doesn't work on TextInputLayout on ConstraintLayout with 0dp width

53 Views Asked by At

I have an AutoCompleteTextView on a TextInputLayout on a ConstraintLayout on a FrameLayout. And i need to add a Button beside it, so i changed the width of the TextInputLayout to 0dp (previously match_parent). But, suddenly the AutoCompleteTextView don't work. It worked before and i checked it, it suddently don't work when the width is 0dp.

The error

java.lang.NullPointerException: Attempt to invoke virtual method 'int java.util.ArrayList.size()' on a null object reference
                                                                                                  
at com.its.vcss.utils.AutocompleteRetrieval.getCount(AutocompleteRetrieval.java:62)
at com.google.android.material.textfield.MaterialAutoCompleteTextView.measureContentWidth(MaterialAutoCompleteTextView.java:355)
at com.google.android.material.textfield.MaterialAutoCompleteTextView.onMeasure(MaterialAutoCompleteTextView.java:332)
at android.view.View.measure(View.java:27720)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7352)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at android.view.View.measure(View.java:27720)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7352)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
at com.google.android.material.textfield.TextInputLayout.onMeasure(TextInputLayout.java:3061)
at android.view.View.measure(View.java:27720)
at androidx.constraintlayout.widget.ConstraintLayout$Measurer.measure(ConstraintLayout.java:811)
at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.measure(BasicMeasure.java:466)
at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.measureChildren(BasicMeasure.java:134)
at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.solverMeasure(BasicMeasure.java:278)
at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:120)
at androidx.constraintlayout.widget.ConstraintLayout.resolveSystem(ConstraintLayout.java:1594)
at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1708)
at android.view.View.measure(View.java:27720)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7352)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at android.view.View.measure(View.java:27720)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7352)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at android.view.View.measure(View.java:27720)
at androidx.constraintlayout.widget.ConstraintLayout$Measurer.measure(ConstraintLayout.java:811)
at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:632)
at androidx.constraintlayout.core.widgets.analyzer.Direct.verticalSolvingPass(Direct.java:452)
at androidx.constraintlayout.core.widgets.analyzer.Direct.solveVerticalMatchConstraint(Direct.java:750)
at androidx.constraintlayout.core.widgets.analyzer.Direct.verticalSolvingPass(Direct.java:503)
at androidx.constraintlayout.core.widgets.analyzer.Direct.solvingPass(Direct.java:224)
at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.layout(ConstraintWidgetContainer.java:693)
at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.solveLinearSystem(BasicMeasure.java:160)
at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.solverMeasure(BasicMeasure.java:291)
at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:120)
at androidx.constraintlayout.widget.ConstraintLayout.resolveSystem(ConstraintLayout.java:1594)
at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1708)
at android.view.View.measure(View.java:27720)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7352)
at androidx.coordinatorlayout.widget.CoordinatorLayout.onMeasureChild(CoordinatorLayout.java:760)
at androidx.coordinatorlayout.widget.CoordinatorLayout.onMeasure(CoordinatorLayout.java:833)
at android.view.View.measure(View.java:27720)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7352)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)

navigation_layout.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/vcss_light_green">

        <fragment
            class="com.google.android.gms.maps.SupportMapFragment"
            android:id="@+id/map_fragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:layout="@layout/onboarding_screen"/>

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="bottom">

            ...

            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/il_search"
                **android:layout_width="0dp"**
                android:layout_height="wrap_content"
                app:layout_constraintTop_toBottomOf="@id/btn_back"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toStartOf="@id/btn_search"
                android:layout_marginTop="10dp"
                android:layout_marginStart="14dp"
                android:layout_marginEnd="10dp"
                app:shapeAppearance="@style/ShapeAppearance.VCSS.SmallComponent"
                app:shapeAppearanceOverlay="@style/ShapeAppearance.VCSS.SmallComponent"
                app:startIconDrawable="@drawable/ic_round_search"
                app:endIconDrawable="@drawable/ic_round_close"
                app:endIconTint="@color/black_40"
                app:endIconMode="custom"
                app:endIconContentDescription="Bersihkan pencarian"
                app:hintEnabled="false"
                app:boxBackgroundColor="@color/white"
                app:expandedHintEnabled="false">

                <AutoCompleteTextView
                    android:id="@+id/edt_search"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:paddingEnd="16dp"
                    android:hint="@string/public_facility_search_hint"
                    android:paddingBottom="0dp"
                    android:paddingTop="0dp"
                    android:inputType="textAutoComplete|textCapWords"
                    android:maxLines="1"
                    android:imeOptions="actionSearch"
                    android:dropDownWidth="200dp"
                    />
            </com.google.android.material.textfield.TextInputLayout>

            <Button
                style="@style/Theme.VCSS.Button"
                android:id="@+id/btn_search"
                android:layout_width="48dp"
                android:layout_height="0dp"
                android:layout_marginEnd="14dp"
                app:cornerRadius="24dp"
                android:insetTop="0dp"
                android:insetBottom="0dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@id/il_search"
                app:layout_constraintTop_toTopOf="@id/il_search"
                app:layout_constraintBottom_toBottomOf="@id/il_search"
                app:icon="@drawable/ic_round_search"/>

            ...

        </androidx.constraintlayout.widget.ConstraintLayout>

   <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="bottom"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/il_search"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="160dp">

        <include
            android:id="@+id/bottom_sheet"
            layout="@layout/navigation_information_bottom_sheet" />

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</FrameLayout>

NavigationFragment.kt

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        ...

        sharedViewModel.location.observe(viewLifecycleOwner) {
            if (it != null) {
                Log.d("Location", it.toString())
                binding.edtSearch.setAdapter(AutocompleteRetrieval(requireContext(),
                   android.R.layout.simple_list_item_1, it))
            } else {
                val location = LatLng(0, 0)
                binding.edtSearch.setAdapter(AutocompleteRetrieval(requireContext(),
                    android.R.layout.simple_list_item_1, location))
            }
        }

        binding.apply {
            ...

            edtSearch.apply {
                setAdapter(
                    AutocompleteRetrieval(
                        requireContext(),
                    android.R.layout.simple_list_item_1,
                    )
                )

                addTextChangedListener(object : TextWatcher {
                    ...
                })

                setOnEditorActionListener { _, i, _ ->
                    ...
                }
            }
            ...
        }
    }

AutoCompleteRetrieval.java

public class AutocompleteRetrieval extends ArrayAdapter implements Filterable {
    private static Context context;
    private LatLng latLng;
    private ArrayList resultList;
    private static final String PLACES_API_BASE = "https://maps.googleapis.com/maps/api/place/autocomplete/json?radius=10000&type=establishment&language=id";

    public AutocompleteRetrieval(@NonNull Context context, int resource, LatLng latLng) {
        super(context, resource);
        this.context = context;
        this.latLng = latLng;
    }

    public AutocompleteRetrieval(@NonNull Context context, int resource) {
        super(context, resource);
    }

    public ArrayList getResultList() {
        return resultList;
    }

    public void setResultList(ArrayList resultList) {
        this.resultList = resultList;
    }

    @Override
    public int getCount() {
        return resultList.size();
    }

    @Override
    public String getItem(int index) {
        return (String) resultList.get(index);
    }

    @Override
    public Filter getFilter() {
        final Filter filter = new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults filterResults = new FilterResults();

                if (constraint != null) {
                    // Retrieve the autocomplete results.
                    resultList = autocomplete(constraint.toString(), latLng);

                    // Assign the data to the FilterResults
                    filterResults.values = resultList;
                    filterResults.count = resultList.size();
                }
                return filterResults;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                if (results != null && results.count > 0) {
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
            }
        };
        return filter;
    }

    public static ArrayList autocomplete(String input, LatLng latLng)
    {
        ArrayList resultList = null;
        HttpURLConnection conn = null;
        String location = "&location="+latLng.lat+","+latLng.lng;
        StringBuilder jsonResults = new StringBuilder();
        try {
            String apiKey = BuildConfig.GOOGLE_MAPS_API_KEY;
            StringBuilder sb = new StringBuilder(PLACES_API_BASE + location +"&key="+apiKey);
            sb.append("&input=").append(URLEncoder.encode(input, "utf8"));
            URL url = new URL(sb.toString());

            conn = (HttpURLConnection) url.openConnection();
            InputStreamReader in = new InputStreamReader(conn.getInputStream());

            int read;
            char[] buff = new char[1024];
            while ((read = in.read(buff)) != -1) {
                jsonResults.append(buff, 0, read);
            }

        } catch (MalformedURLException e) {
            Log.e("AutoComp", "Error processing Places API URL", e);
            return resultList;

        } catch (IOException e) {
            Log.e("AutoComp", "Error connecting to Places API", e);
            return resultList;

        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }

        try {
            // Create a JSON object hierarchy from the results
            JSONObject jsonObj = new JSONObject(jsonResults.toString());
            JSONArray predsJsonArray = jsonObj.getJSONArray("predictions");

            // Extract the Place descriptions from the results
            resultList = new ArrayList(predsJsonArray.length());
            for (int i = 0; i < predsJsonArray.length(); i++) {
                resultList.add(predsJsonArray.getJSONObject(i).getString("description"));
            }

        } catch (JSONException e) {
            Log.e("LOG_TAG", "Cannot process JSON results", e);
        }

        return resultList;
    }
}

What causes that problem? I think it has something to do with the width since it is what makes it suddently don't work. The error trace also shows some layout methods being called.

Some important thing, although the Error showed NPE on the array which is the result of the autocomplete, i think it is not the problem. I changed nothing but the width of the TextInputLayout from match_parent to 0dp and it crashes with the error. I also tried using fixed width like 128dp for example, and it works.

1

There are 1 best solutions below

0
cactustictacs On

Really the problem is that getCount() in your Adapter class can access resultList before it's actually assigned, and invokes size() on it instead of null-checking it and returning 0 by default. That's why you're getting the NullPointerException here.

I'm guessing you're only seeing the problem now because instead of using match_parent (which just reads the parent View's dimensions), you're using 0dp which invokes the constraints you've set - they weren't being used before. And now you're using constraints, that View is taking part in the constraint resolution process. You can see from your stacktrace that the final error (at the top) was caused by a chain of layout measurement requests that came from the ConstraintLayout library (lower down = earlier in the call chain).

I guess it doesn't matter that the AutoCompleteTextView is match_parent so its contents have no bearing on anything - the constraint resolver is asking for measurements, and that ends up calling your getCount method earlier than you're expecting, when it's not safe to do so. So really it's just revealing that bug in your Adapter code. Handle your potentially-null list correctly and it should fix the issue!