En el capítulo pasado, empezamos a conocer la persistencia de datos y su utilidad, el problema es que como ya comenté las shared preferences están destinadas a persistir pequeños datos de preferencia del usuario, por lo que si necesitamos guardar más información necesitamos una base de datos. Aunque hay disponible muchísimas opciones muy completas, en este curso trabajaremos siempre con Room, puesto que es la alternativa que nos propone google.
¿Qué es Room?
Room es un ORM (Object-
La imagen anterior nos muestra el funcionamiento de dicha herramienta que, aunque parezca complicado al principio, es muy fácil de entender cuando nos pongamos a ello.
La idea es clara, tendremos una base de datos que le devolverá a nuestra app los Data Access Objects (DAO) estos son los encargados de persistir la información en la base de datos y de devolvernos las entities, que serán las encargadas de devolvernos la información que hemos ido almacenando.
Una vez entendamos un poco como funciona es el momento de ponerse manos a la obra. Lo primero que haremos será crear nuestro primer proyecto como siempre.
MisNotas
Así se llamará nuestra app y consistirá en una lista de tareas las cuales podemos marcar como hechas o no y tendremos la posibilidad de borrarlas. Al final del artículo lo tendréis disponible para descargar a través de GitHub (ya haré un artículo sobre git/gitflow y demás).
Lo primero que haremos será añadir todas las dependencias necesarias. Como ya he comentado anteriormente las dependencias son pequeñas llamadas que hace nuestro fichero Gradle para implementar funciones que por defecto nuestro proyecto no tiene. Por ejemplo, ahora necesitamos usar Room y aunque sea oficial de Google no viene por defecto en nuestro proyecto y es muy fácil de entender. Si vinieran todas las dependencias ya implementadas al abrir un proyecto, nuestra app pesaría muchísimo.
Así que vamos a ir al gradle del módulo app (por defecto tendremos un gradle por módulo y por aplicación, de eso ya hablaré en otros artículos). Para localizarlo, en tan sencillo como ir a Gradle Scripts si tenemos la vista «android».
Y meteremos todas las dependencias que necesitamos. En este caso vamos a meter cuatro más, ya luego a medida que vayamos a usarlas las iré explicando.
implementation 'com.android.support:design:26.1.0' implementation 'com.android.support:recyclerview-v7:26.1.0' implementation 'org.jetbrains.anko:anko-common:0.9' implementation "android.arch.persistence.room:runtime:1.0.0-rc1" kapt "android.arch.persistence.room:compiler:1.0.0-rc1"
Las meteremos donde están las demás, dentro de dependencies {}. Sincronizamos el gradle.
Creando nuestra base de datos
Vamos a crear un directorio nuevo llamado database dentro de nuestro proyecto y ahí crearemos todo lo necesario siguiente el esquema que vimos antes. Necesitaremos crear 3 clases, empezaremos creando nuestra entidad que será el modelo a persistir en la base de datos.
TaskEntity
La aplicación va a ser una lista de tareas, así que el modelo se llamará TaskEntity y contendrá 3 valores, una ID para localizar el objeto, un nombre (el de la tarea a realizar) y un Booleano que será el que usemos para saber si la tarea está hecha o no. Así que creamos nuestra clase y la dejamos así.
@Entity(tableName = "task_entity") data class TaskEntity ( @PrimaryKey(autoGenerate = true) var id:Int = 0, var name:String = "", var isDone:Boolean = false )
Aunque es bastante pequeñita quiero recalcar algunas cosillas:
- La anotación @Entity la utilizamos para añadirle un nombre a nuestra entidad como tabla de la base de datos. Cada base de datos puede contener una o varias tablas y cada una persiste un modelo diferente.
- La anotación @PrimaryKey (autoGenerate = true) está diciendo que la variable id es un valor que se autogenera al crear un objeto de esta clase y que no podrá repetirse. Es un valor único con el cual podremos localizar un objeto concreto.
TaskDao
TaskDao será la interfaz que contendrá las consultas a la base de datos. Aquí distinguiremos cuatro tipos de consultas.
- @Query: Se hacen consultas directamente a la base de datos, usaremos SQL para hacerlas. En este ejemplo haremos dos muy sencillitas, pero se pueden hacer cosas impresionantes.
- @Insert: Se usará para insertar entidades a la base de datos, a diferencia de las @Query no hay que hacer ningún tipo de consulta, sino pasar el objeto a insertar.
- @Update: Actualizan una entidad ya insertada. Solo tendremos que pasar ese objeto modificado y ya se encarga de actualizarlo. ¿Cómo sabe que objeto hay que modificar? Pues por nuestro id (recordad que es la PrimaryKey).
- @Delete: Como su propio nombre indica borra de la tabla un objeto que le pasemos.
@Dao interface TaskDao { @Query("SELECT * FROM task_entity") fun getAllTasks(): MutableList<TaskEntity> }
Por ahora solo meteremos una query, que lo que hará será seleccionar todas las TaskEntity que tengamos en la base de datos. Fijaros que es una interfaz en lugar de una clase, y que contiene la anotación @Dao.
TaskDatabase
Una vez tengamos nuestro Dao y nuestra entidad, vamos a crear la base de datos que los contendrá, en este caso se llamará TaskDatabase.
@Database(entities = arrayOf(TaskEntity::class), version = 1) abstract class TasksDatabase : RoomDatabase() { abstract fun taskDao(): TaskDao }
Lo primero que debemos fijarnos es en la anotación @Database, que especifica que la entidad será una lista de TaskEntity (entidad que ya hemos creado) y que la versión es 1. Las versiones se usan para la posible migración de datos al actualizar la App. Imaginemos que sacamos una segunda versión de la app y en vez de 3 parámetros almacenamos 4, no podemos cambiar nuestra entidad de golpe pues habría problemas. Para eso se usa la versión, junto a un fichero de migración que le dice al programa que deberá hacer para pasar de la versión 1 a la 2, 3 o la que sea.
También debemos fijarnos en que nuestra clase extienda de RoomDatabase() que es una clase que tenemos gracias a importar la dependencia de Room en nuestro gradle. Para finalizar tiene una sola función que hace referencia al Dao que hemos creado anteriormente, si tuviésemos más Dao’s pues habría que implementarlos ahí también.
Con esto ya tenemos nuestra base de datos preparada, ahora debemos instanciarla al inicio de la aplicación. Como hicimos en el capítulo anterior con las Shared Preferences crearemos una clase application para poder acceder a nuestra información en cualquier clase.
MisNotasApp
class MisNotasApp: Application() { companion object { lateinit var database: TasksDatabase } override fun onCreate() { super.onCreate() MisNotasApp.database = Room.databaseBuilder(this, TasksDatabase::class.java, "tasks-db").build() } }
Como ya he comentado, esta clase es básicamente igual a la del capítulo anterior por lo que no hay nada que explicar salvo que la instancia de database necesitará tres parámetros, el contexto (this), la clase de nuestra base de datos (TasksDatabase) y el nombre que le pondremos, en este caso la he llamado «trasks-db».
Recordad que para que esta clase se lance al abrir la app debemos ir al AndroidManifest.xml y añadir android:name=».MisNotasApp» dentro de la etiqueta <Application>
activity_main
Aunque tengamos varias clases por detrás, nuestra app solo tendrá un layout, una sola vista. La idea era crear algo sencillo y usable, por lo que me decanté por una barra superior donde añadir las tareas y luego un RecyclerView donde se muestren todas. El XML es muy sencillito, solo he usado algún atributo nuevo que comentaré.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/background_light" tools:context="com.cursokotlin.misnotas.UI.MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/rvTask" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/rlAddTask"/> <RelativeLayout android:id="@+id/rlAddTask" android:layout_width="match_parent" android:layout_height="wrap_content" android:elevation="10dp" android:layout_margin="10dp" android:background="@android:color/white"> <EditText android:id="@+id/etTask" android:layout_width="wrap_content" android:layout_height="wrap_content" android:hint="añade una tarea" android:layout_alignParentLeft="true" android:layout_toLeftOf="@+id/btnAddTask" /> <Button android:id="@+id/btnAddTask" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:text="Añadir"/> </RelativeLayout> </RelativeLayout>
Debajo del RecyclerView he metido la barra superior. La he puesto debajo porque en los XML de android, si imaginamos que va todo por capas, la parte inferior es la capa más visible. Por ejemplo si pusiéramos dos fotos de tamaño completo a la pantalla, se vería la que esté en la parte más abajo de nuestro archivo.
A parte de eso conocemos todos los atributos de la vista excepto android:elevation que lo que hace es dar una elevación a nuestro componente añadiéndole una sombra por debajo. El efecto es el siguiente.
MainActivity
Ya tenemos nuestra vista preparada, es el momento de empezar a generar la lógica. Empezamos creando las variables necesarias.
lateinit var recyclerView: RecyclerView lateinit var tasks: MutableList<TaskEntity>
Ahora nos vamos al OnCreate de nuestra actividad, lo primero que haremos será instanciar tasks como una arrayList y acto seguido llamaremos a la función getTasks() que vamos a crear. Esta función será la encargada de acceder a nuestro DAO para hacer la primera consulta, recuperar todas las entidades que el usuario tenga guardadas. Así que por ahora dejamos el OnCreate así.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) tasks = ArrayList() getTasks() }
Ahora antes de seguir tengo que hacer un poco de hincapié en los hilos. Los hilos son los que permiten la multitarea de un dispositivo, por ejemplo en un hilo puedes ir guardando información asíncronamente (por detrás) mientras el usuario sigue haciendo cosas. En Android, el hilo principal es el encargado de la parte visual de la aplicación, por lo que no nos deja acceder a la base de datos desde ahí, por ello crearemos un hilo secundario que de modo asíncrono hará la petición a la base de datos y recuperará la información que necesitemos.
Como este capítulo no va de hilos, no solo lo he resumido mucho sino que lo voy a hacer de la manera más sencilla (en un futuro haré un artículo dedicado a ello). Para este fin vamos a usar Anko, una librería muy completa para Kotlin que nos hará más sencillas muchas tareas del día a día. Anko ya lo implementamos al principio del capítulo en las dependencias.
fun getTasks() { doAsync { tasks = MisNotasApp.database.taskDao().getAllTasks() uiThread { setUpRecyclerView(tasks) } } }
Gracias a Anko la función queda muy sencilla. Todo lo que tengamos que hacer en el segundo hilo asíncrono, lo meteremos dentro de doAsync{} y una vez haya acabado, usando uiThead{} podemos decir que haga algo en el hilo principal, el de la vista. Nosotros hemos asignado a tasks los valores que recuperamos de nuestra base de datos, y cuando los completa, llamamos al método setUpRecyclerView(tasks) que será el que configure nuestro RecyclerView.
Pero antes de mostraros el método anterior vamos a crear nuestro adapter, que a diferencia del primero que vimos hace unos capítulos, este tendrá diferentes eventos para capturar los clicks del usuario.
TasksAdapter
A diferencia de nuestro último adapter que tenía un constructor para pasar los parámetros, en este caso usaremos la propia clase para hacerlo. Se puede hacer de ambas formas pero así vais viendo formas diferentes que más se acomoden a vuestro modo de trabajo.
Antes de empezar a trabajar con el layout de la celda. Vamos a crear algo muy sencillito así que creamos un nuevo layout llamado item_task.xml.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" xmlns:tools="http://schemas.android.com/tools" android:orientation="horizontal" android:layout_margin="10dp"> <CheckBox android:id="@+id/cbIsDone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="10dp" android:layout_marginEnd="10dp" /> <TextView android:id="@+id/tvTask" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" android:textSize="18sp" tools:text="Test"/> </LinearLayout>
Nos quedará una celda muy sencilla. La idea es que cuando marquemos el Checkbox se actualice en la base de datos el objeto y si hacemos click en cualquier otra parte de la vista se borre de base de datos.
Así que esta vez le pasaremos 3 parámetros, la lista de tareas que tenemos almacenadas en nuestra base de datos y funciones. Estas funciones nos permitirán recuperar el evento del click en cada una de las celdas, ya sea la vista completa o un componente concreto.
class TasksAdapter( val tasks: List<TaskEntity>, val checkTask: (TaskEntity) -> Unit, val deleteTask: (TaskEntity) -> Unit) : RecyclerView.Adapter<TasksAdapter.ViewHolder>() {
Ahora en el onBindViewHolder haríamos lo mismo de siempre pero añadiéndole el setOnClickListener a cada uno de los componentes que nos interese, en este caso yo se lo he puesto al checkbox y como quiero controlar el click en cualquier otra parte de la vista podemos acceder a itemView que nos devuelve la celda completa.
override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = tasks[position] holder.bind(item, checkTask, deleteTask) }
Y ya completamos TaskAdapter con los dos métodos que nos faltan onCreateViewHolder y getItemCount. Estos métodos los dejaremos por defecto así que no voy a explicar nada ya que lo he hecho en capítulos anteriores.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) return ViewHolder(layoutInflater.inflate(R.layout.item_task, parent, false)) } override fun getItemCount(): Int { return tasks.size }
Para finalizar la clase ViewHolder que solo tendrá de novedad en la función bind, a través de .isChecked podemos iniciar la vista con el checkbox marcado o no, así que comprobaremos si está a true nuestra entidad y si es así pues lo marcamos.
Una vez configurada la celda, le añadimos .setOnClickListener a nuestro checkBox y al itemView que es el componente completo pasando el propio objeto.
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val tvTask = view.findViewById<TextView>(R.id.tvTask) val cbIsDone = view.findViewById<CheckBox>(R.id.cbIsDone) fun bind(task: TaskEntity, checkTask: (TaskEntity) -> Unit, deleteTask: (TaskEntity) -> Unit) { tvTask.text = task.name cbIsDone.isChecked = task.isDone cbIsDone.setOnClickListener{checkTask(task)} itemView.setOnClickListener { deleteTask(task) } } }
Puedes ver la clase completa haciendo click aquí.
Volvemos al MainActivity
Con nuestro adapter completo es el paso de configurarlo desde MainActivity con nuestra función setUpRecyclerView() a la cual le pasaremos la lista de tareas que hemos recuperado.
fun setUpRecyclerView(tasks: List<TaskEntity>) { adapter = TasksAdapter(tasks, { updateTask(it) }, {deleteTask(it)}) recyclerView = findViewById(R.id.rvTask) recyclerView.setHasFixedSize(true) recyclerView.layoutManager = LinearLayoutManager(this) recyclerView.adapter = adapter }
Debemos fijarnos que al instanciar el adapter le pasamos tres parámetros, la lista de tareas, updateTask(it) y deleteTask(it). Estos no son parámetros sino métodos que tendremos en el MainActivity, que serán llamados automáticamente cuando se ejecute el evento del click que configuramos en el adapter.
Antes de mostraron esos dos métodos, vamos a configurar el botón de añadir tareas, que lo que hará será crear un objeto Task, almacenarlo en base de datos y luego añadirlo a la lista que tiene el adapter. Lo primero que haremos será ir a nuestro DAO y añadir una nueva función de insertar.
@Insert fun addTask(taskEntity : TaskEntity):Long
Simplemente recibirá un objeto TaskEntity y lo añadirá a la base de datos. Fijaros que devuelve un Long, eso es porque nos dará automáticamente la ID del item añadido.
Nos vamos a nuestro onCreate del MainAcitivity y añadimos lo siguiente.
btnAddTask.setOnClickListener { addTask(TaskEntity(name = etTask.text.toString()))}
Simplemente le hemos asignado al evento del click un método llamado addTask() al cual le pasamos un objeto nuevo con el texto de la celda. Dicha función añadirá a base de datos la tarea, luego recuperemos dicha tarea y la añadiremos a la lista del adapter. Hay varias formas de hacer esto mejor, pero he ido poniendo varias formas en cada una de las peticiones a la base de datos para poder observarlas.
fun addTask(task:TaskEntity){ doAsync { val id = MisNotasApp.database.taskDao().addTask(task) val recoveryTask = MisNotasApp.database.taskDao().getTaskById(id) uiThread { tasks.add(recoveryTask) adapter.notifyItemInserted(tasks.size) clearFocus() hideKeyboard() } } }
Así que lo que estamos haciendo es añadir la tarea y luego recuperamos dicho objeto a través de getTaskById pasándole la ID que nos devuelve addTask. Obviamente debemos añadir a nuestro DAO la función de recuperar el item.
@Query("SELECT * FROM task_entity where id like :arg0") fun getTaskById(id: Long): TaskEntity
Cuando hemos acabado de realizar esto, en el hilo principal añadimos el objeto recuperado a la lista, le decimos al adapter que hemos añadido un objeto nuevo a través de adapter.notifyItemInserted (hay que pasarle pa posición, como es el último objeto añadido, podemos saber cual es recuperando el tamaño de la lista) y luego los métodos clearFocus() y hideKeyboard() simplemente nos quitarán el texto del editText y bajarán el teclado.
fun clearFocus(){ etTask.setText("") } fun Context.hideKeyboard() { val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager inputMethodManager.hideSoftInputFromWindow(currentFocus.windowToken, 0) }
Con esto ya podemos insertar tareas en nuestra app.
Una vez tengamos tareas, pasamos a actualizarlas con updateTask() que será el encargado de cambiar en la base de datos el booleano que marca si la tarea está hecha o no, así que nos volvemos a nuestro DAO y añadimos un update.
@Update fun updateTask(taskEntity: TaskEntity):Int
Y su respectivo método en la actividad principal es muy sencillo, simplemente recibe el objeto, nosotros cambiamos el booleano por su opuesto (si es true, lo ponemos a false) y se lo mandamos al DAO.
fun updateTask(task: TaskEntity) { doAsync { task.isDone = !task.isDone MisNotasApp.database.taskDao().updateTask(task) } }
Recordamos que con la exclamación delante de un booleano nos da el valor contrario.
Para finalizar nuestra app nos falta la opción de borrar tareas tocando cualquier parte de la celda menos el checkBox que ya hace una función de actualizar. Así que volvemos al DAO y hacemos una función @Delete.
@Delete fun deleteTask(taskEntity: TaskEntity):Int
En el método deleteTask() debemos hacer algo más, para empezar, buscaremos en nuestra lista tasks la posición del item que vamos a borrar para tener una referencia. Para ello usaremos la función de las listas indexOf(item) que nos devolverá dicha posición y la almacenamos en una variable, luego borraremos el objeto de la base de datos y de nuestra lista y acabaremos en el hilo principal avisando al adapter que hemos removido un objeto, pasándole la posición.
fun deleteTask(task: TaskEntity){ doAsync { val position = tasks.indexOf(task) MisNotasApp.database.taskDao().deleteTask(task) tasks.remove(task) uiThread { adapter.notifyItemRemoved(position) } } }
Puedes ver la clase completa haciendo click aquí.
Este artículo ha sido un poquito más «intenso» pero ya empezamos a entrar en verdadera materia. Os recuerdo que podéis descargar el proyecto completo desde mi GitHub. Y que si tenéis alguna duda podéis dejarla en los comentarios.
Continúa con el curso: Capítulo 18 – Componentes personalizados [Primera parte]
Que vaina bacana bro ! Quiero hacer una WebView desde una activity, ¿Que me recomiendas?. Quiero hacer algo así como de noticias pero quiero desarrollar las noticias con una app web y la app móvil que muestre la web pero de cierta manera.
Vuenas @Iván lo correcto sería crear un servicio GET y desde la app haces una petición y recuperas las noticias en un JSON o en XML. Si todavía se te escapan un poco los conocimientos, puedes empezar a usar un webview que apunte a una url, pero no sería ni lo más eficiente ni lo mejor visualmente.
Saludos
Buenas!! Me están sirviendo de mucho tus artículos para iniciarme en Kotlin y refrescar Android, que lo tenía algo oxidado.
Me ha surgido un par de dudas acerca de Room:
1. ¿Cómo se declararían en la clase abstracta TasDatabase si se diera el caso de tener varias tablas?
2. En el mismo caso de tener varias tablas, ¿Se tendría que crear una interfaz DAO para cada una?
Muchas gracias y sigue así! 😉
Buenas, respondiendo a la primera pregunta, sería usando la coma, por ejemplo.
@Database(entities = {Task.class, Category.class}, version = 2)
En caso de tener varias tablas, lo correcto sería usar varias DAO, separándolas por tablas o casos de uso.
Un saludo
Una pregunta, ¿Sabes de qué modo se pueden relacionar dos tablas de muchos a mucho en Room?
Hola, estuve siguiendo el curso de kotlin y pero siempre me da un error apenas inicio un proyecto, lo que hice para solucionarlo es agregar una linea en gradle.properties, la linea es:
android.enableAapt2=false
Alguno de los que realizo el ejemplo les dió este error? es para saber si hay otra forma de solucionarlo sin tener que desactivar nada
Puedes comentar que error te salía?
El error que me salìa es el siguiente:
Error:java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
y abajo este otro:
Error:Execution failed for task ‘:app:mergeDebugResources’.
> Error: java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
En TaskDao me daba error en :arg0 y despues de investigar la solución que tuve fue esta:
@Query(«SELECT * FROM task_entity where id like :id»)
fun getTaskById(id: Long): TaskEntity reemplace: arg0 por: id y me soluciono el error. Saludos y gracias por los tutoriales!!
El error que me salìa es el siguiente:
Error:java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
y abajo este otro:
Error:Execution failed for task ‘:app:mergeDebugResources’.
> Error: java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
Me interesa saber como utilizar el comodìn para hacer las consultas, por ejemplo una consulta de este tipo:
SELECT * FROM task_entity WHERE name LIKE ‘%limpiar%’
Perdón por mi ignorancia, pero sería algo así como concatenar ‘%’ antes y al final del argumento antes de llamar a la funcion? o se puede incluir el caracter ‘%’ dentro de la consulta. algo así como
SELECT * FROM task_entity WHERE name LIKE %:arg0%
Buenas, sinceramente tendría que investigarlo, esto ya es SQL, te recomiendo echarle un ojo a este pequeño curso http://www.maestrosdelweb.com/tutsql1/
Saludos
Algun ejemplo de retrofit usando Kotllin
Ese es el siguiente capítulo que estoy preparando 🙂
Si tendrás un ejemplo con retrofit, saludos
Esto se podría utilizar para por ejemplo un login de acceso accediendo desde una db alojada en mysql??
Hola por aqui… Tengo un problema:
implementation ‘com.android.support:design:29.1.0’
implementation ‘com.android.support:recyclerview-v7:29.1.0’
implementation ‘org.jetbrains.anko:anko-common:0.9’
implementation «android.arch.persistence.room:runtime:1.0.0-rc1»
kapt «android.arch.persistence.room:compiler:1.0.0-rc1»
Cambie por la version 29, ya que me lo pedia, pero ahora sale un error con las ultimas dos lineas:
Caused by: org.gradle.internal.metaobject.AbstractDynamicObject$CustomMessageMissingMethodException: Could not find method kapt() for arguments [android.arch.persistence.room:compiler:1.0.0-rc1] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
Esta serian todas mis Dependencies:
dependencies {
implementation fileTree(dir: ‘libs’, include: [‘*.jar’])
implementation «org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version»
implementation ‘androidx.appcompat:appcompat:1.1.0’
implementation ‘androidx.core:core-ktx:1.1.0’
implementation ‘androidx.constraintlayout:constraintlayout:1.1.3’
implementation ‘com.android.support:design:29.1.0’
implementation ‘com.android.support:recyclerview-v7:29.1.0’
implementation ‘org.jetbrains.anko:anko-common:0.9’
implementation «android.arch.persistence.room:runtime:1.0.0-rc1»
kapt «android.arch.persistence.room:compiler:1.0.0-rc1»
testImplementation ‘junit:junit:4.12’
androidTestImplementation ‘androidx.test.ext:junit:1.1.1’
androidTestImplementation ‘androidx.test.espresso:espresso-core:3.2.0’
}
Creo que lo resolvi, hay que agregar: apply plugin: ‘kotlin-kapt’
holaaa…. De nuevo yo, tengo un problema
error: Each bind variable in the query must have a matching method parameter. Cannot find method parameters for :arg0.
public abstract com.example.misnotas.TaskEntity getTaskById(long id);
Buenas, puedes mostrar la función getTaskById?
Hola! disculpa y mil gracias, si claro:
@Query(«SELECT * FROM task_entity where id like :arg0»)
fun getTaskById(id: Long): TaskEntity
Me parece en rojo arg0
Prueba a cambiar arg0 por id (el mismo nombre que el parámetro que recibe la función) pero con : delante. Es decir:
@Query(«SELECT * FROM task_entity where id like :id»)
fun getTaskById(id: Long): TaskEntity
Gracias! Asi funciono, corrio sin problema, pero lamentablemente en el emulador no corre https://ibb.co/KNcyP7m
Gracias por el Tutorial. Lo he seguido y ha sido de mucha ayuda, pero tengo problemas al intentar ejecutar este programa.
He creado un nuevo proyecto copiando y pegando el código, pero no se ejecuta en el dispositivo (sale el mensaje de error «la aplicación se ha detenido» o «la aplicación continúa deteniéndose»)
También intenté seguir el tutorial según la aplicación que estoy desarrollando, haciendo los ajustes necesarios. El programa funciona correctamente pero al agregar algo nuevo a la base de datos no se muestra en la pantalla.
Haciendo los cambios noté que me faltaba agregar la siguiente dependencia: «kapt ‘androidx.room:room-compiler:2.2.4′». Al agregarla e intentar compilar la aplicación me genera el siguiente error: «e: C:\Users\XXX\AndroidStudioProjects\MetroCard\app\build\tmp\kapt3\stubs\debug\com\example\metrocard\database\CardDAO.java:14: error: Not sure how to handle insert method’s return type.
public abstract int addCard(@org.jetbrains.annotations.NotNull()», cosa que no sucede si no se agrega esa dependencia.
Alguna idea de cómo solucionar alguno de estos errores ???
[…] a lo largo del curso. Todas ellas son un conjunto de componentes (por ejemplo el RecyclerView o la base de datos Room). Para añadirlas siempre hemos tenido que ir al fichero Gradle y añadir una dependencia, que […]
Agradeciéndole el tutotial, de gran importancia. Cambie los nombres de algunas funciones para personalizar el programa, pero me da el siguiente error:
Caused by: java.lang.RuntimeException: cannot find implementation for giova.varios.miagenda.TareaBaseDatos. TareaBaseDatos_Impl does not exist
en la clase aplicacion la clase no se inicializa en el onCreate, queda desActivada
Que puedo hacer????
Buenas, un nombre de una clase lo has cambiado y en algún sitio estás llamando a la clase TareaBaseDatos_Impl.
Busca en el código donde se llama a esa clase que no existe.
Como se podria insertar una imagen con room
Hola, antes que nada muchas gracias por el curso. Aunque le seré sincero… no logro entender muy como funciona esto pero estoy tratando de hacerlo. La verdad es que tengo un problema al abrir la aplicación ya que esta se cierra de forma inesperada apenas abrirlo. En LOGCAT aparece un error al momento de que la aplicación se cierra, creo que la más importante es esta: Caused by: java.lang.RuntimeException: cannot find implementation for database.TaskDatabase. TaskDatabase_Impl does not exist. Podría ayudarme a solucionar esto? Ya he revisado el ActivityMain y en ninguna parte del código esta escrito . He leído comentarios anteriores para ver si el problema lo había tenido otro usuario pero el único similar que encontré tiene los nombres personalizados a los de este tutorial. Nuevamente muchas gracias.