我是Android开发和Kotlin的新手.我正在try 制作一个应用程序,将一些用户提供的值存储在Room中,并将它们显示在列表中.

到目前为止,我最大的困难是kotlin/compose/android函数的各个版本的教程和参考文献太混乱,对于新手来说很难解析.

My Entity

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

My DAO

@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>>
}   

My Database

@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 }
            }
        }
    }
}

My Repository

class GoalRepository(private val goalDao : GoalDao) {

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

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

My View Model

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做得不对,尽管我读到这是更简单的做事方式,而不是我见过的旧代码做事方式更加冗长.

EDIT例外从线路上开始

goalList!!.forEach {
    GoalCard(it)
}

在主要活动中

推荐答案

您获得NullPointerResponse的原因是,您explicitly asked for it by using !!.

如果您不想要该例外,只需删除!!即可.您现在得到的编译错误可以通过提供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. 您不应该将存储库传递给您的组合.更好的做法是在活动中创建视图模型并将其传递下go .然而,正确的方法是只通过状态和回调:

    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,
        )
    }
    

    MainScreenHome则需要具有这些参数:

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

    这也消除了在Home组合中添加coroutineScope.launch的需要.带有暂停功能的协同程序现在在其所属的视图模型中完成.

  3. 使用Kotlin Clock.System.now()而不是Java LocalDate.now()获取当前时间.

Android相关问答推荐

如何在cordova 中播放html5视频标签?

更新画布上的绘图以具有水平填充

如何检测HitTest是否命中给定的网格对象?

懒惰的垂直网格中盒子的重量-Jetpack组合

有人能帮我在应用程序上使用模拟位置时避免被发现吗?我已经反编译并粘贴了一个代码,S小文件

Android系统应用程序启用编程以太网网络共享

无法加载类';com.android.build.api.extension.AndroidComponentsExtension';

页面更改时不显示 cogo toast 消息

布局可组合项如何具有可测量和约束参数?

当 EditText 用于在 android studio 中将字符串发送到 firebase 时,仅允许安全调用错误

如何绘制内边框?

如何将一个没有 GRADLE 的古老 Android 项目导入到今天的 Android Studio 中?

在 Jetpack Compose 中对齐行项目

在 Jetpack Compose 中使用 .observeAsState() 时,如何在更改 MutableLiveData 的值后开始执行一段代码?

将房间中的实体更新为 isCompleted 并使用 Flow 问题获取所有数据

在jetpack compose中将图像添加到脚手架顶部栏

预览必须是顶级声明或具有默认构造函数的顶级类

0dp 大小的可组合文件是否可以组合?

我可以在不解密的情况下使用 JSch 获取加密的 SSH 私钥的类型或 fingerprint 吗?

将生成的 AAR 与 Composables 一起使用时未解决的参考