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

[AutoCompleteTextView] Drop down items fail to show once fragment is destroyed and re-created #2171

Closed
vipulvishnoi06 opened this issue Apr 8, 2021 · 1 comment
Assignees
Labels

Comments

@vipulvishnoi06
Copy link

Description:

My app is a single activity with multiple parent and nested fragments. In one of the child fragments I use viewpager2 component to show different fragments depending on the tab selected.

For the sake of simplicity, let's say I have a TextInputLayout + AutoCompleteTextView control on tab1. There are a total of 8 tabs and other tabs have a combination of other controls - TextViews, EditTexts, etc.

Steps on how to reproduce:

  1. On first creation of tab1, I can see all the drop down items on the AutoCompleteTextView control.
  2. I navigate to tab2, tab3, tab4... so on... until at some point fragment manager decides to destroy tab1 and onDestroyView is called on tab1 Fragment
  3. Now, if I navigate back to tab1, onCreateView is called and I cannot see all the items in AutoCompleteTextView control anymore, except the previously selected one.

Sample code:

  • To make my layouts more clear and consistent, I use compound views to encapsulate the TextInputLayout and the child AutoCompleteTextView or TextInputEditText
  • For the sake of keeping the explanation short, I have omited code for my viewModel class and the parent fragment that hosts the viewpager2

view_shared_spinner.xml:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/sp_name"
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense.ExposedDropdownMenu"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:minWidth="240dp">

        <AutoCompleteTextView
            android:id="@+id/sp_value"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="none"
            tools:ignore="LabelFor" />

    </com.google.android.material.textfield.TextInputLayout>

</merge>

SpinnerSharedView.kt:

class SpinnerSharedView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyle: Int = 0
) : LinearLayout(context, attrs, defStyle) {

    private var binding: ViewSharedSpinnerBinding
    private var currentSelectedItemPosition: Int = -1

    init {
        val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
        binding = ViewSharedSpinnerBinding.inflate(inflater, this)
        binding.spName.hint = "my spinner"
    }

    fun getSelectedItemPosition(): Int {
        return currentSelectedItemPosition
    }

    // This method is used to attach a view to the 'Register' data model object. It is called in onViewCreated method of the fragment. This is how user updates the data object
    fun attach(
        reg: Register?, items: Array<String>, callback: (Register) -> Unit
    ) {
        reg?.let { // Register is just a data model class

            val adapter: ArrayAdapter<String> = ArrayAdapter<String>(context, R.layout.list_item_spinner, items)
            binding.spValue.setAdapter(adapter)
            // setOnItemSelectedListener doesn't work on AutoCompleteTextView, instead we should use
            // setOnItemClickListener
            binding.spValue.setOnItemClickListener { _, _, position, _ ->
                if (currentSelectedItemPosition != position) {
                    currentSelectedItemPosition = position
                    it.value = position.toDouble() 
                    callback.invoke(reg)
                }
                clearFocus()
            }
        }
    }

    // This method is called when ViewModel observer sees changes to the 'Register' data model object.  This is how updates to the data object are passed to the view
    fun update(reg: RegisterDec?, map: Map<Int, Int>? = null) {
        reg?.let {
            val tmp = reg.value.toInt()
            val mappedValue = if (map == null) tmp
            else map[tmp] ?: 0
            currentSelectedItemPosition = mappedValue
            binding.spValue.setText(binding.spValue.adapter.getItem(mappedValue).toString(), false)
        }
    }

Tab1 layout:

<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginStart="8dp"
    android:layout_marginTop="8dp"
    android:layout_marginEnd="8dp"
    android:layout_marginBottom="8dp"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

            <!--Other controls omitted-->

            <!--Control Mode-->
            <com.nvent.android.elexant5010imanager.view.shared.SpinnerSharedView
                android:id="@+id/mode"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:name="@string/str_mode"
                app:name_append="" />

        </LinearLayout>

</HorizontalScrollView>
class Tab1: Fragment() {

    private var _binding: FragmentTab1Binding? = null
    private val binding get() = _binding!!

    private val viewModel: Tab1ViewModel by activityViewModels {
        InjectorUtils.provideTab1ViewModelFactory()
    }

    private val controlModeItems =
        arrayOf("N/A", "TS1", "TS2", "TS3", "TS4", "TS5", "TS6", "TS7", "TS8", "Average", "Lowest")

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View {
        _binding = FragmentTab1Binding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        attachUI(viewModel.myDataModel)
        observeUI()
    }

    private fun attachUI(d: myDataModel) {
        binding.mode.attach(d.controlMode, controlModeItems, ::editItemCallback)
    }

    private fun observeUI() {
        viewModel.myDataModelLive.observe(viewLifecycleOwner) {
            it?.let {
                binding.mode.update(it.controlMode)
            }
        }
    }

    private fun editItemCallback(reg: Register) {
        viewModel.update(reg)
    }
}

Version Info:
Andnrod API 11
Navigation version: 2.3.5
Fragment version: 1.3.2
Material design version: 1.2.1 also tried with 1.3.0

@drchen
Copy link
Contributor

drchen commented Jan 12, 2022

Duplicate of #1464

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

No branches or pull requests

3 participants