Como usar ROOM(SQLite) com kotlin no Android

Como usar ROOM(SQLite) com kotlin no Android

Neste tutorial de como usar ROOM(SQLite) com kotlin no Android, escrevi esse pequeno aplicativo que apenas cria uma nota(tarefa) e tem um botão de check, para dizer que foi feita ou ainda não.

Primeiramente vamos criar um projeto vazio(Empty) com o nome de br.com.uware.room para começar a programar.

Montando a base do projeto

Então logo após ter criado o projeto vamos deixar a base do projeto certa, adicionando as bibliotecas que vamos utilizar ao gradle.

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    // kapt - Anotações Necessário para Room
    id 'kotlin-kapt'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "br.com.uware.room"
        minSdkVersion 24
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildFeatures {
        viewBinding true
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'

    // ViewModel, fragment e activity-ktx
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
    implementation 'androidx.activity:activity-ktx:1.2.2'
    implementation "androidx.fragment:fragment-ktx:1.3.2"

    // Coroutines
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'

    // Room
    implementation "androidx.room:room-ktx:2.2.6"
    kapt "androidx.room:room-compiler:2.2.6"

    // Tests
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'


}

[ads]

Logo após ter acertado o nosso arquivo de gradle com as bibliotecas que vamos usar, vamos então escrever as strings usadas e as cores.

Primeiramente vamos acertar os valores de nossas strings no arquivo de strings.xml como o seguinte.

<resources>
    <string name="app_name">Room</string>
    <string name="note">Anotação</string>
    <string name="save">SALVAR</string>
    <string name="cancel">CANCELAR</string>
    <string name="delete">APAGAR</string>
    <string name="edit_note">Editar Nota</string>
    <string name="add_note">Adicionar Nota</string>
</resources>

Agora vamos acertar as nossas cores no nosso arquivo de colors.xml como o seguinte.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="purple_200">#FFBB86FC</color>
    <color name="purple_500">#FF6200EE</color>
    <color name="purple_700">#FF3700B3</color>
    <color name="teal_200">#FF03DAC5</color>
    <color name="teal_700">#FF018786</color>
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>

    <color name="light_gray">#d3d3d3</color>
    <color name="dark_gray">#212121</color>
</resources>

Como usar ROOM(SQLite) com kotlin no Android

Com a base do projeto pronta então agora vamos criar a nossa aplicação.

Portanto para entender melhor como separei o projeto segue abaixo uma imagem de como devem ficar os packages no projeto.

Imagem de como devem ficar os arquivos em seus determinados packages.

Para começar vamos então criar o package chamado framework e o package model dentro do mesmo.

Dentro do package model vamos criar a data class chamada Note contendo assim o seguinte código.

package br.com.uware.room.framework.model


/**
 * Created by Rodrigo Leutz on 20/04/21.
 * uware.com.br
 * rodrigo@uware.com.br
 */

data class Note(
    val _id: Long = 0L,
    var name: String = "",
    var checked: Boolean = false
)

[ads]

Database(SQLite)

Agora chegou a parte importante que é criar a nossa database.

Em primeiro lugar vamos criar o package db dentro do package framework para que criemos os arquivos que vão controlar nossa database.

Primeiro vamos criar a classe NoteEntity e depois as interfaces que são como contratos para nossas classes.

package br.com.uware.room.framework.db

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import br.com.uware.room.framework.model.Note


/**
 * Created by Rodrigo Leutz on 20/04/21.
 * uware.com.br
 * rodrigo@uware.com.br
 */

@Entity(tableName = "notes")
class NoteEntity(
    @ColumnInfo(name = "note_id")
    @PrimaryKey(autoGenerate = true)
    val _id: Long,
    @ColumnInfo(name = "note_name")
    val name: String,
    @ColumnInfo(name = "note_checked")
    val checked: Boolean
) {
    companion object {
        fun fromNote(note: Note) = NoteEntity(note._id, note.name, note.checked)
    }

    fun toNote() = Note(_id, name, checked)
}

Vamos então criar 2 arquivos de interfaces com o nome de NoteDataSource e NoteDao.

package br.com.uware.room.framework.db

import br.com.uware.room.framework.model.Note


/**
 * Created by Rodrigo Leutz on 20/04/21.
 * uware.com.br
 * rodrigo@uware.com.br
 */

interface NoteDataSource {
    suspend fun add(note: Note)
    suspend fun get(id: Long): Note?
    suspend fun getAll(): List<Note>
    suspend fun remove(note: Note)
}
package br.com.uware.room.framework.db

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy.REPLACE
import androidx.room.Query


/**
 * Created by Rodrigo Leutz on 20/04/21.
 * uware.com.br
 * rodrigo@uware.com.br
 */

@Dao
interface NoteDao {
    @Insert(onConflict = REPLACE)
    suspend fun addNote(noteEntity: NoteEntity)

    @Query("SELECT * FROM notes WHERE note_id = :id")
    suspend fun getNote(id: Long): NoteEntity?

    @Query("SELECT * FROM notes")
    suspend fun getAllNote(): List<NoteEntity>

    @Delete
    suspend fun deleteNote(noteEntity: NoteEntity)
}

Logo após vamos criar o arquivo de DatabaseService que irá criar a nossa database e fazer a conexão com o banco de dados.

[ads]

package br.com.uware.room.framework.db

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase


/**
 * Created by Rodrigo Leutz on 20/04/21.
 * uware.com.br
 * rodrigo@uware.com.br
 */

@Database(entities = [NoteEntity::class], version = 1)
abstract class DatabaseService: RoomDatabase() {

    companion object {
        private const val DATABASE_NAME = "notelist.db"
        private var instance: DatabaseService? = null
        private fun create(context: Context): DatabaseService =
            Room.databaseBuilder(context, DatabaseService::class.java, DATABASE_NAME)
                .fallbackToDestructiveMigration()
                .build()
        fun getInstance(context: Context): DatabaseService =
            (instance ?: create(context)).also { instance = it }
    }

    abstract fun noteDao(): NoteDao
}

Agora por último vamos então criar o arquivo de RoomNoteDataSource que é o arquivo usado para instanciar e utilizar o banco de dados que foi criado.

package br.com.uware.room.framework.db

import android.content.Context
import br.com.uware.room.framework.model.Note


/**
 * Created by Rodrigo Leutz on 20/04/21.
 * uware.com.br
 * rodrigo@uware.com.br
 */

class RoomNoteDataSource(context: Context): NoteDataSource {
    val noteDao = DatabaseService.getInstance(context).noteDao()
    override suspend fun add(note: Note) = noteDao.addNote(NoteEntity.fromNote(note))
    override suspend fun get(id: Long): Note? = noteDao.getNote(id)?.toNote()
    override suspend fun getAll(): List<Note> = noteDao.getAllNote().map { it.toNote() }
    override suspend fun remove(note: Note) = noteDao.deleteNote(NoteEntity.fromNote(note))
}

[ads]

ViewModels

Então para finalizar o nosso package de framework vamos criar mais um package chamado viewmodel dentro dele e neste package vamos criar 2 arquivos, MainViewModel e NoteViewModel que vão ser utilizados pela UI da aplicação.

package br.com.uware.room.framework.viewmodel

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import br.com.uware.room.framework.db.RoomNoteDataSource
import br.com.uware.room.framework.model.Note
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch


/**
 * Created by Rodrigo Leutz on 20/04/21.
 * uware.com.br
 * rodrigo@uware.com.br
 */

class MainViewModel(application: Application): AndroidViewModel(application) {

    private val coroutineScope = CoroutineScope(Dispatchers.IO)

    val notes = MutableLiveData<List<Note>>()

    fun getNotes(){
        coroutineScope.launch {
            val notesList = RoomNoteDataSource(getApplication()).getAll()
            notes.postValue(notesList)
        }
    }

    fun check(note: Note){
        coroutineScope.launch {
            val room = RoomNoteDataSource(getApplication())
            val newNote = room.get(note._id)
            newNote!!.checked = !newNote.checked
            room.add(newNote)
            getNotes()
        }
    }
}
package br.com.uware.room.framework.viewmodel

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import br.com.uware.room.framework.db.RoomNoteDataSource
import br.com.uware.room.framework.model.Note
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch


/**
 * Created by Rodrigo Leutz on 20/04/21.
 * uware.com.br
 * rodrigo@uware.com.br
 */

class NoteViewModel(application: Application): AndroidViewModel(application) {

    private val coroutineScope = CoroutineScope(Dispatchers.IO)

    var currentNote = MutableLiveData<Note>()

    fun save(note: Note){
        coroutineScope.launch {
            RoomNoteDataSource(getApplication()).add(note)
        }
    }

    fun get(id: Long){
        coroutineScope.launch {
            val note = RoomNoteDataSource(getApplication()).get(id)
            currentNote.postValue(note!!)
        }
    }

    fun delete(note: Note){
        coroutineScope.launch {
            RoomNoteDataSource(getApplication()).remove(note)
        }
    }
}

[ads]

UI – Como usar ROOM(SQLite) com kotlin no Android

Por fim agora vamos criar a UI da aplicação e para isso vamos começar criando um novo package na base do nosso projeto chamado presentation.

Dentro desse package presentation vamos criar mais um package chamado activity e mover a MainActivity para dentro dele e assim deixando mais separado o nosso projeto.

Então dentro desse package activity vamos criar uma nova activity com o nome de NoteActivity.

Portanto chegou a hora de criar o layout da aplicação, então vamos editar primeiramente o arquivo activity_note.xml e depois o activity_main.xml.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".presentation.activity.NoteActivity">

    <TextView
        android:id="@+id/tv_note_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/add_note"
        android:textSize="32sp"
        android:textColor="@color/dark_gray"
        android:textStyle="bold"
        android:gravity="center"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/til_note"/>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/til_note"
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:hint="@string/note"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/et_note"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </com.google.android.material.textfield.TextInputLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent">

        <Button
            android:id="@+id/btn_delete"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/delete"
            android:layout_margin="10dp"
            android:visibility="gone"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <Button
                android:id="@+id/btn_save"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="10dp"
                android:layout_marginEnd="5dp"
                android:layout_weight="1"
                android:text="@string/save" />

            <Button
                android:id="@+id/btn_cancel"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="5dp"
                android:layout_marginEnd="10dp"
                android:layout_weight="1"
                android:text="@string/cancel" />
        </LinearLayout>
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".presentation.activity.MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab_add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:src="@android:drawable/ic_input_add"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Por fim da parte do layout vamos criar duas imagens vetoriais com o nome de ic_checked.xml e ic_unchecked.xml, uma de um ícone checado e outra do ícone vazio para serem usados como checkbox.

Então vamos criar um layout para o RecyclerView com o nome de item_note.xml e o seguinte código.

<?xml version="1.0" encoding="utf-8"?>
<!--
 * Created by Rodrigo Leutz on .
 * uware.com.br
 * rodrigo@uware.com.br
-->
<LinearLayout
    android:id="@+id/ll_item_note"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/iv_check"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginStart="10dp"
        android:layout_gravity="center"
        android:src="@drawable/ic_unchecked"/>

    <TextView
        android:id="@+id/tv_note"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="22sp"
        android:text="TEST"
        android:textColor="@color/dark_gray"
        android:textStyle="bold"
        android:padding="10dp"
        android:gravity="center"/>

</LinearLayout>

[ads]

Para finalizar a nossa aplicação vamos criar mais um package dentro do package presentation com o nome de action, ele será usado como um callback em nossa RecyclerView.

Dentro desse package action vamos criar um arquivo de interface com o nome de MainAction contendo assim o seguinte código.

package br.com.uware.room.presentation.action

import br.com.uware.room.framework.model.Note


/**
 * Created by Rodrigo Leutz on 20/04/21.
 * uware.com.br
 * rodrigo@uware.com.br
 */

interface MainAction {
    fun enterNote(id: Long)
    fun check(note: Note)
}

Vamos então criar o último package dentro do nosso package presentation com o nome de adapter para conter o nosso adapter da RecyclerView.

Logo depois de ter criado o package vamos criar o arquivo do adapter com o nome de NoteListAdapter.

package br.com.uware.room.presentation.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import br.com.uware.room.R
import br.com.uware.room.databinding.ItemNoteBinding
import br.com.uware.room.framework.model.Note
import br.com.uware.room.presentation.action.MainAction


/**
 * Created by Rodrigo Leutz on 20/04/21.
 * uware.com.br
 * rodrigo@uware.com.br
 */

class NoteListAdapter(val noteList: ArrayList<Note>, 
                      val action: MainAction): 
    RecyclerView.Adapter<NoteListAdapter.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemNoteBinding.inflate(
            LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        with(holder){
            with(noteList[position]){ 
                binding.llItemNote.let {
                    if(position % 2 == 0) {
                        it.setBackgroundResource(R.color.light_gray)
                    } else {
                        it.setBackgroundResource(R.color.white)
                    }
                }
                binding.tvNote.text = this.name
                binding.tvNote.setOnClickListener {
                    action.enterNote(this._id)
                }
                binding.ivCheck.let {
                    if(this.checked) {
                        it.setImageResource(R.drawable.ic_checked)
                    } else {
                        it.setImageResource(R.drawable.ic_unchecked)
                    }
                    it.setOnClickListener {
                        action.check(this)
                    }
                }
            }
        }
    }

    override fun getItemCount() = noteList.size

    inner class ViewHolder(val binding: ItemNoteBinding): 
        RecyclerView.ViewHolder(binding.root)
}

Finalizando vamos então inserir o código em nossa NoteActivity e MainActivity.

package br.com.uware.room.presentation.activity

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import br.com.uware.room.R
import br.com.uware.room.databinding.ActivityNoteBinding
import br.com.uware.room.framework.model.Note
import br.com.uware.room.framework.viewmodel.NoteViewModel

class NoteActivity : AppCompatActivity() {

    private lateinit var binding: ActivityNoteBinding

    private val viewModel: NoteViewModel by viewModels()

    private var noteId = 0L
    private var currentNote = Note()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityNoteBinding.inflate(layoutInflater)
        setContentView(binding.root)

        noteId = intent.getLongExtra("_id", 0L)
        if(noteId != 0L){
            binding.tvNoteTitle.text = getString(R.string.edit_note)
            viewModel.get(noteId)
            viewModel.currentNote.observe(this, Observer {
                currentNote = it
                binding.etNote.setText(it.name)
                binding.btnDelete.apply {
                    visibility = View.VISIBLE
                    setOnClickListener {
                        viewModel.delete(currentNote)
                        finish()
                    }
                }
            })
        }

        binding.btnCancel.setOnClickListener {
            finish()
        }

        binding.btnSave.setOnClickListener {
            var note: Note
            note = if(noteId == 0L) {
                Note()
            } else {
                currentNote
            }
            note.apply {
                name = binding.etNote.text.toString()
            }
            viewModel.save(note)
            finish()
        }
    }
}
package br.com.uware.room.presentation.activity

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import br.com.uware.room.databinding.ActivityMainBinding
import br.com.uware.room.framework.model.Note
import br.com.uware.room.framework.viewmodel.MainViewModel
import br.com.uware.room.presentation.action.MainAction
import br.com.uware.room.presentation.adapter.NoteListAdapter

class MainActivity : AppCompatActivity(), MainAction {

    private lateinit var binding: ActivityMainBinding

    private val viewModel: MainViewModel by viewModels()

    private var notes = ArrayList<Note>()
    private var noteListAdapter: NoteListAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.fabAdd.setOnClickListener {
            val intent = Intent(this, NoteActivity::class.java)
            startActivity(intent)
        }

        viewModel.notes.observe(this, Observer {
            notes = it as ArrayList<Note>
            setRecyclerView()
        })
    }

    override fun onResume() {
        super.onResume()
        viewModel.getNotes()
    }

    override fun enterNote(id: Long) {
        val intent = Intent(this, NoteActivity::class.java)
        intent.putExtra("_id", id)
        startActivity(intent)
    }

    override fun check(note: Note) {
        viewModel.check(note)
    }

    private fun setRecyclerView(){
        noteListAdapter = NoteListAdapter(notes, this)
        binding.rvMain.apply {
            layoutManager = LinearLayoutManager(this@MainActivity)
            adapter = noteListAdapter
        }
    }
}

Portanto agora é só compilar e executar o projeto para ver como funciona.

Como usar ROOM(SQLite) com kotlin no Android

Porém se deseja conhecer mais sobre ROOM, acesse o link.

Enfim espero poder ter ajudado com mais este tutorial sobre como usar ROOM(SQLite) com kotlin no Android.

[ads]

chevron_left
chevron_right