0

I have a view that I'm creating multiple child views to create a dynamic form. Some will act like a dropdown and some are EditText controls for data entry. However, when the user enters some data, then goes to the next fragment and hits the back button...all of the EditText controls get the same values. It's whatever was entered on the last one.

I've been racking my brain for over an hour trying to figure out where my bug is. Is it because all of the programmatically created textboxes have the same id?

Here's my onCreateView:

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentNewReceiptInboundCustomAttributesBinding.inflate(layoutInflater)
        val layoutManager: LinearLayout = LinearLayout(context)

        for (i in receipt.InboundCustomAttributes) {
//            val x = newbinding.root
            if (i.CustomControlType in 1..3) {
                val newbindingTB = CustomControlTextboxBinding.inflate(inflater)
                newbindingTB.textView.text = i.LabelCaption
                newbindingTB.textboxvalue.setText(i.DefaultValue)

                binding.layoutItems.addView(newbindingTB.root)
            } else if (i.CustomControlType==4) {
                val newbindingDD = CustomControlDropdownBinding.inflate(inflater)
                newbindingDD.label.text = i.LabelCaption
                newbindingDD.value2.text = i.Value

                newbindingDD.rlCustom.setOnClickListener {
                    val fragment = NewReceiptSelectOptions()
                    val args = Bundle()
                    val receiptblank = Receipt()
                    args.putSerializable("receipt", receiptblank)
                    args.putString("entity", "customattr")
                    args.putSerializable("customattribute", i)
                    fragment.arguments = args
                    activity?.getSupportFragmentManager()?.beginTransaction()
                        ?.replace(R.id.nav_newreceipt_fragment, fragment)
                        ?.addToBackStack("receiptOptions")
                        ?.commit();
                }
                binding.layoutItems.addView(newbindingDD.root)

            }

        }

        binding.nextButton.setOnClickListener {

            var errorcnt: Int =0
            for (i in receipt.InboundCustomAttributes) {
                if (i.Required && i.Value.length==0) {
                    Toast.makeText(
                        this@NewReceiptInboundCustomAttributes.context,
                        i.ErrorMessage,
                        Toast.LENGTH_SHORT
                    ).show()
                    errorcnt=1
                }
            }
            if (errorcnt==0) {
//                addReceiptHeader()
            }
        }
        val view = binding.root
        return view
    }

Here's a screenshot after hitting the back button.

enter image description here

UPDATE:

Now I have converted to use RecyclerView, but the same thing is happening. Here's my adapter...I'm binding to a specific class depending on ViewType and it works well. However, all of my EditText controls still have the same id, so when the user goes to the next fragment and hits the back button, Android is attempting to remember the inputted values. How can i prevent this?


class NewReceiptInboundCustomAttributesAdapter (
    var optionList: MutableList<InboundCustomAttribute>,
    private val listener: NewReceiptInboundCustomAttributes
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

//    inner class ViewHolder(val binding: QuestionItemBinding) : RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
        when (viewType) {
            1 -> {
                bindingTB = CustomControlTextboxBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                CustomControlTB(bindingTB)
            }
            4 -> {
                bindingDD = CustomControlDropdownBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                CustomControlDD(bindingDD)
            }
            6 -> {
                bindingCB = CustomControlCheckboxBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                CustomControlCB(bindingCB)
            }
            else -> {
                bindingTB = CustomControlTextboxBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                CustomControlTB(bindingTB)
            }

        }

    override fun getItemViewType(position: Int) = optionList[position].CustomControlType

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder.getItemViewType()) {
            1 -> {
                (holder as CustomControlTB).bind(optionList[position])
                bindingTB.textboxvalue.addTextChangedListener (object : TextWatcher {

                    override fun afterTextChanged(s: Editable) {}

                    override fun beforeTextChanged(s: CharSequence, start: Int,
                                                   count: Int, after: Int) {
                    }

                    override fun onTextChanged(s: CharSequence, start: Int,
                                               before: Int, count: Int) {
                        listener.textboxChanged(position, bindingTB.textboxvalue.text.toString())
                    }
                })
            }
            4 -> {
                (holder as CustomControlDD).bind(optionList[position])
            }
            6 -> {
                (holder as CustomControlCB).bind(optionList[position])
            }
        }

        holder.itemView.setOnClickListener { v ->
            if (position != RecyclerView.NO_POSITION) {

                listener.optionSelected(position)
            }
        }


    }

    // return the size of languageList
    override fun getItemCount(): Int {
        return optionList.size
    }
}
btorkelson
  • 89
  • 2
  • 10

1 Answers1

0

You really probably shouldn't be using a dynamic view for that, but using a recyclerview. Which would likely fix your problem. Your other option is you're going to have to write custom onSaveInstanceState/onRestoreInstanceState handlers. The problem is that all of your text views likely have the same id, so the built in one uses the same key for all of them and gets confused.

Although really, use a RecyclerView here. Not only does it fix a lot of things and give you a lot of functionality, but you're basically making your own lower quality version of it with the pattern you're using to create this.

Gabe Sechan
  • 90,003
  • 9
  • 87
  • 127
  • Good advice...will convert to using a RecyclerView – btorkelson Oct 17 '22 at 19:54
  • Any guidance on how to bind to different views in a recyclerview adapter? I'm having trouble figuring out how to binding to the correct custom view based on a property of my class. – btorkelson Oct 17 '22 at 21:38
  • You have multiple ViewHolder classes, and override getItemViewType to return what type of viewholder a given index should be. The RecyclerView will pass in that type to onCreateVewHolder. That should create a different ViewHolder class based on the type. Then onBindViewHolder will look at the type of the view holder and call a holder specific bind function. – Gabe Sechan Oct 17 '22 at 22:42
  • Please see my new post, same thing happens in recyclerview – btorkelson Oct 18 '22 at 12:09