Restore fragment with two views having the same id
Asked Answered
K

2

9

I have a complex layout to implement. It has 19 sections that can be displayed or not based on plenty of parameters previously entered by the user. In order to simplify the code and to not display unused sections, the layout is created dynamically.

Everything is inside a fragment. The fragment has a LinearLayout used as a container and, when the fragment is created, I generate all the necessary sections.

Each section is managed by its own local adapter which is in charge to inflate the layout of this section and to add it into the container.

Everything works perfectly fine. The issue is that 2 sections have the exact same structure so they share the same xml layout. Because of that the internal views of both sections have the same id. This is not an issue as the section is managed locally in its adapter. The problem appears when I go to the next fragment and then go back to this one. The system tries to recover the previous state of the view and, because these 2 sections have the same ids, when the second section is restored, its values are set to the first one too.

Is there any solution to manage that or to tell the fragment to not restore its state (as everything is manually reloaded anyway).

Here is an light example of the current structure:

fragment xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

section xml

<EditText xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/section_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

fragment code

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View view = inflater.inflate(R.layout.fragment_layout, container, false);

    if (<condition>)
       createSection1(getContext(),view);

    if (<condition>)        
       createSection2(getContext(),view);

    return view;
}


private void createSection1(Context context, ViewGroup root){
    Section1Adapter adapter = new Section1Adapter(context, root);
    // ...
}

private void createSection2(Context context, ViewGroup root){
    Section2Adapter adapter = new Section2Adapter(context, root);
    // ...
}

adapter code (same idea for both)

public Section2Adapter(LayoutInflater inflater, ViewGroup root) {

    View view = LayoutInflater.from(context).inflate(R.layout.section_layout, root, false);

    initView(view);

    root.addView(view);
}
Kamat answered 22/8, 2017 at 12:43 Comment(0)
P
13

Your issue, as you correctly said, is that basically, you are in this state: enter image description here

What you need to do, is to tell Android yourself at what key in the SparseArray to save the state of which EditText. Basically, you need to get to this:

enter image description here

The mechanism through which you achieve this is very well explained in this amazing article, by Pasha Dudka. (credits to him for the nice images, also)

Just search for "View IDs should be unique" in the article, and you'll have your answer.

The gist of the solution for your particular situation is one the following:

  • you can subclass LinearLayout s.t. your CustomLinearLayout will know the Section a child belongs to, when its state. This way, you can save all child states within a section to a SparseArray dedicated just for that section, and add the dedicated SparseArray to the global SparseArray (just like in the image)
  • you can subclass EditText, s.t. your CustomEditText know in which section it belongs to, and will save its state at a custom key in the SparseArray - e.g. section_text_Section1 for the first section, and section_text_Section2 for the second one

Personally, I prefer the first version, since it will work even if you later add more views to your Sections. The second will not work with more views, since in the second it's not parent that does the smart state saving, but the view itself.

Hope this helps.

Pitchdark answered 22/8, 2017 at 13:37 Comment(5)
Wahoo! Amazing answer! Thx very much as I didn't know about that.This is exactly what I was looking for.Kamat
I just have to say this really, really saved me so much time and pain. Thank you a lot for this!Caravel
Link is broken. An archived version can be found here.Nard
This is a wrong design by Android because they incentivize to use ViewBinding and custom views with the layout. Creating a custom view for every container is a pain and is wrong.Unbreathed
"This is a wrong design by Android [...]". Just one of many (-:Pitchdark
U
1

The accepted solution is awesomely explained, but sometimes creating custom container classes doesn't seem fit.

One simple solution can be to assign ID programatically.

Ids are not inmutable, so you may change the ids during your view creation.

Let's say you inflate some views my_custom_view.xml which have some EditText named nameEditText. At the view creation you could have the following (with view binding):

binding.view1.nameEditText.id = R.id.pageX_field1
binding.view2.nameEditText.id = R.id.pageX_field2

This way the save state will store the values based on the given ids. The restoration of state will happen after the creation of the view.

You may create your ids on a resource file for this purpose:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <!-- PAGE X IDS -->
    <item type="id" name="pageX_field1" />
    <item type="id" name="pageX_field2" />

</resources>
Unbreathed answered 12/1, 2023 at 5:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.