Room en Kotlin
Uso de Room en Kotlin
Creación de una aplicación de manejo de una base de datos con Room
The Notes App – Prerequisites & XML Design
– Packages
1. adapters: NoteAdapter
2. db: NoteDao & NoteDatabase.
3. model: Note.
4. repository: NoteRepository.
5. viewmodel: MainActivity, NoteViewModel & NoteViewModelFactory
6. fragments: AddNoteFragment, EditNoteFragment & HomeFragment
– Resources
1. drawable: add, delete, done, search, emptybkg image & pink_border.xml
2. font: poppins
3. layout: activity_main, fragment_add_note, fragment_edit_note, fragment_home, note_layout.xml
4. menu: home_menu, menu_add_note, menu_edit_note.xml
5. navigation: nav_graph
6. values: colors, strings, themes.xml
7. gradle: project gradle & module gradle
Crear un proyecto llamado NotesApp con Android Studio Meerkat
Esquema de MVVM?
MainActivity – ViewModel – Repositorio – Room
buid.gradle (Project: NotesApp)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
}
dependencies {
// val navVersion = "2.8.9"
classpath(libs.androidx.navigation.safe.args.gradle.plugin)
}
}
plugins {
id ("com.android.application") version "8.9.1" apply false
id ("com.android.library") version "8.9.1" apply false
id ("org.jetbrains.kotlin.android") version "2.1.20" apply false
id ("com.google.devtools.ksp") version "2.1.20-1.0.32" apply false
}
build.gradle (Module: app)
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id ("kotlin-parcelize")
id("com.google.devtools.ksp")
id ("androidx.navigation.safeargs")
}
android {
namespace = "com.example.notesapp"
compileSdk = 35
defaultConfig {
applicationId = "com.example.notesapp"
minSdk = 28
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures{
dataBinding = true
viewBinding = true
}
buildToolsVersion = "35.0.0"
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.navigation.fragment)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit.v115)
androidTestImplementation(libs.androidx.espresso.core.v351)
// ROOM
// val roomVersion = "2.6.1"
implementation (libs.androidx.room.runtime)
ksp(libs.androidx.room.compiler)
// Coroutines
implementation(libs.kotlinx.coroutines.android)
implementation (libs.androidx.room.ktx)
// val fragment_version = "1.8.6"
// Kotlin
implementation(libs.androidx.fragment.ktx)
// Navigation
// val navVersion = "2.8.9 "
// implementation(libs.androidx.navigation.fragment.ktx)
// implementation(libs.androidx.navigation.ui.ktx)
// val nav_version = "2.8.9" // Or the latest version
// implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
// implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
// Life Cycle Arch
// val lifecycleVersion = "2.8.7"
// ViewModel
implementation(libs.androidx.lifecycle.viewmodel.ktx)
// LiveData
implementation(libs.androidx.lifecycle.livedata.ktx)
// Annotation processor
ksp(libs.androidx.lifecycle.compiler)
}
Note
@Entity(tableName = "notes")
@Parcelize
data class Note(
@PrimaryKey(autoGenerate = true)
val id: Int,
val noteTitle: String,
val noteDesc: String
): Parcelable
NoteDao
@Dao
interface NoteDao {
@Query("SELECT * FROM NOTES ORDER BY id DESC")
fun getAllNotes(): LiveData<List<Note>>
@Query("SELECT * FROM NOTES WHERE noteTitle LIKE :query OR noteDesc LIKE :query")
fun searchNote(query: String?): LiveData<List<Note>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertNote(note: Note)
@Update
suspend fun updateNote(note: Note)
@Delete
suspend fun deleteNote(note: Note)
}
NoteDatabase
@Database(entities = [Note::class], version = 1)
abstract class NoteDatabase: RoomDatabase() {
abstract fun getNoteDao(): NoteDao
companion object{
@Volatile
private var instance: NoteDatabase? = null
private val LOCK = Any()
private const val DATABASE_NAME = "notesDB"
operator fun invoke(context: Context) = instance ?:
synchronized(LOCK){
instance ?:
createDatabase(context).also{
instance = it
}
}
private fun createDatabase(context: Context) =
Room.databaseBuilder(
context.applicationContext,
NoteDatabase::class.java,
DATABASE_NAME
).build()
}
}
NoteRepository
class NoteRepository(private val db: NoteDatabase) {
fun getAllNotes() = db.getNoteDao().getAllNotes()
fun searchNote(query: String?) = db.getNoteDao().searchNote(query)
suspend fun insertNote(note: Note) = db.getNoteDao().insertNote(note)
suspend fun deleteNote(note: Note) = db.getNoteDao().deleteNote(note)
suspend fun updateNote(note: Note) = db.getNoteDao().updateNote(note)
}
NoteViewModel
class NoteViewModel(app: Application, private val noteRepository: NoteRepository): AndroidViewModel(app) {
fun getAllNotes() = noteRepository.getAllNotes()
fun searchNote(query: String?) =
noteRepository.searchNote(query)
fun addNote(note: Note) =
viewModelScope.launch {
noteRepository.insertNote(note)
}
fun deleteNote(note: Note) =
viewModelScope.launch {
noteRepository.deleteNote(note)
}
fun updateNote(note: Note) =
viewModelScope.launch {
noteRepository.updateNote(note)
}
}
NoteViewModelFactory
class NoteViewModelFactory(val app: Application, private val noteRepository: NoteRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return NoteViewModel(app, noteRepository) as T
}
}
res/values/themes/themes.xml
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.NotesApp" parent="Theme.Material3.DayNight">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.NotesApp" parent="Base.Theme.NotesApp" />
</resources>
res/values/themes/themes.xml (night)
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.NotesApp" parent="Theme.Material3.DayNight">
<!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
</style>
</resources>
res/drawable/emptybkg.png
res/values/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="pink">#F88379</color>
<color name="red">#c13b34</color>
</resources>
res/drawable/pink_border.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="1dp"
android:color="@color/pink"/>
<corners
android:radius="8dp"/>
</shape>
Añadir los iconos:
res/drawable/baseline_add_24.xml
res/drawable/baseline_delete_24.xml
res/drawable/baseline_done_24.xml
res/drawable/baseline_search_24.xml
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView"
android:name="com.example.notesapp.fragments.HomeFragment"
tools:layout="@layout/fragment_home"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="1dp"
android:layout_marginTop="1dp"
android:layout_marginEnd="1dp"
android:layout_marginBottom="1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding // Declare the binding variable
lateinit var noteViewModel: NoteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater) // Inflate the binding
setContentView(binding.root) // Use binding.root as the content view
/*
val fragmentManager = supportFragmentManager
if (savedInstanceState == null) {
val homeFragment = HomeFragment()
fragmentManager.commit {
setReorderingAllowed(true)
add(binding.fragmentContainerView.id, homeFragment) // Use binding to access views
}
}
*/
setupViewModel()
}
private fun setupViewModel(){
val noteRepository = NoteRepository(NoteDatabase(this))
val viewModelProviderFactory = NoteViewModelFactory(application, noteRepository)
noteViewModel = ViewModelProvider(this, viewModelProviderFactory)[NoteViewModel::class.java]
}
}
layout/note_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardElevation="6dp"
app:cardCornerRadius="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/pink_border"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/noteTitle"
android:layout_weight="200"
android:textColor="@color/red"
android:fontFamily="@font/poppins"
android:text="Note Title"
android:textStyle="bold"
android:textSize="18sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/noteDesc"
android:layout_marginTop="8dp"
android:maxHeight="170dp"
android:text="Description"
android:textSize="14sp"
android:fontFamily="@font/poppins"
android:textColor="@color/pink"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</layout>
NoteAdapter
class NoteAdapter : RecyclerView.Adapter<NoteAdapter.NoteViewHolder>() {
class NoteViewHolder(val itemBinding: NoteLayoutBinding): RecyclerView.ViewHolder(itemBinding.root)
private val differCallback = object : DiffUtil.ItemCallback<Note>(){
override fun areItemsTheSame(oldItem: Note, newItem: Note): Boolean {
return oldItem.id == newItem.id &&
oldItem.noteDesc == newItem.noteDesc &&
oldItem.noteTitle == newItem.noteTitle
}
override fun areContentsTheSame(oldItem: Note, newItem: Note): Boolean {
return oldItem == newItem
}
}
val differ = AsyncListDiffer(this, differCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
return NoteViewHolder(
NoteLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
}
override fun getItemCount(): Int {
return differ.currentList.size
}
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
val currentNote = differ.currentList[position]
holder.itemBinding.noteTitle.text = currentNote.noteTitle
holder.itemBinding.noteDesc.text = currentNote.noteDesc
holder.itemView.setOnClickListener {
// val direction = HomeFragmentDirections.actionHomeFragmentToEditNoteFragment(currentNote)
// it.findNavController().navigate(direction)
}
}
}
menu/home_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/searchMenu"
android:icon="@drawable/baseline_search_24"
android:title="Search"
app:showAsAction="ifRoom"
app:actionViewClass="androidx.appcompat.widget.SearchView" />
</menu>
layout/fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".HomeFragment">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/addNoteFab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_marginBottom="28dp"
android:backgroundTint="@color/pink"
android:clickable="true"
android:contentDescription="image"
android:tintMode="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/baseline_add_24" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/homeRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/emptyNotesImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="40dp"
android:layout_marginTop="60dp"
android:src="@drawable/emptybkg"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
HomeFragment
class HomeFragment : Fragment(R.layout.fragment_home), SearchView.OnQueryTextListener,
MenuProvider {
private var homeBinding: FragmentHomeBinding? = null
private val binding get() = homeBinding!!
private lateinit var notesViewModel : NoteViewModel
private lateinit var noteAdapter: NoteAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
homeBinding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
notesViewModel = (activity as MainActivity).noteViewModel
// addNotes()
setupHomeRecyclerView()
binding.addNoteFab.setOnClickListener {
// it.findNavController().navigate(R.id.action_homeFragment_to_addNoteFragment)
Toast.makeText(context, "Add Button Clicked", Toast.LENGTH_SHORT).show()
}
}
private fun addNotes() {
notesViewModel.addNote(Note(0, "Ejercicios", "Entregar ejercicios de programacion"))
notesViewModel.addNote(Note(0, "Fórmula 1", "Ver la carrera de Fernando Alonso"))
notesViewModel.addNote(Note(0, "Bici", "Dar un paseo en bici"))
}
private fun updateUI(note: List<Note>?){
if (note != null){
if (note.isNotEmpty()){
binding.emptyNotesImage.visibility = View.GONE
binding.homeRecyclerView.visibility = View.VISIBLE
} else {
binding.emptyNotesImage.visibility = View.VISIBLE
binding.homeRecyclerView.visibility = View.GONE
}
}
}
private fun setupHomeRecyclerView(){
noteAdapter = NoteAdapter()
binding.homeRecyclerView.apply {
// layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
setHasFixedSize(true)
adapter = noteAdapter
}
activity?.let {
notesViewModel.getAllNotes().observe(viewLifecycleOwner){ note ->
noteAdapter.differ.submitList(note)
updateUI(note)
}
}
}
private fun searchNote(query: String?){
val searchQuery = "%$query"
notesViewModel.searchNote(searchQuery).observe(this) {list ->
noteAdapter.differ.submitList(list)
}
}
override fun onQueryTextSubmit(p0: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
if (newText != null){
searchNote(newText)
}
return true
}
override fun onDestroy() {
super.onDestroy()
homeBinding = null
}
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menu.clear()
menuInflater.inflate(R.menu.home_menu, menu)
val menuSearch = menu.findItem(R.id.searchMenu).actionView as SearchView
menuSearch.isSubmitButtonEnabled = false
menuSearch.setOnQueryTextListener(this)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return false
}
}
Más información:




Deja una respuesta
Lo siento, debes estar conectado para publicar un comentario.