Ejemplo: Notes app

Creación de una aplicación de notas usando Room y MVVM

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 los fragments para añadir y editar notas

fragment_add_note.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">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".NewNoteFragment"
        android:padding="12dp">

        <TextView
            android:id="@+id/addNoteHeading"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_marginTop="16dp"
            android:text="Add Note."
            android:textColor="@color/red"
            android:textSize="24sp"
            android:textStyle="bold" />

        <EditText
            android:id="@+id/addNoteTitle"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:hint="Enter the title"
            android:padding="12dp"
            android:textSize="20sp"
            android:layout_below="@id/addNoteHeading"
            android:background="@drawable/pink_border"
            android:layout_marginTop="16dp"
            android:maxLines="1" />

        <EditText
            android:id="@+id/addNoteDesc"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:hint="Enter the description"
            android:gravity="top"
            android:padding="12dp"
            android:textSize="18sp"
            android:background="@drawable/pink_border"
            android:layout_below="@id/addNoteTitle"
            android:layout_marginTop="12dp" />

    </RelativeLayout>
</layout>

AddNoteFragment

class AddNoteFragment : Fragment(R.layout.fragment_add_note), MenuProvider {

    private var addNoteBinding: FragmentAddNoteBinding? = null
    private val binding get() = addNoteBinding!!

    private lateinit var notesViewModel: NoteViewModel
    private lateinit var addNoteView: View

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        addNoteBinding = FragmentAddNoteBinding.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
        addNoteView = view
    }

    private fun saveNote(view: View){
        val noteTitle = binding.addNoteTitle.text.toString().trim()
        val noteDesc = binding.addNoteDesc.text.toString().trim()

        if (noteTitle.isNotEmpty()){
            val note = Note(0, noteTitle, noteDesc)
            notesViewModel.addNote(note)

            Toast.makeText(addNoteView.context, "Note Saved", Toast.LENGTH_SHORT).show()
            view.findNavController().popBackStack(R.id.homeFragment, false)
        } else {
            Toast.makeText(addNoteView.context, "Please enter note title", Toast.LENGTH_SHORT).show()
        }
    }

    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        menu.clear()
        menuInflater.inflate(R.menu.menu_add_note, menu)
    }

    override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        return when(menuItem.itemId){
            R.id.saveMenu -> {
                saveNote(addNoteView)
                true
            }
            else -> false
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        addNoteBinding = null
    }
}

fragment_edit_note

<?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">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="12dp"
        tools:context=".UpdateNoteFragment">

        <TextView
            android:id="@+id/editNoteHeading"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_marginTop="16dp"
            android:text="Edit Note."
            android:textStyle="bold"
            android:textColor="@color/red"
            android:textSize="24sp" />

        <EditText
            android:id="@+id/editNoteTitle"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:hint="Enter the title"
            android:padding="12dp"
            android:textSize="20sp"
            android:layout_below="@id/editNoteHeading"
            android:background="@drawable/pink_border"
            android:layout_marginTop="16dp"
            android:maxLines="1" />

        <EditText
            android:id="@+id/editNoteDesc"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:hint="Enter the description"
            android:gravity="top"
            android:padding="12dp"
            android:textSize="18sp"
            android:background="@drawable/pink_border"
            android:layout_below="@id/editNoteTitle"
            android:layout_marginTop="12dp" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/editNoteFab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="20dp"
            android:layout_marginBottom="20dp"
            android:clickable="true"
            android:backgroundTint="@color/pink"
            android:tintMode="@color/white"
            android:src="@drawable/baseline_done_24"
            android:layout_alignParentBottom="true"
            android:layout_alignParentEnd="true"
            android:contentDescription="editFab" />

    </RelativeLayout>
</layout>

EditNoteFragment

class EditNoteFragment : Fragment(R.layout.fragment_edit_note), MenuProvider {

    private var editNoteBinding: FragmentEditNoteBinding? = null
    private val binding get() = editNoteBinding!!

    private lateinit var notesViewModel: NoteViewModel
    private lateinit var currentNote: Note

    private val args: EditNoteFragmentArgs by navArgs()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        editNoteBinding = FragmentEditNoteBinding.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
        currentNote = args.note!!

        binding.editNoteTitle.setText(currentNote.noteTitle)
        binding.editNoteDesc.setText(currentNote.noteDesc)

        binding.editNoteFab.setOnClickListener {
            val noteTitle = binding.editNoteTitle.text.toString().trim()
            val noteDesc = binding.editNoteDesc.text.toString().trim()

            if (noteTitle.isNotEmpty()){
                val note = Note(currentNote.id, noteTitle, noteDesc)
                notesViewModel.updateNote(note)
                view.findNavController().popBackStack(R.id.homeFragment, false)
            } else {
                Toast.makeText(context, " Please enter note title", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun deleteNote(){
        AlertDialog.Builder(activity).apply {
            setTitle("Delete Note")
            setMessage("Do you want to delete this note?")
            setPositiveButton("Delete"){_,_ ->
                notesViewModel.deleteNote(currentNote)
                Toast.makeText(context, " Note Deleted", Toast.LENGTH_SHORT).show()
                view?.findNavController()?.popBackStack(R.id.homeFragment, false)
            }
            setNegativeButton("Cancel", null)
        }.create().show()
    }

    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        menu.clear()
        menuInflater.inflate(R.menu.menu_edit_note, menu)
    }

    override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        return when(menuItem.itemId){
            R.id.deleteMenu -> {
                deleteNote()
                true
            } else -> false
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        editNoteBinding = null
    }
}

Crear la navegación entre fragments

Navigation graph

NavHost

NavController

otra forma de crear la navegación

añadir el contenedor de fragments en el activity_main

<?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="androidx.navigation.fragment.NavHostFragment"
            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:defaultNavHost="true"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:navGraph="@navigation/nav_graph"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

añadir los 3 fragments y la comunicación entre ellos

tools:layout="@layout/fragment_home"
 
tools:layout="@layout/fragment_add_note"
 
tools:layout="@layout/fragment_edit_note"

navigation/nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation
    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"
    android:id="@+id/nav_graph"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.notesapp.fragments.HomeFragment"
        android:label="HomeFragment"
        tools:layout="@layout/fragment_home">
        <action
            android:id="@+id/action_homeFragment_to_addNoteFragment"
            app:destination="@id/addNoteFragment" />
        <action
            android:id="@+id/action_homeFragment_to_editNoteFragment"
            app:destination="@id/editNoteFragment" />
    </fragment>
    <fragment
        android:id="@+id/addNoteFragment"
        android:name="com.example.notesapp.fragments.AddNoteFragment"
        android:label="AddNoteFragment"
        tools:layout="@layout/fragment_add_note">
        <action
            android:id="@+id/action_addNoteFragment_to_homeFragment"
            app:destination="@id/homeFragment" />
    </fragment>
    <fragment
        android:id="@+id/editNoteFragment"
        android:name="com.example.notesapp.fragments.EditNoteFragment"
        android:label="EditNoteFragment"
        tools:layout="@layout/fragment_edit_note">
        <action
            android:id="@+id/action_editNoteFragment_to_homeFragment"
            app:destination="@id/homeFragment" />
        <argument
            android:name="Note"
            app:argType="com.example.notesapp.model.Note"
            app:nullable="true" />
    </fragment>
</navigation>

modificar el listener del botón flotante en HomeFragment

binding.addNoteFab.setOnClickListener {
    it.findNavController().navigate(R.id.action_homeFragment_to_addNoteFragment)
    // Toast.makeText(context, "Add Button Clicked", Toast.LENGTH_SHORT).show()
}

modificar el listener del click en un elemento de la lista en el adapter

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

 

Tarea presencial de la unidad 3

 

Más información:

Lambdas y Variables función en Kotlin

DiffUtil

Animaciones fáciles con DiffUtil

Deja una respuesta