material-components-android icon indicating copy to clipboard operation
material-components-android copied to clipboard

[Exposed Dropdown Menu] Filtering incorrectly applied after rotation

Open mzgreen opened this issue 4 years ago • 29 comments

Description: It seems that in some cases filtering is incorrectly applied to AutocompleteTextView after rotating the device which causes that all options except for the selected one disappear from Dropdown Menu.

Steps to reproduce:

  1. Open Material Catalog app
  2. Go to TextField -> Exposed Dropdown Menu Demo
  3. Tap on 4th TextField from the top and select any value from the dropdown menu.
  4. Rotate the device to landscape and back to portrait
  5. Try to select a value from any of the 4 TextFields

The result is that those Dropdowns are now showing only 1 value instead of all of them.

Expected behavior: All TextField Dropdown Menus should show all values after device rotations

Android API version: Tested on Android 10 and Android 11 Beta

Material Library version: Checked on 1.3.0-alpha01 and 1.2.0-beta01

Device: Google Pixel 3 and Emulator

mzgreen avatar Jul 05 '20 09:07 mzgreen

Hi, you'll want to include a small project that reproduces the issue.

consp1racy avatar Jul 05 '20 09:07 consp1racy

@consp1racy it's reproducible using Material Catalog sample app.

mzgreen avatar Jul 05 '20 09:07 mzgreen

This is because getFreezesText() returns always true for EditText. After rotation setText(CharSequence) is called in TextView#onRestoreInstanceState(Parcelable), which by default does the filtering.

philips77 avatar Jul 31 '20 07:07 philips77

I workaround it by overriding getFreezesText() and returning false, but then I have to set the value manually.

public class ExposedDropdownMenu extends MaterialAutoCompleteTextView {

	public ExposedDropDown(@NonNull final Context context, @Nullable final AttributeSet attributeSet) {
		super(context, attributeSet);
	}

	@Override
	public boolean getFreezesText() {
		return false;
	}
}

philips77 avatar Jul 31 '20 07:07 philips77

I have the same issue :( I tried calling setFreezesText(false); but that doesn't help, the suggested by @philips77 method works fine, but can we have an official solution?

Coinfo avatar Aug 19 '20 13:08 Coinfo

same problem here, hoping for a fix in the next version 👍

wbervoets avatar Aug 27 '20 12:08 wbervoets

I have a same bug when i back via navigation component on my fragment where i allready select some item in dropdown autoCompleteTextView.setFreezesText(false) - This not help me i am avoid bug by next trick: override fun onPause() { super.onPause() etSelectDropdown.setText("",false) }

audiserg avatar Oct 02 '20 14:10 audiserg

FIX: All the values will be visible after the device rotation and also the selected value will be displayed.

public class TextInputDropDownMenu extends AppCompatAutoCompleteTextView {

    public TextInputDropDownMenu(@NonNull Context context) {
        super(context);
    }

    public TextInputDropDownMenu(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public TextInputDropDownMenu(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    {
        setInputType(InputType.TYPE_NULL);
    }

    @Override
    public boolean getFreezesText() {
        return false;
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable parcelable = super.onSaveInstanceState();
        if (TextUtils.isEmpty(getText())) {
            return parcelable;
        }

        CustomSavedState customSavedState = new CustomSavedState(parcelable);
        customSavedState.text = getText().toString();
        return customSavedState;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof CustomSavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }

        CustomSavedState customSavedState = (CustomSavedState) state;
        setText(customSavedState.text, false);
        super.onRestoreInstanceState(customSavedState.getSuperState());
    }

    private static final class CustomSavedState extends BaseSavedState {

        private String text;

        public CustomSavedState(Parcelable superState) {
            super(superState);
        }

        public CustomSavedState(Parcel source) {
            super(source);
            text = source.readString();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeString(text);
        }

        private static final Creator<CustomSavedState> CREATOR = new Creator<CustomSavedState>() {
            @Override
            public CustomSavedState createFromParcel(Parcel source) {
                return new CustomSavedState(source);
            }

            @Override
            public CustomSavedState[] newArray(int size) {
                return new CustomSavedState[size];
            }
        };

    }

}

arvind-codes avatar Oct 05 '20 19:10 arvind-codes

Thanks , It helps me a lot.

a2ke5e1 avatar Oct 15 '20 08:10 a2ke5e1

@ar-arvind Your solution produces a crash while testing it agains the process death scenario (Terminate Application button in android studio, for example):

2020-10-21 11:32:30.553 14482-14482/com.example.sample E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.sample, PID: 14482
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.sample.MainActivity}: java.lang.RuntimeException: Parcel android.os.Parcel@aae25dd: Unmarshalling unknown type code 7209033 at offset 2716
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
        at android.app.ActivityThread.-wrap12(ActivityThread.java)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6119)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
     Caused by: java.lang.RuntimeException: Parcel android.os.Parcel@aae25dd: Unmarshalling unknown type code 7209033 at offset 2716
        at android.os.Parcel.readValue(Parcel.java:2444)
        at android.os.Parcel.readSparseArrayInternal(Parcel.java:2813)
        at android.os.Parcel.readSparseArray(Parcel.java:2068)
        at android.os.Parcel.readValue(Parcel.java:2422)
        at android.os.Parcel.readArrayMapInternal(Parcel.java:2732)
        at android.os.BaseBundle.unparcel(BaseBundle.java:269)
        at android.os.Bundle.getSparseParcelableArray(Bundle.java:934)
        at androidx.fragment.app.FragmentStateManager.restoreState(FragmentStateManager.java:236)
        at androidx.fragment.app.FragmentManager.restoreSaveState(FragmentManager.java:2473)
        at androidx.fragment.app.FragmentController.restoreSaveState(FragmentController.java:196)
        at androidx.fragment.app.FragmentActivity.onCreate(FragmentActivity.java:287)
        at androidx.appcompat.app.AppCompatActivity.onCreate(AppCompatActivity.java:115)
        at com.example.sample.MainActivity.onCreate(MainActivity.kt:12)
        at android.app.Activity.performCreate(Activity.java:6679)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2618)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726) 
        at android.app.ActivityThread.-wrap12(ActivityThread.java) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477) 
        at android.os.Handler.dispatchMessage(Handler.java:102) 
        at android.os.Looper.loop(Looper.java:154) 
        at android.app.ActivityThread.main(ActivityThread.java:6119) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776) 

YarikSOffice avatar Oct 21 '20 08:10 YarikSOffice

are there any updates on this issue? additionally, i get a "mini-bug" (not sure if this is expected behavior) that the autocomplete menu pops up when i change the configuration and the autocomplete textview is selected.

justdeko avatar Dec 26 '20 18:12 justdeko

Is there any update on this issue?

Spikeysanju avatar Jan 27 '21 05:01 Spikeysanju

Is there any update on this issue ?

osquitoOfTheNorth avatar Feb 09 '21 16:02 osquitoOfTheNorth

For a simple pick list the following seems to work -- define a new ArrayAdapter.

In Korlin it is used as follows:

        val arrayAdapterFilterControl = ArrayAdapterFilterControl(
            requireContext(), R.layout.spinner_list_item, fvm.mimeTypeList
        )
        arrayAdapterFilterControl.setNeverFilter(true)

It also allows you to skip the false parameter setting in ```setText("Choice") and not truncate your selection list:

        ui.mimeTypeMenu.setText(fvm.mimeType.value)

The never filter adapter is a trivial extension to ArrayAdapter

package com.hanafey.android.weedkiller;

import android.content.Context;
import android.widget.ArrayAdapter;
import android.widget.Filter;

import androidx.annotation.NonNull;

import java.util.List;

public class ArrayAdapterFilterControl<T> extends ArrayAdapter<T> {
   // ...
    public ArrayAdapterFilterControl(@NonNull Context context, int resource, @NonNull T[] objects) {
        super(context, resource, objects);
    }
   // ...
    private Boolean neverFilter = false;

    public void setNeverFilter(Boolean state) {
        neverFilter = state;
    }

    @NonNull
    @Override
    public Filter getFilter() {
        if (!neverFilter) {
            return super.getFilter();
        } else {
            return new NeverFilter();
        }
    }

    private class NeverFilter extends Filter {
        protected FilterResults performFiltering(CharSequence prefix) {
            final FilterResults results = new FilterResults();
            return results;
        }

        protected void publishResults(CharSequence constraint, FilterResults results) {
            if (ArrayAdapterFilterControl.this.getCount() > 0) {
                notifyDataSetChanged();
            } else {
                notifyDataSetInvalidated();
            }
        }
    }
}

hanafey avatar Feb 14 '21 17:02 hanafey

Can there be some updates for this? Some of the solutions don't work for me.

SourabhSNath avatar Feb 17 '21 06:02 SourabhSNath

For me too, the solutions specified in this issue do not work. It would be nice to have this annoying issue closed.

ankitbatra11 avatar Feb 26 '21 23:02 ankitbatra11

Stumbled upon this issue in our app when fragment view gets recreated from backstack or when a config change happens. We're using inputType="none" so filtering is not needed. The above adapter filtering solution works for me but i've changed the filter slightly to basically do nothing:

class MyAdapter(context: Context, val items: List<Item>)
    : ArrayAdapter<Item>(context, R.layout.layout_item, items) {

    private val noOpFilter = object : Filter() {
        private val noOpResult = FilterResults()
        override fun performFiltering(constraint: CharSequence?) = noOpResult
        override fun publishResults(constraint: CharSequence?, results: FilterResults?) {}
    }

    override fun getFilter() = noOpFilter
}

TepesLucian avatar Apr 27 '21 07:04 TepesLucian

Stumbled upon this issue in our app when fragment view gets recreated from backstack or when a config change happens. We're using inputType="none" so filtering is not needed. The above adapter filtering solution works for me but i've changed the filter slightly to basically do nothing:

class MyAdapter(context: Context, val items: List<Item>)
    : ArrayAdapter<Item>(context, R.layout.layout_item, items) {

    private val noOpFilter = object : Filter() {
        private val noOpResult = FilterResults()
        override fun performFiltering(constraint: CharSequence?) = noOpResult
        override fun publishResults(constraint: CharSequence?, results: FilterResults?) {}
    }

    override fun getFilter() = noOpFilter
}

This solution work for me and it's very easy to implement

manuelcodigobase avatar May 24 '21 10:05 manuelcodigobase

Here's my take on this issue: Instead of creating a custom ArrayAdapter returning a dummy filter, which still launches a background thread to perform the filtering, I create a custom AutoCompleteTextView which always disables filtering when calling setText() if android:inputType="none" (including when restoring view state):

class NonFilterableAutoCompleteTextView @JvmOverloads constructor(context: Context,
                                                                  attributeSet: AttributeSet? = null,
                                                                  defStyleAttr: Int = R.attr.autoCompleteTextViewStyle)
    : MaterialAutoCompleteTextView(context, attributeSet, defStyleAttr) {
    private var isCallingSetText = false

    override fun setText(text: CharSequence?, type: BufferType?) {
        if (isCallingSetText || inputType != EditorInfo.TYPE_NULL) {
            super.setText(text, type)
        } else {
            isCallingSetText = true
            setText(text, false)
            isCallingSetText = false
        }
    }
}

I suggest to include the above code directly in MaterialAutoCompleteTextView to fix the issue, since the class already includes a fix to properly disable editing when android:inputType="none".

cbeyls avatar Sep 06 '21 11:09 cbeyls

Hello, as of today: 9th Dec 2021, I'm still encountering this issue in AutoCompleteTextView. Has there been any official solution??

Memory Refresh: The issue is that upon screen rotation, autoCompleteTextView drop-down only shows the selected entry instead of the multiple entries.

Any help is highly appreciated.

Aamir-Hoda avatar Dec 09 '21 11:12 Aamir-Hoda

This is still issue , why you closed it without fixing bug ?

devaniumesh avatar Jan 17 '22 12:01 devaniumesh

I closed #2171 as a duplicate. : )

drchen avatar Jan 24 '22 16:01 drchen

Stumbled upon this issue in our app when fragment view gets recreated from backstack or when a config change happens. We're using inputType="none" so filtering is not needed. The above adapter filtering solution works for me but i've changed the filter slightly to basically do nothing:

class MyAdapter(context: Context, val items: List<Item>)
    : ArrayAdapter<Item>(context, R.layout.layout_item, items) {

    private val noOpFilter = object : Filter() {
        private val noOpResult = FilterResults()
        override fun performFiltering(constraint: CharSequence?) = noOpResult
        override fun publishResults(constraint: CharSequence?, results: FilterResults?) {}
    }

    override fun getFilter() = noOpFilter
}

It works perfect, Thanks, I convert that to java.

    private static class MyAdapter<T> extends ArrayAdapter<T>{
        private final Filter noOpFilter;

        @NotNull
        private final List<T> items;

        @NotNull
        public Filter getFilter() {
            return (Filter)this.noOpFilter;
        }

        @NotNull
        public final List<T> getItems() {
            return this.items;
        }

        public MyAdapter(@NonNull Context context, int resource, @NonNull List<T> objects) {
            super(context, resource, objects);

            this.items = objects;

            this.noOpFilter = new Filter() {
                private final FilterResults noOpResult = new FilterResults();

                @Override
                protected FilterResults performFiltering(CharSequence charSequence) {
                    return this.noOpResult;
                }

                @Override
                protected void publishResults(CharSequence charSequence, FilterResults filterResults) {

                }
            };

        }
    }

mbsysde99 avatar Mar 07 '22 00:03 mbsysde99

https://user-images.githubusercontent.com/7274841/192289707-dd0ac1c7-d52a-4b2b-b1e2-8fb7329d7cb8.mp4

To solve the list filtering issue after a configuration change I set

isSaveEnabled = false

when instantiating the drop down menu

full code:

_eqFragmentBinding?.autoCompleteTextView?.run {
    setSimpleItems(mPresetsList.toTypedArray())
    isSaveEnabled = false
    setText(mPresetsList[mSelectedPreset], false)
    setOnItemClickListener { _, _, newPreset, _ ->
        // Respond to item chosen
        mSelectedPreset = newPreset
        mEqualizer?.first?.usePreset(mSelectedPreset.toShort())
        updateBandLevels(isPresetChanged = true)
    }
}

enricocid avatar Sep 26 '22 13:09 enricocid

None of above worked - in my case it randomly failed when put on recyclerview with dynamic adding/removing of item. Found workaround though (C#)

autoCompleteTextView.OnFocusChangeListener = new FocusChangeWrapperListener((view, isFocused) =>
{
    if (!isFocused)
        return;

    autoCompleteTextView.Post(() =>
    {
        if (!autoCompleteTextView.IsPopupShowing)
            autoCompleteTextView.ShowDropDown();    
    });
});

autoCompleteTextView.SetOnClickListener(new ClickActionWrapperListener(() =>
{
    autoCompleteTextView.Post(() =>
    {
        if (!autoCompleteTextView.IsPopupShowing)
            autoCompleteTextView.ShowDropDown();    
    });
}));

thefex avatar Dec 26 '22 10:12 thefex

This is a easy workaround.

public class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding.autoCompleteTextView.setThreshold(Integer.MAX_VALUE);                          // *
        binding.autoCompleteTextView.post(() -> binding.autoCompleteTextView.setThreshold(1)); // No need if filtering is not needed
    }
}

or

public class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding.autoCompleteTextView.setThreshold(Integer.MAX_VALUE);  // *
    }

    @Override                                                          //
    protected void onPostCreate(@Nullable Bundle savedInstanceState) { //
        super.onPostCreate(savedInstanceState);                        //
        binding.autoCompleteTextView.setThreshold(1);                  // No need if filtering is not needed
    }                                                                  //
}

or

public class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding.autoCompleteTextView.setThreshold(Integer.MAX_VALUE); // *
    }

    @Override                                                         //
    protected void onResume() {                                       //
        super.onResume();                                             //
        binding.autoCompleteTextView.setThreshold(1);                 // No need if filtering is not needed
    }                                                                 //
}

https://developer.android.com/reference/android/widget/AutoCompleteTextView#setThreshold(int) https://developer.android.com/reference/android/widget/AutoCompleteTextView#enoughToFilter()

manabu-nakamura avatar Nov 28 '23 08:11 manabu-nakamura

I also reported this to the Issue Tracker: https://issuetracker.google.com/issues/322066510

manabu-nakamura avatar Jan 26 '24 11:01 manabu-nakamura