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.