Search code examples
androidandroid-fragmentsfragment-backstack

All Editexts be replace by same value after pop a Fragment


I have a strange issue, I have 2 fragment, in first fragment, I have some custom EditText, and a button to replace this by second fragment (addToBackStack = true), then, in second fragment, I try to using popBackStack() to back to first fragment, the issue occur, all custom EditText have same value.

error

Below is all my code

FirstFragment

class FirstFragment : BaseFragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        btn_next.setOnClickListener {
            val transaction = requireActivity().supportFragmentManager!!.beginTransaction()
                .replace(R.id.contentFrame, SecondFragment(), "")
            commitTransaction(transaction, true, -1)
        }
    }
}

SecondFragment

class SecondFragment : BaseFragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_second, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        btn_back.setOnClickListener {
            requireActivity().supportFragmentManager.popBackStack()
        }
    }
}

fragment_first.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >
    <com.sogia.replacefragmentdemo.CustomView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

    <com.sogia.replacefragmentdemo.CustomView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />
    <Button
        android:id="@+id/btn_next"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="next"
        />
</LinearLayout>

fragment_second.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <Button
        android:id="@+id/btn_back"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="back"
        />
</LinearLayout>

CustomView

class CustomView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
    init {
        inflate(context, R.layout.layout_custom_view, this)
    }
}

layout_custom_view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >
    <EditText
        android:id="@+id/edt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="input something"
        />
</LinearLayout>

Any response would be appreciated.


Solution

  • Finally I find out the root cause and the solution, Thanks @abhradeep ghosh, @Eselfar, @MadScientist and another one else who response this post.

    Cause: view IDs should be unique! Otherwise your state will be overwritten by another view with the same ID. In my case I have 2 views with id @id/edt, so my states container holds only 1 instance of it - whichever came last during state store process.

    Here is my solution, (from this post),

    First, create new class for save sate of view

    class SavedState(superState: Parcelable) : View.BaseSavedState(superState) {
        var childrenStates: SparseArray<Any>? = null
    
        override fun writeToParcel(out: Parcel, flags: Int) {
            super.writeToParcel(out, flags)
            childrenStates?.let {
                out.writeSparseArray(it)
            }
        }
    }
    

    And in my CustomView

    @Suppress("UNCHECKED_CAST")
        public override fun onSaveInstanceState(): Parcelable? {
            val superState = super.onSaveInstanceState()
            val ss = SavedState(superState)
            ss.childrenStates = SparseArray()
            for (i in 0 until childCount) {
                getChildAt(i).saveHierarchyState(ss.childrenStates as SparseArray<Parcelable>)
            }
            return ss
        }
    
        @Suppress("UNCHECKED_CAST")
        public override fun onRestoreInstanceState(state: Parcelable) {
            val ss = state as SavedState
            super.onRestoreInstanceState(ss.superState)
            for (i in 0 until childCount) {
                getChildAt(i).restoreHierarchyState(ss.childrenStates as SparseArray<Parcelable>)
            }
        }
    
        override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
            dispatchFreezeSelfOnly(container)
        }
    
        override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
            dispatchThawSelfOnly(container)
        }