You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose跳转新页面时应用崩溃(空指针异常)

问题:Compose导航跳转时触发空指针异常

我正在开发一款倒计时应用,启动页面用于填写信息,计时功能及展示在另一页面实现。但从启动页跳转至计时页时应用崩溃,Logcat抛出空指针异常,栈信息如下:

java.lang.NullPointerException
at androidx.navigation.NavController.navigate(NavController.kt:1652)
at androidx.navigation.NavController.navigate(NavController.kt:1984)
at androidx.navigation.NavController.navigate$default(NavController.kt:1979)

导航宿主与NavController配置

package com.example.myapplication.ui

import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

enum class TimerRoutes() {
    StartScreen,
    TimerScreen
}

@Composable
fun TimerApp() {
    val navController: NavHostController = rememberNavController()
    NavHost(navController = navController, startDestination = TimerRoutes.StartScreen.name) {

        composable(route = TimerRoutes.StartScreen.name) {
            TimerStartScreen(navController)
        }

        composable(route = TimerRoutes.TimerScreen.name) {
            TimerScreen()
        }

    }
}

TimerStartScreen代码

package com.example.myapplication.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TimerStartScreen(navController: NavHostController) {
    val viewModel: TimerViewModel = viewModel()
    val timerUiState = viewModel.uiState.collectAsState()

    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.TopCenter) {
        Column() {
            TopAppBar(title = {Text(text = "Start Screen", textAlign = TextAlign.Center)})
            TextField(
                value = timerUiState.value.name,
                onValueChange = { viewModel.updateNameText(it) },
                Modifier
                    .fillMaxWidth()
                    .padding(top = 16.dp, start = 16.dp, end = 16.dp),
                label = { Text(text = "Enter Timer Name") }
            )

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

            TextField(
                value = timerUiState.value.date,
                onValueChange = {viewModel.updateCurrentDateNumber(it)},
                Modifier
                    .fillMaxWidth()
                    .padding(start = 16.dp, end = 16.dp),
                label = { Text(text = "Enter Date of Event (dd/MM/yyyy)")},
                keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
            )

            Spacer(modifier = Modifier.height(30.dp))
            Button(
                onClick = {navController.navigate(TimerRoutes.TimerScreen.name)},
                content = { Text(text = "Create!")},
                modifier = Modifier.padding(start = 135.dp)
            )
        }
    }
}

TimerScreen代码

package com.example.myapplication.ui

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun TimerScreen() {
    val viewModel: TimerViewModel = viewModel()
    val timerUiState = viewModel.uiState.collectAsState()

    viewModel.findNumberOfDaysAndHoursLeft(timerUiState.value.date)

    Box(contentAlignment = Alignment.TopCenter) {
        Column() {
            Spacer(modifier = Modifier.height(30.dp))

            Row(){
                Text(text = "Days: ${timerUiState.value.diffInDays.toString()}", modifier = Modifier.padding(start = 110.dp))
                Spacer(modifier = Modifier.width(15.dp))
                Text(text = "Hours: ${timerUiState.value.diffInHours.toString()}")
            }
        }
    }
}

问题排查与修复

1. 核心崩溃原因:错误的NavController实例

你的TimerStartScreen中导入了rememberNavController、NavHost等导航API,但这个Composable不需要这些——你已经通过参数接收了上层TimerApp传递的navController。如果在这个Composable内部不小心调用了rememberNavController(),会生成一个未关联到NavHost的独立实例,调用navigate时就会触发空指针。

修复: 删除TimerStartScreen中多余的导航导入:

// 移除这三行无用导入
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

2. 额外问题:ViewModel实例不共享

当前TimerStartScreen和TimerScreen各自通过viewModel()获取实例,这会导致两个页面拿到不同的ViewModel对象——启动页填写的数据无法传递到计时页,还可能引发后续空值问题。

修复: 在TimerApp中创建共享ViewModel并传递给子页面:

@Composable
fun TimerApp() {
    val navController: NavHostController = rememberNavController()
    val viewModel: TimerViewModel = viewModel() // 全局共享的ViewModel实例
    NavHost(navController = navController, startDestination = TimerRoutes.StartScreen.name) {
        composable(route = TimerRoutes.StartScreen.name) {
            TimerStartScreen(navController, viewModel) // 传递ViewModel
        }
        composable(route = TimerRoutes.TimerScreen.name) {
            TimerScreen(viewModel) // 传递ViewModel
        }
    }
}

修改TimerStartScreen参数:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TimerStartScreen(navController: NavHostController, viewModel: TimerViewModel) {
    val timerUiState = viewModel.uiState.collectAsState()
    // 其余代码不变
}

修改TimerScreen参数:

@Composable
fun TimerScreen(viewModel: TimerViewModel) {
    val timerUiState = viewModel.uiState.collectAsState()
    // 其余代码不变
}

3. 防御性建议:处理空日期

在ViewModel的findNumberOfDaysAndHoursLeft方法中,先判断日期字符串是否为空或格式错误,避免后续逻辑触发空指针:

fun findNumberOfDaysAndHoursLeft(dateStr: String?) {
    if (dateStr.isNullOrEmpty()) {
        // 处理空日期情况,比如更新uiState提示用户
        return
    }
    // 原日期处理逻辑
}

内容的提问来源于stack exchange,提问作者Allen J Mathew

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 10:52:22