Home > other >  UnsupportedOperationException when adding to a map in kotlin
UnsupportedOperationException when adding to a map in kotlin

Time:11-06

I have a crash, that only happened once in my app, due to an UnsupportedOperationException when I try to add an item to a map. For the stacktrace it seems that an AbstractMap is instantiated instead of a MutableMap. The code is in kotlin:

val productMap: MutableMap<ProductModel, Int> =
            binding.myView.getProductMap() as? MutableMap<ProductModel, Int>
                ?: mutableMapOf()

            presenter.getProduct()?.let {
                productMap.put(it, 0)
            }

Could it be that kotlin/java did something weird behind curtains or is there anything I am missing?

The stacktrace:

Fatal Exception: java.lang.UnsupportedOperationException
       at java.util.AbstractMap.put(AbstractMap.java:218)
       at com.package.MyView.method2(MyView.java:108)
       at com.package.MyParentView.method1(MyParentView.java:1278)

CodePudding user response:

I don't know if this is what happens here, but we should not really cast read-only types to mutable like this. If the return type of getProductMap() is Map, not MutableMap then this is probably for a reason and we should not try to use it as it is mutable.

For example, buildList(): List utility actually returns a list that implements MutableList, but is in fact read-only and throws exceptions when we try to modify it. Similarly, collections returned from utils like Collections.unmodifiableMap() can be cast to mutable.

I think what you really need to do here is to create a mutable copy of the data in the map:

binding.myView.getProductMap().toMutableMap()

Or, if getProductMap() returns nullable:

binding.myView.getProductMap()?.toMutableMap() ?: mutableMapOf()

As a side note, your code seems strange to me. It uses the contents of the source map if it is mutable, but it ignores its contents if it is not.

CodePudding user response:

I think the issue here is that MutableMap is a mapped type: the Kotlin compiler knows about the difference between Map and MutableMap, but they both compile down to java.util.Map in the Java* bytecode. That's because the underlying Java classes don't distinguish mutable from immutable collections; they use the same interface for both, and immutable implementations simply throw UnsupportedOperationException.

So in this case, the as? safe cast doesn't do what you want: it's effectively checking only whether the object is a Map, not whether it's mutable. (I don't know Android, but if getProductMap() is defined to return a Map, then that boils down to a simple null check.)

There are several approaches to fixing this, depending on your needs, e.g.:

  • If you happen to know the concrete, mutable type you expect (such as ArrayList), then you could cast to that. Since ArrayLists are always mutable, the put() should then be safe; but it would do nothing any other type of map was provided.

  • You could trap the UnsupportedOperationException in a trycatch block. This would avoid the error, but do nothing if an immutable map was provided.

  • You could create a mutable map from the given map, using toMutableMap(). This would always update the map — but it might be a private map not used by the view.


(* The stack trace shows that this question is about Kotlin/JVM.  You might get different results on other platforms.)

  • Related