I have a dynamic list of input fields. Those. Depending on the user's choice of certain values, a list of input fields is displayed. So I had to add these fields (EditText) programmatically to an empty LinearLayout, which I specifically placed in the markup. The code is added at a certain moment (when the user in the spiner has selected the desired category). The code for creating input fields is:

fun generateAdditionalFields(fields: ArrayList<String>) { tempAdditionalFields.removeAll(tempAdditionalFields) var isFirst = true fields.forEach { tempAdditionalFields.add(makeEditText(it, it, isFirst)) isFirst = false } additional_fields_block.removeAllViews() tempAdditionalFields.forEach { additional_fields_block.addView(it) } } fun makeEditText(hint: String, name: String, first: Boolean) : EditText { val editText = EditText(ContextThemeWrapper(this, R.style.MainInput), null, 0) editText.hint = hint editText.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) editText.tag = name if (!first) editText.setMargin(topMargin = dpToPix(5f)) return editText } 

I also have a list for storing these tempAdditionalFields input fields so that you can later save the data entered by the user.

When a user switches a category, the old input fields are deleted (both from the list and from LinearLayout ) and generated again.

Everything worked without problems. But I decided to modify the code and save the data when the device's screen rotates, and restore it after the re-creation of the activation. Usually there are no problems with this operation. But in this case, after recreating the tempAdditionalFields , I restore the tempAdditionalFields list, and then from it I add all the EditText back to LinearLayout, but then it gives an error saying that they already contain these views. Then I call before adding additional_fields_block.removeAllViews() , and the same error still crashes. Then I removed the add. But the screen displays completely new input fields. Yes, these are the input fields that were before the re-creation of the activation, but without the text entered. Although in the restored tempAdditionalFields list tempAdditionalFields entered text is. I removed the restoration of this list for the experiment. And even removed the addition of these input fields after recovery. The result was the same. And what is most interesting, the additional_fields_block.removeAllViews() method does not work after the re-creation. Or rather, if I call it in onCreate or onStart or onResume . But if you create a button and hang a listener on it, and when you click to set this very removal of views, then when clicked, it deletes as it should.

I didn’t publish all the code, as there is a lot of it. But here are some parts.

onCreate:

  var step = 1 var avatar_id : Int? = null var tempAdditionalFields = LinkedList<EditText>() var companies = LinkedList<Company>() val companiesAdapter = CompaniesShortListAdapter(companies) var dontSwitchFields = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_create_user) if (!restoreFromSavedInstance(savedInstanceState)){ GetCompaniesListTask().execute() } else dontSwitchFields = true switchUserInfoBtnsVis() switchFirmNameBtnsVis() switchChooseRoleBtnsVis() setListeners() switchStep(step) } 

Methods of saving and restoring (here I changed and commented out some lines for the experiment):

  override fun onSaveInstanceState(outState: Bundle?) { outState?.let {bundle -> bundle.putInt("step", step) avatar_id?.let { bundle.putInt("avatar_id", it) } bundle.putSerializable("tempAdditionalFields", tempAdditionalFields) bundle.putSerializable("companies", companies) } super.onSaveInstanceState(outState) } fun restoreFromSavedInstance(savedInstanceState: Bundle?): Boolean { savedInstanceState?.let { bundle -> step = bundle.getInt("step") switchStep(step) avatar_id = bundle.getInt("avatar_id") // tempAdditionalFields.removeAll(tempAdditionalFields) // tempAdditionalFields.addAll(bundle.getSerializable("tempAdditionalFields") as Collection<EditText>) /* tempAdditionalFields.forEach { Log.e("TEST_EDIT", "edit: ${it.text}") }*/ additional_fields_block.removeAllViews() companies.removeAll(companies) companies.addAll(bundle.getSerializable("companies") as Collection<Company>) companiesAdapter.notifyDataSetChanged() return true } return false } 

I understand that the system itself restores these added views. But then at what stage? And I understand that in this case it would be possible to go the other way and just save the data entered in the fields, and then fill it out again. But I would like to get to the bottom of the truth, why I cannot delete the views added before the screen rotation ( EditText )

    1 answer 1

    In general, as it turned out, the problem was in two places at once. In short, they were:

    1. In spinner
    2. In the method of removing views

    Now more, suddenly someone come in handy.

    1. Spinner

    I had a listener on the spinner, in which I processed the onItemSelected method. When this method triggered, I caused the generation of input fields. Those. depending on the selected category, they created their own EditText and each time their own number. But I did not think that when initializing the list of categories in the spinner, this method also works. Those. as soon as the category list (or adapter) is connected to the spinner, this method works.

    When I understood it, I changed the approach. I created a variable that stores the position of the selected category in the spinner and defaults to 0. And when the onItemSelected method is onItemSelected I make a check whether the value of this variable matches the new position. If it does not match, then the user has changed the category, and then we save the new position to this variable and execute the method we need for generating input fields.

    I save and restore this variable when I change the screen. And I do it from the onCreate method before the spinner recovers. Therefore, when the spinner is restored and sets the old position, it will coincide with the restored variable and the generation of input fields will not occur.

    This solved the problem when views were recreated from scratch. But the problem remained java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first. error java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first. java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

    2. Removal of views

    If I refer directly to the view containing the input fields and call removeAllViews() , and then I try to add the input fields, the error will crash. At first, I understood its text as “the parent view already contains this child view, delete it first from the parent”. But it turned out to be a slightly different meaning. "A daughter view already has a parent. First, call the removeView() method on the parent child view."

    Although it seems that these two sentences have the same meaning, but this is not quite so. At first, I tried to call this method directly on the view, passing it each EditText from the restored array separately. But it did not work.

    But it turned out that it seems that EditText 's belong to the old additional_fields_block ( LinearLayout view), and the new one has no child views. Therefore, their parent is not a new additional_fields_block , but an old one. And the old additional_fields_block still exists in memory, since the link to it is in EditText . Then I created this method:

      fun restoreAdditionalFields(){ tempAdditionalFields.forEach { (it.parent as ViewGroup).removeView(it) additional_fields_block.addView(it) } } 

    Those. I take an ancestor from each text field and delete this text field from there, and then add it to the new additional_fields_block . At least I understood it all so. Maybe my guesses are wrong, then please correct me. It is interesting to know how this mechanism actually works.

    True, onCreate my method did not work (the error did not occur, but it had no effect). Then I stuck his call to this method:

      override fun onRestoreInstanceState(savedInstanceState: Bundle?) { super.onRestoreInstanceState(savedInstanceState) restoreAdditionalFields() } 

    Not quite certainly understand why. Can anyone explain?