Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Exposed Dropdown Menu] Filtering incorrectly applied after rotation #1464

Open
mzgreen opened this issue Jul 5, 2020 · 27 comments
Open

[Exposed Dropdown Menu] Filtering incorrectly applied after rotation #1464

mzgreen opened this issue Jul 5, 2020 · 27 comments

Comments

@mzgreen
Copy link

mzgreen commented Jul 5, 2020

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 mzgreen added the bug label Jul 5, 2020
@consp1racy
Copy link
Contributor

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

@mzgreen
Copy link
Author

mzgreen commented Jul 5, 2020

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

@philips77
Copy link

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
Copy link

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;
	}
}

@Coinfo
Copy link

Coinfo commented Aug 19, 2020

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?

@wbervoets
Copy link

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

@audiserg
Copy link

audiserg commented Oct 2, 2020

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)
}

@arvind-codes
Copy link

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];
            }
        };

    }

}

@a2ke5e1
Copy link

a2ke5e1 commented Oct 15, 2020

Thanks , It helps me a lot.

@YarikSOffice
Copy link

@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) 

@justdeko
Copy link

justdeko commented Dec 26, 2020

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.

@Spikeysanju
Copy link

Is there any update on this issue?

@osquitoOfTheNorth
Copy link

Is there any update on this issue ?

@hanafey
Copy link

hanafey commented Feb 14, 2021

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();
            }
        }
    }
}

@SourabhSNath
Copy link

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

@ankitbatra11
Copy link

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

@TepesLucian
Copy link

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
}

@manuelcodigobase
Copy link

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

@cbeyls
Copy link

cbeyls commented Sep 6, 2021

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".

@drchen drchen self-assigned this Oct 1, 2021
@Aamir-Hoda
Copy link

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.

@devaniumesh
Copy link

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

@drchen
Copy link
Contributor

drchen commented Jan 24, 2022

I closed #2171 as a duplicate. : )

@mbsysde99
Copy link

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) {

                }
            };

        }
    }

@enricocid
Copy link

Record_2022-09-26-15-28-59.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)
    }
}

@thefex
Copy link

thefex commented Dec 26, 2022

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();    
    });
}));

@manabu-nakamura
Copy link
Contributor

manabu-nakamura commented Nov 28, 2023

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
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests