Siru malayil
Siru malayil

Reputation: 355

Insert and Read Data from RoomDb by using LiveData and JetPack Compose Android Kotlin

This project implements a basic note-taking application using Jetpack Compose, Room Database, and LiveData. Here’s an overview of the key components and code flow:

Main Components: Database and Data Models:

Notes: A data entity representing a note with an ID, title, and description.
NotesDao: Data Access Object (DAO) containing functions for database operations (insert, delete, fetch).
NotesDatabase: Singleton class for Room database configuration, providing access to the DAO.
Repository:

NotesRepository: Abstracts data operations, connecting the ViewModel to the Room database. Provides methods to fetch, add, and delete notes.
ViewModel:

NoteViewModel: Manages UI-related data by exposing LiveData for observing notes and handles adding/removing notes in a background thread using Kotlin coroutines.
NoteViewModelFactory: Provides the ViewModel with a repository instance.
UI Components (Jetpack Compose):

MainActivity: Initializes the database, repository, ViewModel, and renders the NoteScreen composable.
NoteScreen: Displays the list of notes and allows adding/removing notes via input fields and a save button. Uses LiveData to observe and update the UI when the data changes.
NoteRow: Renders individual notes in a list.
NoteInput: Input field for note title and description.
NoteButton: Button for saving notes.
Code Flow:
MainActivity initializes the Room database, repository, and ViewModel.
NoteScreen observes the LiveData from the ViewModel and renders the list of notes using a LazyColumn.
The user can add a new note via input fields and the save button, triggering the addNote() function in the ViewModel.
The removeNote() function handles note deletion.
The ViewModel interacts with the repository, which in turn communicates with Room to perform database operations.
The app is designed using modern Android architecture principles, leveraging Jetpack Compose for UI, Room for data persistence, and LiveData/ViewModel for lifecycle-aware data handling.

Upvotes: 1

Views: 135

Answers (1)

Siru malayil
Siru malayil

Reputation: 355

    Add below libraries module app gradle file

    plugins {
       alias(libs.plugins.kotlin.kapt)
    }
    
    // Room DB
    implementation (libs.androidx.room.ktx)
    implementation (libs.androidx.room.runtime)
    annotationProcessor (libs.androidx.room.compiler)
    kapt (libs.androidx.room.compiler)
    
    //LiveData
    implementation(libs.androidx.lifecycle.viewmodel.compose)
    implementation(libs.androidx.runtime.livedata)
    
    [libraries]
    androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomKtx" }
    androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" }
    androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomKtx" }
    androidx-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "runtimeLivedata" }
    androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" }
    
    
    [versions]
    kaptVersion = "1.9.0"
    runtimeLivedata = "1.7.0"
    roomKtx = "2.6.1"
    lifecycleRuntimeKtx = "2.8.4"
    agp = "8.4.0-rc02"
    kotlin = "1.9.0"
    
    [plugins]
    android-application = { id = "com.android.application", version.ref = "agp" }
    jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
    kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kaptVersion"}


//build gradle file(Project level)
   plugins{
      alias(libs.plugins.kotlin.kapt) apply false
   }


//MainActivity
   
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            NoteAppTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    val userDao = NotesDatabase.getDatabase(this).notesDao()
                    val repository = NotesRepository(userDao)
                    val factory = NoteViewModelFactory(repository)
                    val noteViewModel = ViewModelProvider(this,factory)[NoteViewModel::class.java]

                    NoteScreen(noteViewModel)
                }
            }
        }
    }
}

@Composable
fun NoteScreen(noteViewModel: NoteViewModel? = viewModel()) {
    val noteListState = noteViewModel?.readAllData?.observeAsState(initial = emptyList())
    val noteList = noteListState?.value ?: emptyList()

    NoteScreen(notes = noteList,
        onAddNote = {
            noteViewModel?.addNote(it)
        },
        onRemoveNote = {
            noteViewModel?.removeNote(it)
        })
}


// NoteScreen
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NoteScreen(notes: List<Notes>, onAddNote: (Notes) -> Unit,
                onRemoveNote: (Notes) -> Unit) {
    var title by remember {
        mutableStateOf("")
    }
    var description by remember {
        mutableStateOf("")
    }


    Column {
        TopAppBar(title = {
            Text(text = stringResource(id = R.string.app_name))
        }, actions = {
            Icon(imageVector = Icons.Rounded.Notifications, contentDescription = "Notification icon")
        }, colors = TopAppBarDefaults.topAppBarColors(
            containerColor = Color.LightGray
        ))
        val context = LocalContext.current

        Column(modifier = Modifier.padding(16.dp)) {
            NoteInput(modifier = Modifier.fillMaxWidth(), label = "Title", text = title, onTextChange = {
                if(it.all { char ->
                    char.isLetter() || char.isWhitespace()
                }) title = it
            })

            Spacer(modifier = Modifier.height(10.dp))

            NoteInput(modifier = Modifier.fillMaxWidth(), label = "Add a note", text = description, onTextChange = {
                if (it.all { char ->
                    char.isLetter() || char.isWhitespace()
                    }) description = it
            })

            Spacer(modifier = Modifier.height(10.dp))

            NoteButton(text = "Save",
                enabled = title.isNotEmpty() && description.isNotEmpty(),
                onClick = {
                if (title.isNotEmpty() && description.isNotEmpty()) {
                    onAddNote(Notes(title = title, description = description))
                    Toast.makeText(context, "Added note $title", Toast.LENGTH_SHORT).show()
                    title = ""
                    description = ""
                }
            }, modifier = Modifier.align(
                alignment = Alignment.CenterHorizontally
            ))

            Divider(modifier = Modifier.padding(10.dp))

            LazyColumn {
                items(notes) { note ->
                    NoteRow(onClick = {
                         onRemoveNote(note)
                    }, note = note)
                }
            }

        }
    }
}

@Composable
fun NoteRow(
    modifier: Modifier = Modifier,
    onClick: (Notes) -> Unit,
    note: Notes,
){
    Surface(modifier = modifier
        .padding(10.dp)
        .clip(RoundedCornerShape(topEnd = 33.dp, bottomStart = 33.dp))
        .fillMaxWidth(),
        color = Color.Cyan,
        tonalElevation = 4.dp){
        Column(modifier = modifier
            .clickable { onClick(note) }
            .padding(horizontal = 12.dp, vertical = 8.dp),
            horizontalAlignment = Alignment.Start) {
            Text(text = note.title, style = MaterialTheme.typography.titleSmall)
            Text(text = note.description, style = MaterialTheme.typography.bodySmall)
        }
    }
}

//Note Input class
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun NoteInput(
    modifier: Modifier,
    label: String,
    text: String,
    maxLine: Int = 1,
    onTextChange: (String) -> Unit,
    onImeAction: () -> Unit = {}
) {

    val keyboardController = LocalSoftwareKeyboardController.current

    TextField(value = text, onValueChange = onTextChange,
        colors = TextFieldDefaults.textFieldColors(
            containerColor = Color.Transparent
        ),
        label = { Text(text = label) },
        maxLines = maxLine,
        keyboardOptions = KeyboardOptions.Default.copy(
            imeAction = ImeAction.Done
        ),
        keyboardActions = KeyboardActions(onDone = {
            onImeAction()
            keyboardController?.hide()
        }),
        modifier = modifier
    )
}

@Composable
fun NoteButton(
    modifier: Modifier = Modifier,
    text: String,
    onClick: () -> Unit,
    enabled: Boolean = false,
) {
    Button(onClick = { onClick.invoke() },
        shape = CircleShape,
        enabled = enabled,
        modifier = modifier,
        ) {
        Text(text = text)
    }
}


//Repository
class NotesRepository(private val notesDao: NotesDao) {


    fun fetchNotes(): LiveData<List<Notes>> = notesDao.fetchNotesData()

    suspend fun addNotes(notes: Notes) = notesDao.insertData(notes)
}

//Data entity model
@Entity(tableName = "notes_table")
data class Notes(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    val title: String,
    val description: String
)

//Dao Class
@Dao
interface NotesDao {

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insertData(note: Notes)

    @Query("SELECT * FROM notes_table ORDER BY id ASC")
    fun fetchNotesData(): LiveData<List<Notes>>

    @Delete
    suspend fun deleteNote(notes: Notes)

}

//Database
@Database(entities = [Notes::class], version = 1, exportSchema = false)
abstract class NotesDatabase : RoomDatabase() {

    abstract fun notesDao(): NotesDao

    companion object {

        var DATABASE_INSTANCE: NotesDatabase? = null

        fun getDatabase(context: Context): NotesDatabase {
            val tempInstance = DATABASE_INSTANCE
            if (tempInstance != null) {
                return tempInstance
            }
            synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    NotesDatabase::class.java,
                    "notes_database"
                ).build()
                DATABASE_INSTANCE = instance
                return instance
            }
        }
    }

}

//Repository
class NotesRepository(private val notesDao: NotesDao) {


    fun fetchNotes(): LiveData<List<Notes>> = notesDao.fetchNotesData()

    suspend fun addNotes(notes: Notes) = notesDao.insertData(notes)

    suspend fun deleteNote(notes: Notes) = notesDao.deleteNote(notes)

}


//ViewModel

import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.noteapp.model.Notes
import com.example.noteapp.repository.NotesRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class NoteViewModel(private val repository: NotesRepository) : ViewModel() {

    var readAllData: LiveData<List<Notes>>

    init {
        readAllData = repository.fetchNotes()
    }

    fun addNote(note: Notes) {
        viewModelScope.launch(Dispatchers.IO) {
            repository.addNotes(note)
        }
    }

    fun removeNote(note: Notes) {
        noteList.remove(note)
    }

    fun getAllNotes(): LiveData<List<Notes>> {
        return readAllData
    }

}

class NoteViewModelFactory(private val repository: NotesRepository) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(NoteViewModel::class.java)) {
            return NoteViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

Upvotes: 0

Related Questions