0

I'm having an issue where I assign a variable based on data that is being received from a server (in JSON). I have two class variables

  1. teams - which will be changed based on user interaction
  2. reset_teams - which should always stay the same as the original list so that a user can reset all values whenever they choose.

These variables are used to (re-)populate a RecyclerView at various times based on user interaction (i.e., pressing a button inside of the app)

My problem is that any time I make a change to an object in the teams ArrayList, it also makes a change to the reset_teams ArrayList, as if they are somehow connected (similar to pass by value/reference in a function).

I am somewhat new to Kotlin, so it's quite possible I am missing something, but I'm wondering how I can keep the reset_teams ArrayList exactly the same at all times, even if the teams ArrayList has changes to different properties of the objects it stores?

// Class variables
var teams: ArrayList<myObject> = ArrayList()
var reset_teams: ArrayList<myObject> = ArrayList()

// Function to handle JSON after receiving data from remote server
private fun handleJson(jsonString: String?) {
    val jsonArray = JSONArray(jsonString)

    val list = ArrayList<myObject>()

    var x = 0

    while (x < jsonArray.length()) {
        val jsonObject = jsonArray.getJSONObject(x)


        list.add(
            myObject(// a bunch of code to make an object of type myObject)
        )
        x++
    }

    teams = list
    reset_teams = list

    test()  // Change a property
}


private fun test(){
    teams[0].someProperty = 10
    println(teams)      // The someProperty of the first element of the ArrayList has been correctly assigned to 10
    println(reset_teams) // The someProperty of the first element of the ArrayList has been changed to 10 even though the reset_teams was never assigned a new value
}
M. Black
  • 353
  • 1
  • 4
  • 20

1 Answers1

1

By assigning

teams = list
reset_teams = list

both list teams and list reset_teams reference the same list (space) in memory (heap). With teams[0].someProperty = 10 you change someProperty of the first element of this referenced list to 10. Since reset_teams references the same list, it also shows the changes.

In order to fix this unintended behaviour you will have to add copies of the objects to both lists teams and reset_teams. If class myObject is a data class, then you can add a copy by calling teams.add(m.copy()) where m is an instance of class myObject.

Remarks:

  • use camelCase for variable names: resetTeams instead of reset_teams
  • start class names with a capital letter: MyObject instead of myObject
  • prefer immutable objects if possible
Michael Kreutz
  • 1,216
  • 6
  • 9
  • Thanks for your comment Michael. That makes sense to me and I understand why the reference to the same memory is causing the issue. Can you explain to me how to 'add copies of the objects to both lists'? I tried setting teams = ArrayList(list) and reset_teams = ArrayList(list) both this does not fix the problem. Thanks – M. Black May 12 '20 at 20:21
  • 1
    Hmmm, that ought to fix the problem…  Can you double-check?  A similar alternative: `teams.clear(); teams.addAll(list); reset_teams.clear(); reset_teams.addAll(list)`.  (You could then make their references `val`s.  Or if you create new lists, you could make their references `List`, i.e. nullable and immutable.  In general, I find it's often a code smell to have a mutable list with a mutable reference.) – gidds May 12 '20 at 20:42
  • 1
    By the way, it's conventional to use camelCase, not snake_case, for identifiers in Kotlin: `resetTeams` instead of `reset_teams`.  And PascalCase for classes and object types: `MyObject` instead of `myObject`.  (That won't affect how it runs, of course, but would make it slightly easier for idiots like me to follow!) – gidds May 12 '20 at 20:48
  • Thanks for the Kotlin conventions. I will adhere to that moving forward. For consistency's sake in this thread I will continue using the original that I posted. It is strange, even if I: teams.clear(); teams.addAll(list); reset_teams.clear(); reset_teams.addAll(list); teams[0].someProperty = 99 println(reset_teams) println(teams) both lists are exactly the same. They are also declared as 'val' – M. Black May 13 '20 at 00:15
  • I created a bit of a workaround that does work, but it unfortunately doesn't really solve the problem. Oh well, c'est la vie – M. Black May 13 '20 at 00:41
  • if class `myObject` is a data class then you can simply call `.copy()` on an instance in order to place a copy in your list. Besides I totally agree with @gidds remarks! – Michael Kreutz May 13 '20 at 07:33
  • Ah!  Light dawns.  I suspect what's happening is that you're now creating a completely new list each time — but that list references the same `myObject` instances as the original.  So if you change the state of a `myObject` instance via one list, it'll be visible through the others.  (Hard to be sure without seeing the code for `myObject`, but seems very likely.)  If so, what you need is a ‘deep copy’ of the list, one that creates new `myObject` instances (replicating the state of the originals)… (contd) – gidds May 13 '20 at 08:36
  • …As Michael Kreutz says, calling `copy()` (if available) goes part-way toward that, but it's not a full solution: if `myObject` contains any references to _other_ objects, `copy()` would simply copy _those_ references.  A proper deep copy isn't easy; see [this question](https://stackoverflow.com/questions/47359496/kotlin-data-class-copy-method-not-deep-copying-all-members). You may need to rethink your approach with that in mind. – gidds May 13 '20 at 08:39