0

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!

deadmouze
  • 3
  • 3

2 Answers2

1

Okay so in your dao class you can do something like this to retrieve the user id by passing in the username .

   @Query("SELECT userId FROM User WHERE username = :username")
    suspend fun getUserIdByUsername(username: String): Int?

And then add this function to your repository then in the NotesViewModel create a mutableStateOf Int as 0 and instead of passing the userId in the constructor of notesViewModel you must pass the whole user object

class NotesViewModel(
    private val repo: NotesRepository, private val user: User?
) : ViewModel()

#and then create a mutableStateOf Int and initialize it to zero in the NotesViewModel,

var userId = mutableStateOf(0)

#create a init block in the NotesViewModel and call the function from the #repository

init {
   userId = repo.getUserIdByUsername(user.username)
}


At last in your composable, you can do something like this remove the userId from the parameters

var userId by remember{
   mutableStateOf(viewModel.userId.value)
}

LaunchedEffect(viewModel.userId.value){
userId = viewModel.userId.value
}

You should getTheUser id by this way and you can use it in further functions :)

zaid khan
  • 825
  • 3
  • 11
  • Despite implementing your corrections in the code, I still seem to be doing something wrong. Could you please take a look at my repository? [link](https://github.com/dmouze/notepad/tree/main/app/src/main/java/com/example/notepad) I have uploaded the code as it currently stands, and I would be extremely happy if you could spare a moment to review the entire project. I apologize for my desperation, but standing still only discourages me, and I can see that you've extended a 'helping hand,' for which I am very grateful. – deadmouze May 21 '23 at 19:02
1

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

zaid khan
  • 825
  • 3
  • 11