Search code examples
androidkotlinandroid-jetpack-composeandroid-room

How do I retrieve the correct user ID when creating a login-enabled note-taking app using Jetpack Compose and Room Database in Android Studio?


The inability to retrieve the correct user ID, which prevents me from assigning a note to the logged-in user.

Hey, for the past two weeks, I've been struggling with an issue while creating a login-enabled note-taking application in Android Studio using Jetpack Compose and Room Database. I just started learning and this problem makes me crazy because i know someone with experience would fix this probably in 5 minutes.

First and foremost, I'd like to mention that I found a similar problem on a forum text, but the solution provided didn't work in my case, I even tried to ask ChatGPT for help and that's still not working.

Below, I'm providing the code for LoginRegisterViewModel, NotesViewModel, and the CreateNote Composable function where I invoke the logged-in user. Sorry for a mess in code but that's a "last version" of my code when i was trying to repair this :/

LoginRegisterViewModel:

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:

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 Composable function:

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

            }
        }
    }
}

Thanks in advance for any kind of help!


Solution

  • I have found a way to pass the userId to NotesList page. First of all remove all the changes said by me in the first answer also remove the user object parameter from the NotesViewModel class.

    I saw in your code that in the LoginRegisterViewModel you had flow object of currentUser that retrieves the current user object from the database and I tested it to know if it is getting the value correctly , so you won't need any extra methods just to get the userId.

       composable("notelist_page/{userId}",
                        arguments = listOf(navArgument("userId"){
                            type = NavType.IntType
                        })
                    ) {
                        val userId = remember {
                            it.arguments?.getInt("userId")
                        }
                        if(userId != null){
                            NoteList(navController = navController,notesViewModel,userId)
                        }
    
                    }
    

    In the NotesList composable add a paramter of userId and change the route as I did , to send the userId from login page to NotesList page in the route paramter.

    I the LoginPage this is how you would navigate

          // Sprawdź, czy hasło zostało uzupełnione i czy zgadza się z użytkownikiem
                if (passwordState.value.isNotEmpty() && validPassword) {
                             // Wywołaj funkcję logowania z ViewModel
                                 runBlocking {
                                    loginRegisterViewModel.login(
                                            loginState.value,
                                            passwordState.value
                                        )
                                    }
              navController.navigate(
    "notelist_page/${loginRegisterViewModel.currentUser.value!!.id}"
    ) {
          launchSingleTop = true
          }
    }
    

    And at last in the NotesListPage change the value of userId which is in the NotesViewModel

    fun NoteList(
        navController: NavController,
        viewModel: NotesViewModel,
        userId : Int
    ) {
    
        viewModel.userId.value = userId
        val notes = viewModel.getNotesByUserId
    
    }
    
    

    these lines of code in the NotesViewModel would retrive the notes list using the userId.

    
    class NotesViewModel(
        private val repo: NotesRepository, private val userRepo: UserRepository
    ) : ViewModel() {
    
    
        var userId = mutableStateOf(0)
    
        val getNotesByUserId: List<Note> = runBlocking { 
                     repo.getNotesByUserId(userId.value) 
    }
    

    After doing all these changes an error occurred regarding the userId generation, so what it did was remove the Unique key parameter from the Entity class and kept it simple , the autoGenerate = true attribute is enough to generate new id's.

    @Entity(tableName = Constants.USERS_TABLE_NAME)
    data class User(
        @PrimaryKey(autoGenerate = true) val id: Int? = 0,
        @ColumnInfo(name = "nameValue") val nameValue: String,
        @ColumnInfo(name = "loginValue") val loginValue: String,
        @ColumnInfo(name = "passwordValue") val passwordValue: String
    )
    

    After doing this change you would also have to change the RoomDatabase version and setup the migration as well and if you don't want to mention the migration just add .fallbackToDestructiveMigration().

    
       private var db: AppDatabase? = null
    
            fun getInstance(context: Context): AppDatabase {
                if (db == null) {
                    db = Room.databaseBuilder(context, AppDatabase::class.java, Constants.DATABASE_NAME)
                    //    .addMigrations(MIGRATION_2_3)
                        .fallbackToDestructiveMigration()
                        .build()
                }
                return db!!
            }