Jetpack Compose Navigation:How to pass an array as an argument?
Asked Answered
A

1

8

I have a filter dialog that shows filter options :

enter image description here

When I click on "Parent Platform" section I'm navigating to the PlatformsScreen with two arguments. Argument one is parent which is int and the second is platforms which is an array of int :

sealed class Screen(val route: String, val arguments: List<NamedNavArgument>) {

    object PlatformScreen : Screen(PLATFORM_ROUTE,arguments = 
        listOf(navArgument("parent", builder = {type = NavType.IntType}),
        navArgument("platforms",builder = {type = NavType.IntArrayType})))
}


// click action
@Composable
fun FilterDialog(
    modifier: Modifier = Modifier,
    viewModel: FilterViewModel,
    navigateToPlatformScreen: (Int,Array<Int?>) -> Unit
) {
 val filterState by viewModel.filterState.collectAsState()
 Row(//other stuff
       .clickable {
                    navigateToPlatformScreen(filterState.parentPlatform?.id ?: -1,
                    filterState.platforms?.map { it?.id }?.toTypedArray() ?: arrayOf(-1))
                }
)
  //here is my filter bottom sheet
   bottomSheet(route = FILTER_ROUTE) {
        FilterDialog(viewModel = viewModel,
            navigateToPlatformScreen = { parentId,platforms ->
                navController.navigate("${Screen.PlatformScreen.route}?parent=${parentId}&platforms=${platforms}")
            })
    }

    //platforms screen
    bottomSheet(route = Screen.PlatformScreen.route+"?parent={parent}&platforms={platforms}",arguments = Screen.PlatformScreen.arguments) { backstackEntry ->
        val parent = backstackEntry.arguments?.getInt("parent")
        val platforms = backstackEntry.arguments?.getIntArray("platforms")
        PlatformScreen(parent = parent,platforms = platforms)
    }  

And as the last part, here is my FilterViewModel that holds my parentId and platforms array :

class FilterViewModel : ViewModel() {

    private val _selectedParentPlatform = MutableStateFlow<ParentPlatform?>(ParentPlatform())

    private val _filterState = MutableStateFlow(FilterState())

    val filterState:StateFlow<FilterState> get() = _filterState

    init {
        viewModelScope.launch {
           _selectedParentPlatform.mapLatest {
               FilterState(
                   parentPlatform = it,
                   platforms = it?.platforms
               )
           }
                .catch { cause ->

                }
                .collectLatest {
                    _filterState.value = it
                }
        }
    }
}

I have no problem with parent argument. I can pass it with no problem however the array type causing a crash with the following error: java.lang.UnsupportedOperationException: Arrays don't support default values.

Alienable answered 7/2, 2022 at 23:14 Comment(2)
Did you read the docs on passing custom types? It even uses search filters as its example.Outstrip
I did not see this. This solved my problem. Instead of just passing id and child platform ids as an argument I can pass the whole object as an argument now. Thank you so much.Alienable
T
1

When I see the docs, I look in the code of the NavType.IntArrayType to find out how it converts String (value from key-value query parameters of the screen route) to IntArray. There I found these two methods of parsing String into IntArray:

    public val IntArrayType: NavType<IntArray?> = object : NavType<IntArray?>(true) {
        //other methods skipped

        
        override fun parseValue(value: String): IntArray {
            return intArrayOf(IntType.parseValue(value))
        }

        override fun parseValue(value: String, previousValue: IntArray?): IntArray {
            return previousValue?.plus(parseValue(value)) ?: parseValue(value)
        }
    }
  • parseValue(value: String): converts single String value to a IntArray containing only one value.
  • parseValue(value: String, previousValue: IntArray?): add the new value to the previousValue array.

From this, I get to the conclusion that we can give an array as multiple key-value pair with the same key in query params then it will append all the value in a single array. Example :

screen_name?platforms=1&platforms=2&platforms=3

will result in IntArray containing [1,2,3] for key "platforms". For the first key (platforms=1), it will use parseValue(value: String) to convert that Int to IntArray. Thereafter, it will use parseValue(value: String, previousValue: IntArray?) to append further key values.

I have made an extension function for 'IntArray' and a generic extension function for any type of Array to convert them into nav args query parameters. You can use this in this way:

//function for only IntArray. You have to make function for each type 
//of array (LongArray, FloatArray etc)
fun IntArray.toNavArgs(key: String) : String = buildString {
    [email protected]{ i, item ->
        if(i > 0) append("&")
        append("$key=$item")
    }
}

//generic function for any type of array
fun Array<*>.toNavArgs(key: String) : String = buildString {
    [email protected]{ i, item ->
        if(i > 0) append("&")
        append("$key=$item")
    }
}
    //here is my filter bottom sheet
    bottomSheet(route = FILTER_ROUTE) {
        FilterDialog(viewModel = viewModel,
            navigateToPlatformScreen = { parentId: Int,platforms: Array<Int?> ->
                navController.navigate(
                    "${Screen.PlatformScreen.route}?" +
                            "parent=${parentId}" +
                            //add the platforms in the route if it is not empty
                            if(platforms.isNotEmpty()) 
                                "&${platforms.toNavArgs("platforms")}" 
                            else ""
                )
            })
    }

    //platforms screen
    bottomSheet(route = Screen.PlatformScreen.route+"?parent={parent}&platforms={platforms}",arguments = Screen.PlatformScreen.arguments) { backstackEntry ->
        val parent = backstackEntry.arguments?.getInt("parent")
        
        //get the IntArray from the arguments
        val platforms = backstackEntry.arguments?.getIntArray("platforms")
        
        PlatformScreen(parent = parent,platforms = platforms)
    }  

Tartarus answered 8/2 at 18:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.