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

Jetpack Compose+Room笔记应用中无法获取正确用户ID求助

无法获取正确用户ID导致笔记无法关联登录用户的问题

问题描述

我用Jetpack Compose + Room Database开发带登录功能的笔记应用,两周来一直解决不了用户ID获取异常的问题——登录后无法将笔记正确分配给当前用户。试过论坛类似问题的解决方案,也找过ChatGPT帮忙,都没解决。以下是关键代码文件,求各位帮忙排查:

代码片段

LoginRegisterViewModel.kt

class LoginRegisterViewModel(app: Application) : AndroidViewModel(app) {

    private val repo = UserRepository(app.applicationContext)
    private var userExist = false
    private val currentUser = MutableStateFlow<User?>(null)

    private var userId : Int? = null

    suspend fun checkPassword(username: String, password: String): Boolean {
        return withContext(Dispatchers.IO) {
            val user = getUserByLoginAndPassword(username, password)
            user != null
        }
    }

    fun getUserId(): Int? {
        println(userId)
        return userId
    }

    private fun setCurrentUser(user: User) {
        currentUser.value = user
        userId = user.id
    }

    suspend fun checkIfUserExists(loginValue: String): Boolean {
        return withContext(Dispatchers.IO) {
            val user = repo.getUserByLogin(loginValue)
            user != null
        }
    }
    private suspend fun getUserByLoginAndPassword(
        loginValue: String,
        passwordValue: String
    ): User? {
        return repo.getUserByLoginAndPassword(loginValue, passwordValue)
    }

    suspend fun login(loginValue: String, passwordValue: String): Boolean {
        return withContext(Dispatchers.IO) {
            val user = getUserByLoginAndPassword(loginValue, passwordValue)
            if (user != null) {
                setCurrentUser(user)
                userId = repo.getUserId(loginValue)
                println(currentUser.value)
                true
            } else {
                false
            }
        }
    }

    fun logout() {
        currentUser.value = null
    }


    fun registerUser(
        nameValue: String,
        loginValue: String,
        passwordValue: String,
        confirmPasswordValue: String
    ) {
        viewModelScope.launch(Dispatchers.IO) {
            if (passwordValue == confirmPasswordValue) {
                userExist = false
                val user = User(
                    nameValue = nameValue,
                    loginValue = loginValue,
                    passwordValue = passwordValue
                )
                repo.insert(user)
            } else {
                userExist = true
            }
        }
    }
}

NotesViewModel.kt

class NotesViewModel(
    private val repo: NotesRepository, private val userId: Int?
) : ViewModel() {

    val getNotesByUserId: List<Note> = runBlocking { repo.getNotesByUserId(userId) }

    fun deleteNotes(note: Note) {
        viewModelScope.launch(Dispatchers.IO) {
            repo.deleteNote(note)
        }
    }

    fun updateNote(note: Note) {
        viewModelScope.launch(Dispatchers.IO) {
            repo.updateNote(note)
        }
    }

    fun createNote(title: String, note: String, userId: Int?) {
        viewModelScope.launch(Dispatchers.IO) {
            repo.insertNote(Note(title = title, note = note, userId = userId))
            println(userId)
        }
    }

    suspend fun getNote(noteId: Int): Note? {
        return repo.getNoteById(noteId)
    }

}

@Suppress("UNCHECKED_CAST")
class NotesViewModelFactory(
    private val repo: NotesRepository, private val loginRegisterViewModel: LoginRegisterViewModel
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return NotesViewModel(repo, loginRegisterViewModel.getUserId()) as T
    }
}

CreateNote.kt

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "UnusedMaterialScaffoldPaddingParameter")
@Composable
fun CreateNote(
    navController: NavController,
    viewModel: NotesViewModel,
    userId: Int?
) {
    val currentNote = remember {
        mutableStateOf("")
    }

    val currentTitle = remember {
        mutableStateOf("")
    }

    val saveButtonState = remember {
        mutableStateOf(false)
    }

    val currentUser = remember {
        mutableStateOf(userId)
    }

    println(currentUser)

    NotepadTheme {
        Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
            Scaffold(
                topBar = {
                    AppBar(
                        title = "Create Note",
                        onIconClick = {

                            viewModel.createNote(
                                currentTitle.value,
                                currentNote.value,
                                currentUser.value
                            )
                            navController.popBackStack()
                        },
                        icon = {
                            Icon(
                                imageVector = ImageVector.vectorResource(R.drawable.save),
                                contentDescription = stringResource(R.string.save_note),
                                tint = Color.Black,
                            )
                        },
                        iconState = remember { mutableStateOf(true) }
                    )
                },
            ) {
                Column(
                    Modifier
                        .padding(12.dp)
                        .fillMaxSize()
                ) {

                    TextField(
                        value = currentTitle.value,
                        modifier = Modifier.fillMaxWidth(),
                        onValueChange = { value ->
                            currentTitle.value = value
                            saveButtonState.value =
                                currentTitle.value != "" && currentNote.value != ""
                        },
                        colors = TextFieldDefaults.textFieldColors(
                            cursorColor = Color.Black,
                            focusedLabelColor = Color.Black
                        ),
                        label = { Text(text = "Title") }
                    )
                    Spacer(modifier = Modifier.padding(12.dp))

                    TextField(
                        value = currentNote.value,
                        modifier = Modifier
                            .fillMaxHeight(0.5f)
                            .fillMaxWidth(),
                        onValueChange = { value ->
                            currentNote.value = value
                            saveButtonState.value =
                                currentTitle.value != "" && currentNote.value != ""
                        },
                        colors = TextFieldDefaults.textFieldColors(
                            cursorColor = Color.Black,
                            focusedLabelColor = Color.Black
                        ),
                        label = { Text(text = "Note") }
                    )
                }

            }
        }
    }
}

问题排查与解决方案

核心问题点

  1. 状态同步失效:LoginRegisterViewModel中同时维护currentUser(StateFlow)和userId(私有变量),login方法里重复赋值userId,可能导致状态不一致;且外部只能通过getUserId()获取一次性值,无法监听用户状态变化。
  2. ViewModel依赖僵化:NotesViewModel在构造时传入固定userId,后续用户登录/切换时无法更新,导致笔记始终关联旧ID或空值。
  3. Compose状态未联动:CreateNote中的currentUser仅初始化时赋值,未监听LoginRegisterViewModel的用户状态变化,登录后无法获取最新ID。
  4. 数据获取方式错误:NotesViewModel用runBlocking在主线程同步获取笔记,会阻塞UI,且无法实时响应数据更新。

修正后的代码实现

1. 改造LoginRegisterViewModel,统一用StateFlow暴露用户状态

class LoginRegisterViewModel(app: Application) : AndroidViewModel(app) {

    private val repo = UserRepository(app.applicationContext)
    private var userExist = false
    // 对外暴露不可变的StateFlow,让其他组件监听用户状态变化
    val currentUser: StateFlow<User?> = MutableStateFlow(null)
        private set

    suspend fun checkPassword(username: String, password: String): Boolean {
        return withContext(Dispatchers.IO) {
            getUserByLoginAndPassword(username, password) != null
        }
    }

    // 移除单独的userId变量,直接从currentUser中获取
    fun getCurrentUserId(): Int? = currentUser.value?.id

    private fun setCurrentUser(user: User) {
        (currentUser as MutableStateFlow).value = user
    }

    suspend fun checkIfUserExists(loginValue: String): Boolean {
        return withContext(Dispatchers.IO) {
            repo.getUserByLogin(loginValue) != null
        }
    }

    private suspend fun getUserByLoginAndPassword(
        loginValue: String,
        passwordValue: String
    ): User? {
        return repo.getUserByLoginAndPassword(loginValue, passwordValue)
    }

    suspend fun login(loginValue: String, passwordValue: String): Boolean {
        return withContext(Dispatchers.IO) {
            val user = getUserByLoginAndPassword(loginValue, passwordValue)
            if (user != null) {
                setCurrentUser(user)
                println(currentUser.value)
                true
            } else {
                false
            }
        }
    }

    fun logout() {
        (currentUser as MutableStateFlow).value = null
    }

    fun registerUser(
        nameValue: String,
        loginValue: String,
        passwordValue: String,
        confirmPasswordValue: String
    ) {
        viewModelScope.launch(Dispatchers.IO) {
            if (passwordValue == confirmPasswordValue) {
                userExist = false
                val user = User(
                    nameValue = nameValue,
                    loginValue = loginValue,
                    passwordValue = passwordValue
                )
                repo.insert(user)
            } else {
                userExist = true
            }
        }
    }
}

2. 调整NotesViewModel,依赖StateFlow实时获取用户ID

class NotesViewModel(
    private val repo: NotesRepository,
    private val loginRegisterViewModel: LoginRegisterViewModel
) : ViewModel() {

    // 用Flow实时获取当前用户的笔记,自动响应数据变化
    val userNotes: Flow<List<Note>> = loginRegisterViewModel.currentUser
        .flatMapLatest { user ->
            repo.getNotesByUserIdFlow(user?.id)
        }

    fun deleteNotes(note: Note) {
        viewModelScope.launch(Dispatchers.IO) {
            repo.deleteNote(note)
        }
    }

    fun updateNote(note: Note) {
        viewModelScope.launch(Dispatchers.IO) {
            repo.updateNote(note)
        }
    }

    fun createNote(title: String, note: String) {
        viewModelScope.launch(Dispatchers.IO) {
            val userId = loginRegisterViewModel.getCurrentUserId()
            userId?.let {
                repo.insertNote(Note(title = title, note = note, userId = it))
                println("笔记已关联用户ID:$it")
            } ?: run {
                println("未找到当前登录用户,无法创建笔记")
            }
        }
    }

    suspend fun getNote(noteId: Int): Note? {
        return repo.getNoteById(noteId)
    }
}

@Suppress("UNCHECKED_CAST")
class NotesViewModelFactory(
    private val repo: NotesRepository,
    private val loginRegisterViewModel: LoginRegisterViewModel
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return NotesViewModel(repo, loginRegisterViewModel) as T
    }
}

注意:需要给NotesRepository添加getNotesByUserIdFlow方法,返回Flow<List<Note>>,Room支持直接返回Flow进行实时数据监听。

3. 修正CreateNote,监听用户状态变化

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun CreateNote(
    navController: NavController,
    viewModel: NotesViewModel,
    loginRegisterViewModel: LoginRegisterViewModel
) {
    val currentNote = remember { mutableStateOf("") }
    val currentTitle = remember { mutableStateOf("") }
    // 监听当前用户状态,自动更新用户ID
    val currentUser by loginRegisterViewModel.currentUser.collectAsState()

    val saveButtonState = remember {
        mutableStateOf(currentTitle.value.isNotEmpty() && currentNote.value.isNotEmpty())
    }

    NotepadTheme {
        Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
            Scaffold(
                topBar = {
                    AppBar(
                        title = "Create Note",
                        onIconClick = {
                            viewModel.createNote(currentTitle.value, currentNote.value)
                            navController.popBackStack()
                        },
                        icon = {
                            Icon(
                                imageVector = ImageVector.vectorResource(R.drawable.save),
                                contentDescription = stringResource(R.string.save_note),
                                tint = Color.Black,
                            )
                        },
                        iconState = remember { mutableStateOf(true) }
                    )
                },
            ) { padding ->
                Column(
                    Modifier
                        .padding(padding)
                        .padding(12.dp)
                        .fillMaxSize()
                ) {
                    TextField(
                        value = currentTitle.value,
                        modifier = Modifier.fillMaxWidth(),
                        onValueChange = { value ->
                            currentTitle.value = value
                            saveButtonState.value = currentTitle.value.isNotEmpty() && currentNote.value.isNotEmpty()
                        },
                        colors = TextFieldDefaults.textFieldColors(
                            cursorColor = Color.Black,
                            focusedLabelColor = Color.Black
                        ),
                        label = { Text(text = "Title") }
                    )
                    Spacer(modifier = Modifier.padding(12.dp))

                    TextField(
                        value = currentNote.value,
                        modifier = Modifier
                            .fillMaxHeight(0.5f)
                            .fillMaxWidth(),
                        onValueChange = { value ->
                            currentNote.value = value
                            saveButtonState.value = currentTitle.value.isNotEmpty() && currentNote.value.isNotEmpty()
                        },
                        colors = TextFieldDefaults.textFieldColors(
                            cursorColor = Color.Black,
                            focusedLabelColor = Color.Black
                        ),
                        label = { Text(text = "Note") }
                    )
                }
            }
        }
    }
}

额外注意事项

  • 确保Room的Note实体类中userId字段正确关联用户表,建议添加外键约束。
  • 登录成功后跳转页面时,确保LoginRegisterViewModel的状态已更新,避免页面初始化时获取到空值。
  • 移除所有不必要的println调试代码,改用Android Studio的Logcat进行日志输出。

内容的提问来源于stack exchange,提问作者deadmouze

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 05:44:59