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 -
@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
2024-02-20 06:09:43.084 14920-14977 Surface com.example.pokemonworld101 D Surface::disconnect(this=0xc0c08000,api=1)
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}
2024-02-20 06:09:43.088 14920-14920 InputTransport com.example.pokemonworld101 I Destroy ARC handle: 0xd0f4b6f0
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 }
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 }
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
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
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
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
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)
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
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)
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)
}