Proyecto de Android con Kotlin usando el patrón MVVM donde implemento algunas librerías de Android Jetpack como Navigation, Databinding, Room y pruebas con JUnit; para la creación de una aplicación sencilla donde se pueden crear, editar, eliminar y filtrar notas.
- Kotlin
 - Android KTX version: '2.3.0'
 - Room version: '2.2.5'
 - Material version: '1.2.0'
 - JUnit version: '4.12'
 
Este proyecto esta basado en la arquitectura MVVM (Model View ViewModel), tambien uso el patrón Repository Pattern.
Para utilizar la librería en el proyecto tienen que agregar en el archivo build.gradle de tu modulo lo siguiente en dependencies:
apply plugin: 'kotlin-kapt'
dependencies {
  ...
        // Room
        implementation "androidx.room:room-runtime:2.2.5"
        kapt "androidx.room:room-compiler:2.2.5"
  ...
}Ahora procedemos a crear nuestro directorio model donde vamos a crear nuestro archivo Note una vez creado el archivo
agregamos los atributos que tendra nuestro modelo como son noteId, title, content, updatedAt
    @Entity(tableName = "note_table")
    data class Note(
        @PrimaryKey(autoGenerate = true)
        var noteId: Long,
    
        @ColumnInfo(name = "title")
        var title: String,
    
        @ColumnInfo(name = "content")
        var content: String,
    
        @ColumnInfo(name = "updatedAt")
        var updatedAt: Long,
    ) {
        constructor() : this(0L, "", "", -1L)
    }Veamos qué hacen estas anotaciones:
- 
@Entity(tableName = "note_table")El@Entityva a representar una tabla en SQLite. La cual para este caso le pasamos el nombre de la tabla si no queremos que se llame igual al modelo el cual nos quedatableName = "note_table"y nos indica que nuestra tabla se llamaranote_table. - 
@PrimaryKey(autoGenerate = true)El@PrimaryKeyindicamos que este valor sera nuestra clave primaria de la tabla y conautoGenerate = trueques este valor sera generado automáticamente. - 
@ColumnInfo(name = "content")El@ColumnInfoespecifica el nombre de la columna en la tabla y conname = "content"le damos un nombre a dicho elemento el cual puede ser diferente a nuestro atributo del modelo. 
Y por ultimo un constructor que nos creara un elemento para hacer pruebas mas adelante constructor() : this(0L, "", "", -1L) esta parte es opcional
DAO (data access object) especifica consultas SQL y las asocia con llamadas a métodos. El cual debe ser una interfaz o una clase abstracta.
Ahora procedemos a crear nuestro directorio database donde vamos a crear nuestro Dao el cual llamaremos NoteDatabaseDao una vez creado el archivo
agregamos nuestras consultas que SQL que sera insertar, actualizar, eliminar, listar por ID y listar todas.
    @Dao
    interface NoteDatabaseDao {    
        @Insert
        fun insert(note: Note)
    
        @Update
        fun update(note: Note)
    
        @Query("SELECT * FROM note_table ORDER BY noteId DESC")
        fun getAllNotes(): LiveData<List<Note>>
    
        @Query("SELECT * FROM note_table WHERE noteId = :key")
        fun get(key: Long): Note
    
        @Delete
        fun delete(note: Note)    
    }Veamos qué hace esta interface:
@DaoEl@Daoidentifica esto como una clase DAO para ROOM.@InsertEl@Insertes una anotación de método DAO especial la cual no necesita proporcionar ninguún SQL para realizar la operacion de insertar datos (También tenemos @Delete y @Update para eliminar y actualizar filas).@QueryEl@Queryrequiere que se le pase una consulta SQL como parametro lo cual se usa para consultas complejas y otras operaciones como en este caso@Query("SELECT * FROM note_table ORDER BY noteId DESC")le indicamos que queremos todos los datos y que los ordene de manera descendente y en este@Query("SELECT * FROM note_table WHERE noteId = :key")queremos que nos consulte una nota por id; para mas información consultar.
Ahora procedemos a crear en nuestro directorio database el archivo NoteDatabase  una vez creado el archivo
agregamos la información correspondiente.
    @Database(entities = [Note::class], version = 1, exportSchema = false)
    abstract class NoteDatabase : RoomDatabase() {
    
        abstract  val noteDatabaseDao: NoteDatabaseDao
    
        companion object {
            @Volatile
            private var INSTANCE: NoteDatabase? = null
    
            fun getInstance(context: Context) : NoteDatabase {
                synchronized(this) {
                    var instance = INSTANCE
                    if (instance == null) {
                        instance = Room.databaseBuilder(
                            context.applicationContext,
                            NoteDatabase::class.java,
                            "note_history_database"
                        )
                            .fallbackToDestructiveMigration()
                            .build()
    
                        INSTANCE = instance
                    }
                    return instance
                }
            }
        }
    }Veamos qué temenos:
- Nuestra clase debe ser 
abstracty extender deRoomDatabase @DatabaseAl@Databasele agregamos las entidades que tendrá que en este caso es solo unaentities = [Note::class]el cual recibe un array de las mismas, también le indicamos la versión que para este sera 1version = 1y como no vamos a manejar migraciones dejamosexportSchemaenfalse.- Implementamos singleton para evitar que se abran varias instancias de la base de datos al mismo tiempo el cual nos garantiza que siempre
tenemos la misma instancia de la Database la cual la obtendremos de 
getInstance. 
Ya para finalizar y comprobar que todo este funcionando creamos nuestro test procedemos a crear nuestro archivo NoteDatabaseTest
en el directorio de nuestra app androidTest
    @RunWith(AndroidJUnit4::class)
    class NoteDatabaseTest {
        private lateinit var noteDao: NoteDatabaseDao
        private lateinit var db: NoteDatabase
    
        @Before
        fun createDb() {
            val context = InstrumentationRegistry.getInstrumentation().targetContext
            db = Room.inMemoryDatabaseBuilder(context, NoteDatabase::class.java)
                    .allowMainThreadQueries()
                    .build()
    
            noteDao = db.noteDatabaseDao
        }
    
        @After
        @Throws(IOException::class)
        fun closeDb() {
            db.close()
        }
    
        @Test
        @Throws(Exception::class)
        fun insetAndGetNote() {
            val note = Note()
            noteDao.insert(note)
            val toNote = noteDao.get(note.noteId)
            assertEquals(toNote?.updatedAt, -1L)
        }
    
    }Veamos qué temenos:
@RunWithEl@RunWithnos indica que la prueba va a ser ejecutada conJUnit 4@BeforeEn el@Beforecreamos un método para inicializar la base de datos@AfterEn el@Aftercreamos un método para limpiar la base de datos usandodb.close()@TestEn el@Testcreamos un método de prueba donde vamos a insertar una nota y consultarla.
Si recordamos en nuestro constructor del modelo Note tenemos constructor() : this(0L, "", "", -1L) por eso no le pasamos valores
cuando creamos la nota val note = Note() luego procedemos a insertarla en la base de datos con noteDao.insert(note) y luego con el
noteId la consultamos val toNote = noteDao.get(note.noteId) y con el método de junit assertEquals comprobamos que la nota consultada
tenga el mismo valor en el updatedAt con el cual se creo assertEquals(toNote?.updatedAt, -1L) y corremos el test.
Ahora procedemos a cambiar un valor en de -1L a -6l en assertEquals(toNote?.updatedAt, -6L) y corremos el test el cual no debe pasar
Ya con esa comprobación  procedemos a implementar el patrón Repository Pattern y la creación de los ViewModel para alimentar nuestras vistas.

