androidandroid-jetpack-composeandroid-architecture-navigationandroid-navigation

App is crashing while trying to pass values from one screen to other


Project Overview I am making an app, it is like a Pokedex . It can give detail to any of the pokemon you entered.

Problem In that app only, there is a button as "Find Location" which is in PokemonInfoLayout.kt. When clicked, my app is getting crash. It is crashing only when I try to pass the value from one screen to other .Otherwise if try to navigate from one screen to other without passing the value it is working totally fine. One more keyPoint to remember -

  1. I had checked that the values that is passed while navigating from pokemonInfoLayout screen to location screen is not null.
  2. There is no issue regarding fetching of data from internet. It is also working perfectly as expected. The issue is only while I try to navigate with the value.

@Composable
fun PokedexScreen(
    viewModel: PokedexViewModel = androidx.lifecycle.viewmodel.compose.viewModel(),
    navController:NavController

) {

    val scope:CoroutineScope = rememberCoroutineScope()

    // Taking pokemon name from the user
    var pokName by remember {
        mutableStateOf("")
    }

    // Taking current screen's context
    val context = LocalContext.current

    //This name is send to the screen which shows the location of pokemon
    var sendName :String = pokName





    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.Yellow)
    ) {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            OutlinedTextField(
                value = pokName,
                onValueChange = {
                    pokName = it
                },
                label = { Text(text = "Enter the pokemon name: ") },
                singleLine = true,
                shape = RoundedCornerShape(16.dp),
                modifier = Modifier.background(Color.White, RoundedCornerShape(16.dp))
            )
            Spacer(modifier = Modifier.height(8.dp))
            Button(
                onClick = {
                    Toast.makeText(context, "Pokemon searching is in progress", Toast.LENGTH_LONG).show()
                    scope.launch{
                        try {
                            viewModel.fetchPokDetails(pokName.lowercase(Locale.ROOT).trim())

                        }catch (e:Exception){
                            e.printStackTrace()
                        }finally {
                            sendName = pokName
//                            pokName = ""
                        }

                    }

                }
            ) {
                Text(text = "Search")
            }
        }

        Spacer(modifier = Modifier.height(8.dp))


        PokemonInfoLayout(
            onFindPokemonClicked = { pokName, locationUrl ->
                // Pass values to the PokemonLocationDetailScreen
                navController.navigate(
                    "${Screens.OtherScreen.LocationDetailScreen.route}/$pokName/$locationUrl"
                )
            }

            ,
            onExperienceClicked = {  },
            context = context,
        )
    }
}


Location detail screen

@Composable
fun PokemonLocationDetailScreen(
    viewModel: PokedexViewModel = androidx.lifecycle.viewmodel.compose.viewModel(),
    onBackNavClicked: () -> Unit,
    navController: NavController,
) {
    // Retrieve values from the previousBackStackEntry
    val pokName = navController.currentBackStackEntry?.arguments?.getString("pokName") ?: "raichu"
    val url = navController.currentBackStackEntry?.arguments?.getString("locationUrl") ?: "https://pokeapi.co/api/v2/pokemon/132/encounters"

    LaunchedEffect(viewModel) {
        if (url != "No URL") {
            viewModel.fetchPokemonLocationDetail(url)
        } else {
            Log.d("Location_Screen", "Error Occurred in fetching details.")
        }
    }
    val pokemonLocationDetailState by viewModel.locationState.observeAsState()

    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Text(text = Screens.OtherScreen.LocationDetailScreen.otherTitle + pokName)
                },
                navigationIcon = {
                    IconButton(onClick = { onBackNavClicked() }) {
                        Icon(
                            imageVector = Icons.AutoMirrored.Filled.KeyboardArrowLeft,
                            contentDescription = "Go Back"
                        )
                    }
                },
                backgroundColor = Color.Cyan
            )
        }
    ) {
        when {
            
            pokemonLocationDetailState?.isLoading == true ->{
                CircularProgressIndicator(modifier = Modifier.padding(it))
            }
            
            pokemonLocationDetailState?.error != null ->{
                Text(text = "Error Occured!!!")
            }
            
            pokemonLocationDetailState == null ->{
                Text(text = "Can't fetch detail")
            }
            
            else ->{

                LazyColumn {
                    items(pokemonLocationDetailState!!.locationDetail) {
                        Text(text = it.location_area.name)
                        }
                    }
                }

            }
        }
    }

Pokemon info layout screen

@Composable
fun PokemonInfoLayout(
    onFindPokemonClicked: (pokName: String,locationUrl: String) -> Unit,
    onExperienceClicked: () -> Unit,
    context: Context,
    viewModel: PokedexViewModel = androidx.lifecycle.viewmodel.compose.viewModel(),
) {

    val pokemonDetailState by viewModel.state.observeAsState()
    var locationUrl = pokemonDetailState?.details?.location_area_encounters
    var pokName = pokemonDetailState?.details?.name




    var isFavoriteSelected by remember {
        mutableStateOf(false)
    }
    when{
        pokemonDetailState?.isLoading == true ->{
            CircularProgressIndicator()
        }

        pokemonDetailState?.error != null->{
            Text(text = "Error Occurred !! ${pokemonDetailState!!.error}")
        }

        pokemonDetailState == null -> {
            Text(text = "Can't fetch the state.")
        }

        else ->{
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
                    .clickable {
                        //TODO to show user what they had written about this pokemon
                    },
                backgroundColor = Color.Magenta,
                contentColor = Color.White,
                shape = RoundedCornerShape(8.dp)
            ) {
                Box(
                    modifier = Modifier
                        .fillMaxSize()
                ) {
                    Column(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(16.dp)
                            .verticalScroll(rememberScrollState())
                    ) {
                        Image(
                            painter = painterResource(id = R.drawable.baseline_home_24),
                            contentDescription = null,
                            modifier = Modifier
                                .fillMaxWidth()
                                .height(250.dp)
                        )

                        pokemonDetailState?.details?.let {
                            detail->
                            Text(
                                modifier = Modifier.fillMaxWidth(),
                                text = detail.name.uppercase(Locale.ROOT),
                                textAlign = TextAlign.Center,
                                fontWeight = FontWeight.ExtraBold,
                                fontSize = 20.sp
                            )
                        }

                        Text(text = "The type of this pokemon is: ", fontWeight = FontWeight.ExtraBold)

                        pokemonDetailState?.details?.let {
                            detail->
                            detail.types.forEachIndexed { index, types ->
                                Text(text = "${index + 1}. ${types.type.name}")
                            }
                        }
                        Spacer(modifier = Modifier.height(8.dp))

                        Text(text = "It has moves as follows... ", fontWeight = FontWeight.ExtraBold)

                        pokemonDetailState?.details?.let {
                            detail->
                            detail.moves.forEachIndexed { index, moves ->
                                Text(text = "${index + 1}. ${moves.move.name}")

                            }
                        }
                        Spacer(modifier = Modifier.height(8.dp))


                        Text(text = "It has the ability of ...", fontWeight = FontWeight.ExtraBold)

                        pokemonDetailState?.details?.let {
                            detail->
                            detail.abilities.forEachIndexed { index, abilities ->
                                Text(text = "${index+1}. ${abilities.ability.name}")

                            }
                        }
                        Spacer(modifier = Modifier.height(8.dp))


                        Text(text = "It has items as follows...", fontWeight = FontWeight.ExtraBold)

                        pokemonDetailState?.details?.let {
                            detail->
                            detail.held_items.forEachIndexed { index, heldItems ->
                                Text(text = "${index + 1}. ${heldItems.item.name}")
                            }
                        }
                        Spacer(modifier = Modifier.height(8.dp))

                        pokemonDetailState?.details?.location_area_encounters?.let { Text(text = it) }

                        Spacer(modifier = Modifier.height(8.dp))

                        Row(
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(8.dp),
                            verticalAlignment = Alignment.CenterVertically,
                            horizontalArrangement = Arrangement.SpaceEvenly
                        ) {
                            Button(onClick = {
                                Toast.makeText(context, "Enter Your Experience", Toast.LENGTH_SHORT).show()
                                onExperienceClicked()
                            }) {
                                Text(text = "Experience?")
                            }
                            if (!isFavoriteSelected) {
                                IconButton(onClick = {
                                    //TODO Save it on favorites list
                                    isFavoriteSelected = true
                                }) {
                                    Icon(imageVector = Icons.Default.FavoriteBorder, contentDescription = "favorite")
                                }
                            } else {
                                IconButton(onClick = {
                                    //TODO Remove it from favorites list
                                    isFavoriteSelected = false
                                }) {
                                    Icon(
                                        imageVector = Icons.Default.Favorite,
                                        contentDescription = "favorite",
                                        tint = Color.Red

                                    )
                                }
                            }

                            Button(onClick = {
                                // TODO: Safely handle nullable values
                                Log.d("PokemonInfoLayout","The Value of location area is : ${pokemonDetailState?.details?.location_area_encounters} and name of pokemon is : ${pokemonDetailState?.details?.name}")
                                Log.d("PokemonInfoLayout","The value of pokName is $pokName and locationUrl is $locationUrl")
                                if (pokemonDetailState?.details?.name != null && pokemonDetailState?.details?.location_area_encounters != null) {
                                    Toast.makeText(context, "Finding this Pokemon", Toast.LENGTH_SHORT).show()
                                    onFindPokemonClicked(pokemonDetailState?.details?.name!!,
                                        pokemonDetailState?.details?.location_area_encounters!!
                                    )
                                } else {
                                    // Handle the case where either pokName or locationUrl is null
                                    Toast.makeText(context, "Error finding Pokemon. Details not available.", Toast.LENGTH_SHORT).show()
                                }
                            }) {
                                Text(text = "Find Pokemon")
                            }

                        }
                    }
                }
            }
        }

    }
    }


App navigation

@Composable
fun AppNavigation(){

    val navController = rememberNavController()


    NavHost(
        navController = navController,
        startDestination = Screens.BottomScreen.Home.route

    ){

        composable(Screens.BottomScreen.Home.route){
            MainView(
                onNavigateToDrawerItem = {
                navController.navigate(it.dRoute)
            },
                onNavigateToBottomBarItem = {
                    navController.navigate(it.route)
                },
                onNavigateToSheetItem = {
                    navController.navigate(it.route)
                },
                onNavigateToRegisterScreen = {
                    navController.navigate(Screens.OtherScreen.Register.route)
                },
                onNavigateToProfileScreen = {
                    navController.navigate(Screens.DrawerScreen.Profile.route)
                }
            )
        }
        composable(Screens.BottomScreen.Pokedex.route){
            PokedexScreen(navController = navController)
        }
        composable(Screens.BottomScreen.Favorites.route){
            FavoritesScreen()
        }
        composable(Screens.DrawerScreen.Profile.route){
            ProfileScreen(
                onUpdateClicked = {
                    userId ->
                    navController.navigate(Screens.OtherScreen.UpdateScreen.route +"/${userId}")},
                onDeleteClicked = {navController.navigate(Screens.BottomScreen.Home.route)},
                onBackNavClick = {navController.navigate(Screens.BottomScreen.Home.route)}
            )
        }
        composable(Screens.BottomSheetScreen.Settings.route){
            SettingsScreen()
        }
        composable(Screens.OtherScreen.Register.route){
            RegisterScreen(
                onSaveButtonClicked = { navController.navigate(Screens.BottomScreen.Home.route) },
                onDiscardButtonClicked = { navController.navigate(Screens.BottomScreen.Home.route) },

            )
        }
        composable(Screens.OtherScreen.UpdateScreen.route + "/{userId}"){
            val userId = navController.currentBackStackEntry?.arguments?.getString("userId") ?: "No string"

            UpdateScreen(
                onSaveButtonClicked = { navController.navigate(Screens.BottomScreen.Home.route) },
                onDiscardButtonClicked = { navController.navigate(Screens.DrawerScreen.Profile.route) },
                userId = userId
            )
        }

        // Add a placeholder route for LocationDetailScreen in your navigation graph
        composable(Screens.OtherScreen.LocationDetailScreen.route + "/{pokName}/{locationUrl}") {
            PokemonLocationDetailScreen(
                onBackNavClicked = { navController.navigateUp() },
                navController = navController,
            )
        }


    }

}

Logs info at the time of crashing of app

  1. 2024-02-20 06:09:43.084 14920-14977 Surface com.example.pokemonworld101 D Surface::disconnect(this=0xc0c08000,api=1)

  2. 2024-02-20 06:09:43.087 14920-14920 View com.example.pokemonworld101 D [Warning] assignParent to null: this = android.widget.LinearLayout{e857abd V.E...... ......ID 0,0-602,211}

  3. 2024-02-20 06:09:43.088 14920-14920 InputTransport com.example.pokemonworld101 I Destroy ARC handle: 0xd0f4b6f0

  4. 2024-02-20 06:09:44.455 14920-14920 ViewRootImpl com.example.pokemonworld101 D [TouchInput][ViewRootImpl] KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x48, repeatCount=0, eventTime=1159512349, downTime=1159512349, deviceId=-1, source=0x101, displayId=-1 }

  5. 2024-02-20 06:09:44.467 14920-14920 ViewRootImpl com.example.pokemonworld101 D [TouchInput][ViewRootImpl] KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x48, repeatCount=0, eventTime=1159512372, downTime=1159512349, deviceId=-1, source=0x101, displayId=-1 }

  6. 2024-02-20 06:09:45.311 14920-14920 ForceDarkHelper com.example.pokemonworld101 D updateByCheckExcludeList: pkg: com.example.pokemonworld101 activity: com.example.pokemonworld101.MainActivity@7584703

  7. 2024-02-20 06:09:48.079 14920-14937 pokemonworld10 com.example.pokemonworld101 I Background young concurrent copying GC freed 42115(1745KB) AllocSpace objects, 0(0B) LOS objects, 25% free, 5519KB/7365KB, paused 464us total 136.811ms

  8. 2024-02-20 06:09:57.532 14920-14920 ForceDarkHelper com.example.pokemonworld101 D updateByCheckExcludeList: pkg: com.example.pokemonworld101 activity: com.example.pokemonworld101.MainActivity@7584703

  9. 2024-02-20 06:09:57.544 14920-14920 ForceDarkHelper com.example.pokemonworld101 D updateByCheckExcludeList: pkg: com.example.pokemonworld101 activity: com.example.pokemonworld101.MainActivity@7584703

  10. 2024-02-20 06:09:57.656 14920-14920 InputEventReceiver com.example.pokemonworld101 W App Input: Dispatching InputEvent took 142ms in main thread! (MotionEvent: event_seq=28, seq=3248118, action=ACTION_UP)

  11. 2024-02-20 06:09:57.672 14920-14920 ForceDarkHelper com.example.pokemonworld101 D updateByCheckExcludeList: pkg: com.example.pokemonworld101 activity: com.example.pokemonworld101.MainActivity@7584703

  12. 2024-02-20 06:09:57.935 14920-14920 Process com.example.pokemonworld101 I Sending signal. PID: 14920 SIG: 9 13.2024-02-20 06:09:57.970 1213-1329 InputDispatcher system_server E channel '5743e7c com.example.pokemonworld101/com.example.pokemonworld101.MainActivity (server)' ~ Channel is unrecoverably broken and will be disposed!

The number 12th log is the error message and then the app crashed.

Edited 14. 2024-02-21 15:22:53.609 1213-17415 MessageQueue system_server W Handler (android.media.audiofx.Visualizer$NativeEventHandler) {467cbd9} sending message to a Handler on a dead thread java.lang.IllegalStateException: Handler (android.media.audiofx.Visualizer$NativeEventHandler) {467cbd9} sending message to a Handler on a dead thread at android.os.MessageQueue.enqueueMessage(MessageQueue.java:561) at android.os.Handler.enqueueMessage(Handler.java:754) at android.os.Handler.sendMessageAtTime(Handler.java:703) at android.os.Handler.sendMessageDelayed(Handler.java:673) at android.os.Handler.sendMessage(Handler.java:611)

  1. 2024-02-21 15:22:53.664 1213-2332 AES system_server W Exception Log handling...

Solution

  • In Jetpack Compose, when passing a URL as a parameter in a navigation route, it's essential to encode the URL to ensure it doesn't conflict with the navigation system's route structure.

    Before navigating to the destination screen, encode the URL using Uri.encode() to handle special characters properly.

    @Composable
    fun PokedexScreen() {
        // rest of the code
        PokemonInfoLayout(
            onFindPokemonClicked = { pokName, locationUrl ->
                // Pass values to the PokemonLocationDetailScreen
                // encode the location url since it conflict with the navigation url.
                val encodedLocation = Uri.encode(locationUrl)
                navController.navigate(
                    "${Screens.OtherScreen.LocationDetailScreen.route}/$pokName/$encodedLocation"
                )
            }
    
            ,
            onExperienceClicked = {  },
            context = context,
        )
    }
    

    In the destination composable, retrieve the encoded URL from the navigation arguments, and then decode it using Uri.decode() to get the original URL.

    @Composable
    fun PokemonLocationDetailScreen(
        viewModel: PokedexViewModel = androidx.lifecycle.viewmodel.compose.viewModel(),
        onBackNavClicked: () -> Unit,
        navController: NavController,
    ) {
        // Retrieve values from the previousBackStackEntry
        val pokName = navController.currentBackStackEntry?.arguments?.getString("pokName") ?: "raichu"
        val encodedUrl = navController.currentBackStackEntry?.arguments?.getString("locationUrl")
        
        // decode the url here.
        val url = if (encodedUrl == null) "https://pokeapi.co/api/v2/pokemon/132/encounters"
        else Uri.decode(encodedUrl)
    }