Search code examples
androidkotlinandroid-roomandroid-jetpack-composedagger-hilt

Save User input on a screen composable and in a room database using Compose


My goal is to save data that the user inputs in a field ( my case outlinedtextfield ) and store that data both on screen for the user to see and in a local database using room. I have created the basics of room ( Database, Dao, Data class, Repository, RepositoryImpl and a viewmodel ), but I cant figure out how to save the user input into it/ take user input and save it to the database I created. I want to save both string input and Int input. How can I achieve this while following best practices with dependency injection ?

My current information:

Main Activity: https://gyazo.com/163dfa890d4ac10c2bd28c7876f25b4a

Data class:

@Entity(tableName = "student_table")
data class Student(

    @PrimaryKey(autoGenerate = true) val id: Int?,
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?,
    @ColumnInfo(name = "phone_number") val phoneNumber: Int?

)

My Dao:

@Dao
interface StudentDao {

    @Query("SELECT * FROM student_table")
    fun getAll(): Flow<List<Student>>

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(student: Student)

    @Update
    fun update(student: Student)

    @Delete
    fun delete(student: Student)

    @Delete
    fun deleteAll(student: Student)

}

My Database:

@Database(entities = [Student::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {

    abstract fun studentDao() : StudentDao

    companion object {

        @Volatile
        private var INSTANCE : AppDatabase? = null

        fun getDatabase(context: Context) : AppDatabase {

            val tempInstance = INSTANCE
            if (tempInstance != null) {

                return tempInstance
            }

            synchronized(this) {

                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                return instance
            }
        }
    }
}

My repository:

interface StudentRepository {

    suspend fun getAllStudentsFromRoom(): Flow<List<Student>>

    suspend fun addStudent(student: Student)

    suspend fun updateStudent(student: Student)

    suspend fun deleteStudent(student: Student)
}

My Implementation Repository

class StudentRepositoryImpl(
        private val studentDao: StudentDao
) : StudentRepository {
    override suspend fun getAllStudentsFromRoom(): Flow<List<Student>> = studentDao.getAll()

    override suspend fun addStudent(student: Student) = studentDao.insert(student)

    override suspend fun updateStudent(student: Student) = studentDao.update(student)

    override suspend fun deleteStudent(student: Student) = studentDao.delete(student)

}

My ViewModel:

@HiltViewModel
class StudentViewModel @Inject constructor(
    private val repo: StudentRepository
) : ViewModel() {

    fun addStudent(student: Student) = viewModelScope.launch(Dispatchers.IO) {
        repo.addStudent(student)
    }

    fun updateStudent(student: Student) = viewModelScope.launch(Dispatchers.IO) {
        repo.updateStudent(student)
    }

    fun deleteStudent(student: Student) = viewModelScope.launch(Dispatchers.IO) {
        repo.deleteStudent(student)
    }


}

Solution

  • Update 2022.08.16

    I managed to solve the problem. There was a few things missing that I needed to add in order for it to work.

    1. I needed to attribute @AndroidEntryPoint on the mainActivity. enter image description here

    2. I needed to create an application class for my project: https://gyazo.com/326ac277d951814cf77a255afa438794

    3. After creating the application class, I had to "call it" in the Android manifest by adding it in the following format in the application: "android:name=".application.MyDatabaseProjekt2022" Like this: enter image description here

    4. I also needed to create an appModule class, I did this by doing the following: https://gyazo.com/0fed4a454d56e696c08cd98f02c7c98f

    5. I also took some information suggested by FishHawk and created a simple UI to display the information. rememberSavable is used to save user information on screen incase the user navigates away from that composable screen! This is the result:

    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.Spacer
    import androidx.compose.foundation.layout.height
    import androidx.compose.foundation.text.KeyboardOptions
    import androidx.compose.material.Button
    import androidx.compose.material.OutlinedTextField
    import androidx.compose.material.Text
    import androidx.compose.runtime.*
    import androidx.compose.runtime.saveable.rememberSaveable
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.text.input.KeyboardType
    import androidx.compose.ui.unit.dp
    import androidx.lifecycle.viewmodel.compose.viewModel
    import com.example.MyDatabaseProjekt2022.data.Student
    import com.example.MyDatabaseProjekt2022.viewmodel.StudentViewModel
    
    @Composable
    fun Test() {
        val viewModel = viewModel<StudentViewModel>()
        Column {
            var id: Int by rememberSaveable { mutableStateOf(0) }
            var firstName by rememberSaveable { mutableStateOf("") }
            var lastName by rememberSaveable { mutableStateOf("") }
            var phoneNumber by rememberSaveable { mutableStateOf("") }
    
            Spacer(modifier = Modifier.height(20.dp))
            OutlinedTextField(value = firstName, onValueChange = { firstName = it },
                label = { Text(text = "First Name")},
                singleLine = true,
            )
    
            Spacer(modifier = Modifier.height(20.dp))
            OutlinedTextField(value = lastName, onValueChange = { lastName = it },
                label = { Text(text = "Last Name")},
                singleLine = true
                )
    
            Spacer(modifier = Modifier.height(20.dp))
            OutlinedTextField(
                value = phoneNumber,
                onValueChange = { phoneNumber = it },
                label = { Text(text = "Phone Number")},
                keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Phone),
                singleLine = true,
            )
    
            Spacer(modifier = Modifier.height(20.dp))
    
            Button(onClick = {
                val realPhoneNumber = phoneNumber
                val student = Student(id,firstName, lastName, realPhoneNumber)
                viewModel.addStudent(student)
            }) { Text(text = "Save Student") }
        }
    }
    
    
    1. In order to verify whether or not the information gets saved in the database, do the following. Run the app and go to: View -> Tool windows -> App inspection

    2. Type in the information (firstname, lastname, phonenumber), press the save student button and database should show up like this: https://gyazo.com/b85766f0ffa4470036cf5d7c4dd9d9f7

    3. Double tap student_table and another window next to that should pop up with the saved information that you put in.

    Hopefully this is everything you need in order to make a functional and working database. This took me around 2 months to figure out ( I do programming as a hobby and Im still in the "early stage" ). Hopefully someone here will find it useful. Happy coding!