I have a class like following:
class SelectedUriData(
val uri: Uri,
val type: String
) {
object Saver : androidx.compose.runtime.saveable.Saver<MutableState<SelectedUriData?>, String> {
override fun restore(value: String): MutableState<SelectedUriData?> {
if (value.isEmpty()) {
return mutableStateOf(null)
} else {
val parts = value.split("\\|", limit = 2)
val type = parts[0]
val uri = parts[1].toUri() // this throws the exception above..
return mutableStateOf(SelectedUriData(uri, type))
}
}
override fun SaverScope.save(value: MutableState<SelectedUriData?>): String {
val data = value.value
return if (data == null) {
""
} else {
"${data.type}|${data.uri}"
}
}
}
}
Usage
val selectedData = rememberSaveable(
saver = SelectedUriData.Saver
) { mutableStateOf(null) }
Crash
I got crashes that report following:
java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
at java.util.Collections$SingletonList.get(Collections.java:5042)
at com.my.app.classes.SelectedUriData$Saver.restore(SelectedUriData.kt:24)
at com.my.app.classes.SelectedUriData$Saver.restore(SelectedUriData.kt:17)
at androidx.compose.runtime.saveable.RememberSaveableKt.rememberSaveable(RememberSaveable.kt:87)
at com.my.app.ui.dialogs.DialogImportKt.DialogImport(DialogImport.kt:79)
...
Question
As far as I see it, this exception is impossible to reach, isn't it? I'm using androidx.compose.runtime.saveable 1.7.6
.
My saver will in any case either save ""
or ".*|.*"
and I do handle both cases in the restore function.
Any ideas?
The problem is that you split the string at "\\|"
. That is a string containing a backslash and a pipe character, whereas your saved string only uses a pipe character. Therefore the split function doesn't find anything to split and only returns a list with one entry, which contains the entire input string.
Then you try to access the second item in the list, which is not available - hence the IndexOutOfBoundsException.
The fix is simple, use the actual delimiter instead:
value.split("|", limit = 2)
That said, there are two alternatives that would work even better:
Since you want to save multiple Strings at once, you can employ a listSaver
instead:
companion object {
val Saver: androidx.compose.runtime.saveable.Saver<MutableState<SelectedUriData?>, Any> =
listSaver<MutableState<SelectedUriData?>, String>(
save = { state ->
state.value
?.let { listOf(it.type, it.uri.toString()) }
?: emptyList()
},
restore = {
if (it.isEmpty()) {
mutableStateOf(null)
} else {
val type = it[0]
val uri = it[1].toUri() // this throws the exception above..
mutableStateOf(SelectedUriData(uri, type))
}
},
)
}
This way you do not need to implement the split and merge manually. It would have prevented your issue in the first place.
Better yet, since you only want to store Strings which are already parcelable, just make your SelectedUriData
class Parcelable as well and you can remove the custom Saver completely:
@Parcelize
class SelectedUriData(
val uri: Uri,
val type: String,
) : Parcelable {
// no explicit saver needed
}
Then rememberSaveable
can handle everything automatically under the hood. You can simply use this:
rememberSaveable { mutableStateOf(SelectedUriData(...)) }
Just make sure you apply the kotlin-parcelize
plugin in your gradle file, as the link above explains.