Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Restore fragment with two views having the same id

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);
}
like image 677
Eselfar Avatar asked Aug 22 '17 12:08

Eselfar


People also ask

Is it possible to reuse a fragment in multiple screens?

Yes you can, but you have to add more logic to your fragments and add some interfaces for each activity.

How do you reattach a fragment?

when your activity is recreated, the fragment gets destroyed. So you have to create new instance of fragment and add it again. Ok, but you cant retain the fragment view. If you want to retain the data which is in fragment, then use onSaveInstanceState() and onRestoreInstanceState() .

Can a fragment have its own view?

Fragments cannot live on their own--they must be hosted by an activity or another fragment. The fragment's view hierarchy becomes part of, or attaches to, the host's view hierarchy. Note: Some Android Jetpack libraries, such as Navigation, BottomNavigationView , and ViewPager2 , are designed to work with fragments.

How do I know if I have Framelayout fragments?

You have to use findFragmentByTag() or findFragmentById() methods to find it. If returned instance is not null, then the fragment is present. Additionally you can use Fragment. isInLayout() method to check whether fragment was defined in layout using <fragment> tag or added programmatically.


1 Answers

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.

like image 133
cjurjiu Avatar answered Oct 27 '22 00:10

cjurjiu