Почему я получаю исключение NULL-указателя при использовании .observeAsState()?

Я новичок в разработке Android и Котлине. Я пытаюсь создать приложение, которое хранит некоторые предоставленные пользователем значения в комнате и показывает их в списке.

Моя самая большая трудность на данный момент заключается в том, что существует беспорядок учебных пособий и ссылок из различных версий функций kotlin/compose/android, которые очень сложно разобрать новичку.

Моя сущность

@Entity(tableName = "goals")
data class Goal(
    @PrimaryKey(autoGenerate = true)
    val id : Int = 0,
    val text : String,
    val date : String,
    val complete : Boolean = false
)

Мой ДАО

@Dao
interface GoalDao {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(goal : Goal)

    @Update
    suspend fun update(goal : Goal)

    @Delete
    suspend fun delete(goal : Goal)

    @Query("SELECT * FROM goals WHERE id = :id")
    fun getGoal(id : Int) : Flow<Goal>

    @Query("SELECT * FROM goals ORDER BY date DESC")
    fun getAllGoals() : Flow<List<Goal>>
}   

Моя база данных

@Database(
    entities = [Goal::class],
    version = 1,
    exportSchema = false
)
abstract class GoalDatabase : RoomDatabase() {
    abstract fun goalDao() : GoalDao

    companion object {
        @Volatile
        private var Instance : GoalDatabase? = null

        fun getDatabase(context : Context) : GoalDatabase {
            return Instance ?: synchronized(this) {
                Room.databaseBuilder(context, GoalDatabase::class.java, "goal_database")
                    .fallbackToDestructiveMigration()
                    .build()
                    .also { Instance = it }
            }
        }
    }
}

Мой репозиторий

class GoalRepository(private val goalDao : GoalDao) {

    fun getAllGoals(): Flow<List<Goal>> = goalDao.getAllGoals()

    @Suppress("RedundantSuspendModifier")
    @WorkerThread
    suspend fun insertGoal(goal : Goal) {
        goalDao.insert(goal)
    }
}

Моя модель просмотра

class GoalViewModel(private val repository: GoalRepository) : ViewModel() {

    val allGoals : LiveData<List<Goal>> = repository.getAllGoals().asLiveData()

    fun insert(goal : Goal) = viewModelScope.launch {
        repository.insertGoal(goal)
    }
}

class GoalViewModelFactory(private val repository: GoalRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass : Class<T>) : T {
        if (modelClass.isAssignableFrom(GoalViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return GoalViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

И вот моя основная деятельность:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val goalRepository = GoalRepository(GoalDatabase.getDatabase(application as Context).goalDao())
        setContent {
            PositiveTrackerTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    MainScreen(goalRepository)
                }
            }
        }
    }
}

@Composable
fun MainScreen(goalRepository: GoalRepository) {

    val navController = rememberNavController()

    NavHost(navController = navController, startDestination = "main") {
        composable("main") {
            Home(goalRepository)
        }
    }
}

@Composable
fun NewGoal(
    onCompleteGoal: (String) -> Unit,
    onCancelGoal: () -> Unit
) {
    val focusRequester = remember { FocusRequester() }

    LaunchedEffect(Unit) {
        focusRequester.requestFocus()
    }
    Dialog(
        onDismissRequest = {
            onCancelGoal()
        }
    )
    {
        Card {
            var goalText by remember { mutableStateOf("") }

            Column(
                modifier = Modifier.padding(16.dp)
            ) {
                Text(
                    text = "New Goal",
                    style = MaterialTheme.typography.titleMedium,
                    modifier = Modifier.padding(bottom = 16.dp)
                )
                TextField(
                    value = goalText,
                    onValueChange = {
                        goalText = it
                    },
                    singleLine = true,
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(bottom = 16.dp)
                        .focusRequester(focusRequester)
                )
                Row {
                    TextButton(
                        onClick = { onCancelGoal() },
                        modifier = Modifier.padding(8.dp)
                    ) {
                        Text(text = "Cancel")
                    }
                    TextButton(
                        onClick = { onCompleteGoal(goalText) },
                        modifier = Modifier.padding(8.dp)
                    ) {
                        Text(text = "Create Goal")
                    }
                }
            }
        }
    }
}

@Composable
fun GoalCard(goal : Goal) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
            .clip(shape = RoundedCornerShape(10.dp)),
        colors = CardDefaults.cardColors()
    ) {
        Column(
            modifier = Modifier.fillMaxWidth()
        ) {
            Text(
                text = goal.text,
                style = MaterialTheme.typography.affirmationQuote,
                modifier = Modifier.padding(16.dp)
            )
            Text(
                text = goal.date,
                style = MaterialTheme.typography.labelSmall,
                modifier = Modifier
                    .padding(16.dp)
                    .align(Alignment.End)
            )
        }
    }
}

@Composable
fun Home(goalRepository : GoalRepository) {

    var showNewGoal by remember { mutableStateOf(false) }

    val coroutineScope = rememberCoroutineScope()

    val viewModel : GoalViewModel = viewModel(factory = GoalViewModelFactory(goalRepository))

    val goalList by viewModel.allGoals.observeAsState()

    if (showNewGoal) {
        NewGoal(
            onCompleteGoal = {
                showNewGoal = false
                coroutineScope.launch {
                    goalRepository.insertGoal(
                        Goal(
                            text = it,
                            date = LocalDate.now().toString()
                        )
                    )
                }
            },
            onCancelGoal = {
                showNewGoal = false
            }
        )
    }

    Box(
        modifier = Modifier.fillMaxSize()
    )
    {
        Column(
            modifier = Modifier.fillMaxSize()
        ) {
            goalList!!.forEach {
                GoalCard(it)
            }
        }
    }
}

Когда я запускаю приложение, log cat выдает мне кучу проблем:

java.lang.NullPointerException
    at com.positivetracker.MainActivityKt.Home(MainActivity.kt:228)
    at com.positivetracker.MainActivityKt$MainScreen$1$1.invoke(MainActivity.kt:92)
    at com.positivetracker.MainActivityKt$MainScreen$1$1.invoke(MainActivity.kt:91)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:139)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.navigation.compose.NavHostKt$NavHost$14$1.invoke(NavHost.kt:308)
    at androidx.navigation.compose.NavHostKt$NavHost$14$1.invoke(NavHost.kt:306)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:248)
    at androidx.compose.runtime.saveable.SaveableStateHolderImpl.SaveableStateProvider(SaveableStateHolder.kt:84)
    at androidx.navigation.compose.NavBackStackEntryProviderKt.SaveableStateProvider(NavBackStackEntryProvider.kt:65)
    at androidx.navigation.compose.NavBackStackEntryProviderKt.access$SaveableStateProvider(NavBackStackEntryProvider.kt:1)
    at androidx.navigation.compose.NavBackStackEntryProviderKt$LocalOwnersProvider$1.invoke(NavBackStackEntryProvider.kt:52)
    at androidx.navigation.compose.NavBackStackEntryProviderKt$LocalOwnersProvider$1.invoke(NavBackStackEntryProvider.kt:51)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.navigation.compose.NavBackStackEntryProviderKt.LocalOwnersProvider(NavBackStackEntryProvider.kt:47)
    at androidx.navigation.compose.NavHostKt$NavHost$14.invoke(NavHost.kt:306)
    at androidx.navigation.compose.NavHostKt$NavHost$14.invoke(NavHost.kt:295)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:139)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.animation.AnimatedContentKt$AnimatedContent$6$1$5.invoke(AnimatedContent.kt:755)
    at androidx.compose.animation.AnimatedContentKt$AnimatedContent$6$1$5.invoke(AnimatedContent.kt:744)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:118)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.animation.AnimatedVisibilityKt.AnimatedEnterExitImpl(AnimatedVisibility.kt:818)
    at androidx.compose.animation.AnimatedContentKt$AnimatedContent$6$1.invoke(AnimatedContent.kt:726)
    at androidx.compose.animation.AnimatedContentKt$AnimatedContent$6$1.invoke(AnimatedContent.kt:709)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.animation.AnimatedContentKt.AnimatedContent(AnimatedContent.kt:768)
    at androidx.navigation.compose.NavHostKt.NavHost(NavHost.kt:273)
    at androidx.navigation.compose.NavHostKt.NavHost(NavHost.kt:128)
    at com.positivetracker.MainActivityKt.MainScreen(MainActivity.kt:90)
    at com.positivetracker.MainActivity$onCreate$1$1$1.invoke(MainActivity.kt:78)
    at com.positivetracker.MainActivity$onCreate$1$1$1.invoke(MainActivity.kt:77)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.material3.SurfaceKt$Surface$1.invoke(Surface.kt:134)
    at androidx.compose.material3.SurfaceKt$Surface$1.invoke(Surface.kt:115)
E   at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.compose.material3.SurfaceKt.Surface-T9BRK9s(Surface.kt:112)
    at com.positivetracker.MainActivity$onCreate$1$1.invoke(MainActivity.kt:74)
    at com.positivetracker.MainActivity$onCreate$1$1.invoke(MainActivity.kt:72)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:248)
    at androidx.compose.material3.TextKt.ProvideTextStyle(Text.kt:352)
    at androidx.compose.material3.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:72)
    at androidx.compose.material3.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:71)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.compose.material3.MaterialThemeKt.MaterialTheme(MaterialTheme.kt:64)
    at com.positivetracker.ui.theme.ThemeKt.PositiveTrackerTheme(Theme.kt:87)
    at com.positivetracker.MainActivity$onCreate$1.invoke(MainActivity.kt:72)
    at com.positivetracker.MainActivity$onCreate$1.invoke(MainActivity.kt:71)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.ui.platform.ComposeView.Content(ComposeView.android.kt:428)
    at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:252)
    at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:251)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:186)
    at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:119)
    at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:118)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.ProvideAndroidCompositionLocals(AndroidCompositionLocals.android.kt:110)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$2.invoke(Wrapper.android.kt:139)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$2.invoke(Wrapper.android.kt:138)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:248)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:138)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:123)
E   at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:90)
    at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3302)
    at androidx.compose.runtime.ComposerImpl.composeContent$runtime_release(Composer.kt:3235)
    at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:725)
    at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:1071)
    at androidx.compose.runtime.CompositionImpl.composeInitial(Composition.kt:633)
    at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:619)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:123)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:114)
    at androidx.compose.ui.platform.AndroidComposeView.setOnViewTreeOwnersAvailable(AndroidComposeView.android.kt:1289)
    at androidx.compose.ui.platform.WrappedComposition.setContent(Wrapper.android.kt:114)
    at androidx.compose.ui.platform.WrappedComposition.onStateChanged(Wrapper.android.kt:164)
    at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.kt:322)
    at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.kt:199)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:121)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:114)
    at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.android.kt:1364)
    at android.view.View.dispatchAttachedToWindow(View.java:20479)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3489)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3496)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3496)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3496)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3496)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2417)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1952)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8171)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:972)
    at android.view.Choreographer.doCallbacks(Choreographer.java:796)
    at android.view.Choreographer.doFrame(Choreographer.java:731)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:223)
    at android.app.ActivityThread.main(ActivityThread.java:7656)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

Но я понятия не имею, с чего начать поиски. Я подозреваю, что делаю что-то не так с observeAsState, хотя я читал, что это более простой способ сделать что-то в отличие от гораздо более многословных способов, которые я видел в старом коде.

РЕДАКТИРОВАТЬ Исключение начинается в строке

goalList!!.forEach {
    GoalCard(it)
}

в основной активности

Можете ли вы выделить строку в MainActivity, из которой генерируется исключение?

tomerpacific 20.04.2024 08:27

Это не «большая куча проблем». Это трассировка стека для одной проблемы. Вам нужно сообщить нам, что это за строка 228. Но все NPE одинаковы. Вы попытались использовать ссылочную переменную, значение которой было нулевым.

user207421 20.04.2024 10:46
2
2
84
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Причина, по которой вы получаете исключение NullPointerException, заключается в том, что вы явно запросили его, используя !!.

Если вам не нужно это исключение, просто удалите !!. Ошибку компиляции, которую вы сейчас получаете, можно легко исправить, указав начальное значение для observeAsState:

val goalList by viewModel.allGoals.observeAsState(emptyList())

Тем не менее, при использовании Compose вообще нет необходимости использовать старый LiveData. Вместо этого вам следует использовать StateFlow в вашей модели представления:

val allGoals: StateFlow<List<Goal>> = repository.getAllGoals().stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5_000),
    initialValue = emptyList(),
)

Затем в вашей компоновке вы используете это:

val goalList by viewModel.allGoals.collectAsStateWithLifecycle()

Для этого вам понадобится зависимость Gradle androidx.lifecycle:lifecycle-runtime-compose. androidx.compose.runtime:runtime-livedata теперь можно удалить.


Хотя это не имеет отношения к вашей проблеме, вам также следует исправить следующее:

  1. Ваш репозиторий должен зависеть только от GoalDatabase, а не от Dao:

    class GoalRepository(goalDb: GoalDatabase) {
        private val goalDao: GoalDao = goalDb.goalDao()
        // ...
    }
    
  2. Вы не должны передавать репозитории вашим составным объектам. Лучше создайте модель представления в действии и передайте ее. Однако правильным способом было бы передавать только состояния и обратные вызовы:

    val goalRepository = GoalRepository(GoalDatabase.getDatabase(applicationContext))
    
    setContent {
        val viewModel: GoalViewModel = viewModel(factory = GoalViewModelFactory(goalRepository))
        val goalList by viewModel.allGoals.collectAsStateWithLifecycle()
    
        // PositiveTrackerTheme and Surface here, just omitted for brevity
        MainScreen(
            goalList = goalList,
            insertGoal = viewModel::insert,
        )
    }
    

    MainScreen и Home тогда должны иметь следующие параметры:

    goalList: List<Goal>,
    insertGoal: (Goal) -> Unit,
    

    Это также устраняет необходимость в coroutineScope.launch в вашей композиции Home. Работа сопрограммы с функцией приостановки теперь выполняется в модели представления, которой она и принадлежит.

  3. Используйте Kotlin Clock.System.now(), чтобы получить текущее время вместо Java LocalDate.now().

Спасибо за все советы здесь. Как я уже сказал, я новичок в разработке Android, и существует так много устаревших руководств и ссылок, что мне трудно понять, как правильно что-то делать. еще раз спасибо

NeomerArcana 21.04.2024 11:26

Другие вопросы по теме