Object is changed after sending it to another Fragment

情到浓时终转凉″ 提交于 2021-02-11 06:16:07

问题


So, I have a really weird issue, I pass an object from Fragment A to Fragment B , I modify this object in a new instance in Fragment B, but after I change a value on this object it also changes that value when I pop Framgment B and that object keeps modified now also for Fragment A

Fragment A

...

   override fun onItemClick(v: View?, position: Int) {
        searchView.clearFocus()
        val bundle = Bundle()
        bundle.putSerializable("shop", landingAdapter.getItem(position))
        findNavController().navigate(R.id.action_navigation_landing_to_shopFragment, bundle)
    }

...

Now, from Fragment B I get this object

Fragment B

    private lateinit var shop: Shop
    private lateinit var shopAdapter:ShopAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        shopAdapter = ShopAdapter(sharedViewModel, requireContext(),this)
        arguments?.let {
             shop = it.getSerializable(ARG_SHOP) as Shop
            if (shop.products.isNotEmpty()) {
                shopAdapter.setItems(shop.products)
            }
        }
    }

Now, after I get this Shop object from Fragment A, I modify it in Fragment B only with

onViewCreated(){

    shop.quantity = 1

}

but when I go back to Fragment A, now that Shop object quantity value is 1 , but it should be nothing since I have only changed the object at Fragment B not Fragment A , and in Fragment B is a new instance of that object

I'm really confused

EDIT

What I have tried so far to send a fresh instance each time I go from Fragment A to Fragment b

   val bundle = Bundle()
                bundle.putSerializable("shop", landingAdapter.getItem(position).copy())  

findNavController().navigate(R.id.action_navigation_landing_to_shopFragment, bundle)



         val bundle = Bundle()
                val shop = landingAdapter.getItem(position)
                bundle.putSerializable("shop", shop)  findNavController().navigate(R.id.action_navigation_landing_to_shopFragment, bundle)


         val bundle = Bundle()
                val shop = Shop(landingAdapter.getItem(position).name,landingAdapter.getItem(position).quantity)
                bundle.putSerializable("shop", shop)  
    findNavController().navigate(R.id.action_navigation_landing_to_shopFragment, bundle)

None of them sends a fresh instance of shop to Fragment B, so whenever I change quantity at fragment B, fragment A gets the same quantity value which should not mutate


回答1:


This is actually not an obvious question with an obvious answer. I got this question about 2 months ago and it confused me as well, as this is sometimes the behavior you get, and sometimes not.

First thing to note is, when you give arguments to a Fragment, you put them in a Bundle. This bundle internally stores a Map for string keys.

So when you call fragment.setArguments(bundle), you're basically "sending a map".

Therefore, as no IPC happens (unlike in Activities, where you talk to Android through an Intent), the same object instance exists in the map.

That is until the properties of the Bundle arguments are used by the system, and the instance is indeed recreated.

After process death (you have the app in background, Android reclaims for memory, and on restart Android rebuilds your task stack and recreates the active Fragments based on existing history saved into onSaveInstanceState from the FragmentManager), the originally passed arguments are used to "recreate" the incoming properties.

At this time, the Serializable was recreated by the system, and if you were to navigate back, it would be a different instance than the one you had originally sent.

Therefore, you get the same instance because Bundle is internally a map.

You can also get a different instance because Android can recreate it.

Solution: use a copy before sending your instance for consistent behavior.

EDIT:

This also applies for nested objects inside mutable lists. Therefore, if you have classes like

class A(
    private val list: List<B>
)

And

class B(
    private var blah: String
)

Then if B is mutated after sending A over through the Bundle, then the List<B> has the Bs in it change, and this will reflect on both screen, UNLESS after process death.

So that's something to keep in mind.

To create a copy, you could do

val copyList = a.list.map { it.copy() }



回答2:


Object references are passed by value

All object references in Kotlin are passed by value. This means that a copy of the value will be passed to a method. But the trick is that passing a copy of the value also changes the real value of the object. To understand why, try this example:

object  ObjectReferenceExample {
 fun transform(p:Person) {
        p.name = "Person2";
 }
}

class Person(var name:String) 
   fun main() {
     val  person =  Person("Person1");
     ObjectReferenceExample.transform(person);
     System.out.println(person.name);// output are Person2
}

The reason is that Kotlin object variables are simply references that point to real objects in the memory heap. Therefore, even though Kotlin passes parameters to methods by value, if the variable points to an object reference, the real object will also be changed. the same are applied to java



来源:https://stackoverflow.com/questions/62363268/object-is-changed-after-sending-it-to-another-fragment

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!