material-components-android
material-components-android copied to clipboard
[Exposed Dropdown Menu] Filtering incorrectly applied after rotation
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:
- Open Material Catalog app
- Go to TextField -> Exposed Dropdown Menu Demo
- Tap on 4th TextField from the top and select any value from the dropdown menu.
- Rotate the device to landscape and back to portrait
- 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
Hi, you'll want to include a small project that reproduces the issue.
@consp1racy it's reproducible using Material Catalog sample app.
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.
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;
}
}
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?
same problem here, hoping for a fix in the next version 👍
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) }
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];
}
};
}
}
Thanks , It helps me a lot.
@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)
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.
Is there any update on this issue?
Is there any update on this issue ?
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();
}
}
}
}
Can there be some updates for this? Some of the solutions don't work for me.
For me too, the solutions specified in this issue do not work. It would be nice to have this annoying issue closed.
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
}
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
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"
.
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.
This is still issue , why you closed it without fixing bug ?
I closed #2171 as a duplicate. : )
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) {
}
};
}
}
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)
}
}
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();
});
}));
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()
I also reported this to the Issue Tracker: https://issuetracker.google.com/issues/322066510