1

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.

GianhTran
  • 3,443
  • 2
  • 22
  • 42
  • Why are you using your custom EditText (com.sogia.replacefragmentdemo.CustomView) and not the default one? – sebasira Sep 13 '18 at 03:53
  • @sebasira just for demo this issue, in real code, I using more view – GianhTran Sep 13 '18 at 04:08
  • If you use a default EditText, do you have the same behavior or the problem is gone? If it is gone, then probably the problem is in your custom view. Can you post it if the error is only when using your custom view? – sebasira Sep 13 '18 at 11:37
  • @sebasira yes, the issue occur only when I using custom view, but you can see my `CustomView`, it's very clearly and I can not see any problem – GianhTran Sep 14 '18 at 01:48
  • 1
    I had a similar issue, have a look at the answer to [this question](https://stackoverflow.com/q/45818015/1827254) – Eselfar Sep 14 '18 at 02:30
  • @Eselfar awesome, finally I got it, thank you so much – GianhTran Sep 17 '18 at 06:14

4 Answers4

9

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)
    }
GianhTran
  • 3,443
  • 2
  • 22
  • 42
4

You're not adding to the back stack, you're replacing, try this to open the second fragment, this will keep the state of FirstFragment intact and in stack

btn_next.setOnClickListener {
            val transaction = requireActivity().supportFragmentManager!!.beginTransaction()
                .add(R.id.contentFrame, SecondFragment(), "")
            commitTransaction(transaction, true, -1)
        }

Also both of your EditTexts have the same ids android:id="@+id/edt" thus the synthesizer function which runs findViewById will point to the same object of EditText

MadScientist
  • 2,134
  • 14
  • 27
  • 1
    I already add fragment to back stack in line 'commitTransaction(transaction, true, -1)', I also think 'kotlinx.android.synthetic' is root cause, but as you see, my code is clearly, I will focus on this point and tell you the result when I come back – GianhTran Sep 13 '18 at 09:07
  • I have answer for this question, that a pity I can not accept your answer because it has no solution, but anw, thank you so much (+1) – GianhTran Sep 17 '18 at 06:41
  • 1
    No worries, please do post your answer for knowledge sake. Thank you. – MadScientist Sep 17 '18 at 06:42
  • I did :) please see below – GianhTran Sep 17 '18 at 06:43
0

I would try adding a different ID to each of your custom views in the layout. Like :

com.sogia.replacefragmentdemo.CustomView
    android:id="@+id/custom1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
/>

com.sogia.replacefragmentdemo.CustomView
    android:id="@+id/custom2"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    />
sebasira
  • 1,739
  • 1
  • 22
  • 41
0

Having same id can be a problem as managing state could get messy. Check this one for an solution: Restore fragment with two views having the same id