Android room persistence library

Урок 5. Room. Основы

Библиотека Room предоставляет нам удобную обертку для работы с базой данных SQLite. В этом уроке рассмотрим основы. Как подключить к проекту. Как получать, вставлять, обновлять и удалять данные.

Полный список уроков курса:

Подключение к проекту

В build.gradle файл проекта добавьте репозитарий google()

В build.gradle файле модуля добавьте dependencies:

Если у вас студия ниже 3.0 и старые версии Gradle и Android Plugin, то подключение будет выглядеть так:

Room имеет три основных компонента: Entity, Dao и Database. Рассмотрим их на небольшом примере, в котором будем создавать базу данных для хранения данных по сотрудникам (англ. — employee).

При работе с Room нам необходимо будет писать SQL запросы. Если вы не знакомы с ними, то имеет смысл прочесть хотя бы основы.

Entity

Аннотацией Entity нам необходимо пометить объект, который мы хотим хранить в базе данных. Для этого создаем класс Employee, который будет представлять собой данные сотрудника: id, имя, зарплата:

Класс помечается аннотацией Entity. Объекты класса Employee будут использоваться при работе с базой данных. Например, мы будем получать их от базы при запросах данных и отправлять их в базу при вставке данных.

Этот же класс Employee будет использован для создания таблицы в базе. В качестве имени таблицы будет использовано имя класса. А поля таблицы будут созданы в соответствии с полями класса.

Аннотацией PrimaryKey мы помечаем поле, которое будет ключом в таблице.

В следующих уроках мы рассмотрим возможности Entity более подробно.

В объекте Dao мы будем описывать методы для работы с базой данных. Нам нужны будут методы для получения списка сотрудников и для добавления/изменения/удаления сотрудников.

Описываем их в интерфейсе с аннотацией Dao.

Методы getAll и getById позволяют получить полный список сотрудников или конкретного сотрудника по id. В аннотации Query нам необходимо прописать соответствующие SQL-запросы, которые будут использованы для получения данных.

Обратите внимание, что в качестве имени таблицы мы используем employee. Напомню, что имя таблицы равно имени Entity класса, т.е. Employee, но в SQLite не важен регистр в именах таблиц, поэтому можем писать employee.

Для вставки/обновления/удаления используются методы insert/update/delete с соответствующими аннотациями. Тут никакие запросы указывать не нужно. Названия методов могут быть любыми. Главное — аннотации.

В следующих уроках мы рассмотрим возможности Dao и его аннотаций более подробно.

Database

Аннотацией Database помечаем основной класс по работе с базой данных. Этот класс должен быть абстрактным и наследовать RoomDatabase.

В параметрах аннотации Database указываем, какие Entity будут использоваться, и версию базы. Для каждого Entity класса из списка entities будет создана таблица.

В Database классе необходимо описать абстрактные методы для получения Dao объектов, которые вам понадобятся.

Практика

Все необходимые для работы объекты созданы. Давайте посмотрим, как использовать их для работы с базой данных.

Database объект — это стартовая точка. Его создание выглядит так:

Используем Application Context, а также указываем AppDatabase класс и имя файла для базы.

Учитывайте, что при вызове этого кода Room каждый раз будет создавать новый экземпляр AppDatabase. Эти экземпляры очень тяжелые и рекомендуется использовать один экземпляр для всех ваших операций. Поэтому вам необходимо позаботиться о синглтоне для этого объекта. Это можно сделать с помощью Dagger, например.

Если вы не используете Dagger (или другой DI механизм), то можно использовать Application класс для создания и хранения AppDatabase:

Не забудьте добавить App класс в манифест

В коде получение базы будет выглядеть так:

Из Database объекта получаем Dao.

Теперь мы можем работать с Employee объектами. Но эти операции должны выполняться не в UI потоке. Иначе мы получим Exception.

Добавление нового сотрудника в базу будет выглядеть так:

Метод getAll вернет нам всех сотрудников в List

Получение сотрудника по id:

Обновление данных по сотруднику.

Room будет искать в таблице запись по ключевому полю, т.е. по id. Если в объекте employee не заполнено поле id, то по умолчанию в нашем примере оно будет равно нулю и Room просто не найдет такого сотрудника (если, конечно, у вас нет записи с >

Аналогично обновлению, Room будет искать запись по ключевому полю, т.е. по id

Давайте для примера добавим еще один тип объекта — Car.

Описываем Entity объект

Теперь Dao для работы с Car объектом

Будем считать, что нам надо только читать все записи, добавлять новые и удалять старые.

В Database необходимо добавить Car в список entities и новый метод для получения CarDao

Т.к. мы добавили новую таблицу, изменилась структура базы данных. И нам необходимо поднять версию базы данных до 2. Но об этом мы подробно поговорим в Уроке 12. А пока можно оставить версию равной 1, удалить старую версию приложения и поставить новую.

UI поток

Повторюсь, операции по работе с базой данных — синхронные, и должны выполняться не в UI потоке.

Читайте также:  Car stream для андроид авто как установить

В случае с Query операциями мы можем сделать их асинхронными используя LiveData или RxJava. Об этом еще поговорим в следующих уроках.

В случае insert/update/delete вы можете обернуть эти методы в асинхронный RxJava. В моем блоге есть статья на эту тему.

Также, вы можете использовать allowMainThreadQueries в билдере создания AppDatabase

В этом случае вы не будете получать Exception при работе в UI потоке. Но вы должны понимать, что это плохая практика, и может добавить ощутимых тормозов вашему приложению.

Переход на Room

Если вы надумали с SQLite мигрировать на Room, то вот пара полезных ссылок по этой теме:

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Android Architecture Components: the Room Persistence Library

In this final article of the Android Architecture Components series, we’ll explore the Room persistence library, an excellent new resource that makes it a lot easier to work with databases in Android. It provides an abstraction layer over SQLite, compile-time checked SQL queries, and also asynchronous and observable queries. Room takes database operations on Android to another level.

Since this is the fourth part of the series, I’ll assume that you’re familiar with the concepts and components of the Architecture package, such as LiveData and LiveModel. However, if you didn’t read any of the last three articles, you’ll still be able to follow. Still, if you don’t know much about those components, take some time to read the series—you may enjoy it.

1. The Room Component

As mentioned, Room isn’t a new database system. It is an abstract layer that wraps the standard SQLite database adopted by Android. However, Room adds so many features to SQLite that it is almost impossible to recognize. Room simplifies all the database-related operations and also makes them much more powerful since it allows the possibility of returning observables and compile-time checked SQL queries.

Room is composed of three main components: the Database, the DAO (Data Access Objects), and the Entity. Each component has its responsibility, and all of them need to be implemented for the system to work. Fortunately, such implementation is quite simple. Thanks to the provided annotations and abstract classes, the boilerplate to implement Room is kept to a minimum.

  • Entity is the class that is being saved in the Database. An exclusive database table is created for each class annotated with @Entity .
  • The DAO is the interface annotated with @Dao that mediates the access to objects in the database and its tables. There are four specific annotations for the basic DAO operations: @Insert , @Update , @Delete , and @Query .
  • The Database component is an abstract class annotated with @Database , which extends RoomDatabase . The class defines the list of Entities and its DAOs.

2. Setting Up the Environment

To use Room, add the following dependencies to the app module in Gradle:

If you’re using Kotlin, you need to apply the kapt plugin and add another dependency.

3. Entity, the Database Table

An Entity represents the object that is being saved in the database. Each Entity class creates a new database table, with each field representing a column. Annotations are used to configure entities, and their creation process is really simple. Notice how simple it is to set up an Entity using Kotlin data classes.

Once a class is annotated with @Entity , the Room library will automatically create a table using the class fields as columns. If you need to ignore a field, just annotate it with @Ignore . Every Entity also must define a @PrimaryKey .

Table and Columns

Room will use the class and its field names to automatically create a table; however, you can personalize the table that’s generated. To define a name for the table, use the tableName option on the @Entity annotation, and to edit the columns name, add a @ColumnInfo annotation with the name option on the field. It is important to remember that the table and column names are case sensitive.

Indices and Uniqueness Constraints

There are some useful SQLite constraints that Room allows us to easily implement on our entities. To speed up the search queries, you can create SQLite indices at the fields that are more relevant for such queries. Indices will make search queries way faster; however, they will also make insert, delete and update queries slower, so you must use them carefully. Take a look at the SQLite documentation to understand them better.

There are two different ways to create indices in Room. You can simply set the ColumnInfo property, index , to true , letting Room set the indices for you.

Читайте также:  Как восстановить планшет андроид samsung

Or, if you need more control, use the indices property of the @Entity annotation, listing the names of the fields that must compose the index in the value property. Notice that the order of items in value is important since it defines the sorting of the index table.

Another useful SQLite constraint is unique , which forbids the marked field to have duplicate values. Unfortunately, in version 1.0.0, Room doesn’t provide this property the way it should, directly on the entity field. But you can create an index and make it unique, achieving a similar result.

Other constraints like NOT NULL , DEFAULT , and CHECK aren’t present in Room (at least until now, in version 1.0.0), but you can create your own logic on the Entity to achieve similar results. To avoid null values on Kotlin entities, just remove the ? at the end of the variable type or, in Java, add the @NonNull annotation.

Relationship Between Objects

Unlike most object-relational mapping libraries, Room doesn’t allow an entity to directly reference another. This means that if you have an entity called NotePad and one called Note , you can’t create a Collection of Note s inside the NotePad as you would do with many similar libraries. At first, this limitation may seem annoying, but it was a design decision to adjust the Room library to Android’s architecture limitations. To understand this decision better, take a look at Android’s explanation for their approach.

Even though Room’s object relationship is limited, it still exists. Using foreign keys, it is possible to reference parent and child objects and cascade their modifications. Notice that it’s also recommended to create an index on the child object to avoid full table scans when the parent is modified.

Embedding Objects

It is possible to embed objects inside entities using the @Embedded annotation. Once an object is embedded, all of its fields will be added as columns in the entity’s table, using the embedded object’s field names as column names. Consider the following code.

In the code above, the Location class is embedded in the Note entity. The entity’s table will have two extra columns, corresponding to fields of the embedded object. Since we’re using the prefix property on the @Embedded annotation, the columns’ names will be ‘ note_location_lat ’ and ‘ note_location_lon ’, and it will be possible to reference those columns in queries.

4. Data Access Object

To access the Room’s Databases, a DAO object is necessary. The DAO can be defined either as an interface or an abstract class. To implement it, annotate the class or interface with @Dao and you’re good to access data. Even though it is possible to access more than one table from a DAO, it is recommended, in the name of a good architecture, to maintain the Separation of Concerns principle and create a DAO responsible for accessing each entity.

Insert, Update, and Delete

Room provides a series of convenient annotations for the CRUD operations in the DAO: @Insert , @Update , @Delete , and @Query . The @Insert operation may receive a single entity, an array , or a List of entities as parameters. For single entities, it may return a long , representing the row of the insertion. For multiple entities as parameters, it may return a long[] or a List instead.

As you can see, there is another property to talk about: onConflict . This defines the strategy to follow in case of conflicts using OnConflictStrategy constants. The options are pretty much self-explanatory, with ABORT , FAIL , and REPLACE being the more significant possibilities.

To update entities, use the @Update annotation. It follows the same principle as @Insert , receiving single entities or multiple entities as arguments. Room will use the receiving entity to update its values, using the entity PrimaryKey as reference. However, the @Update may only return an int representing the total of table rows updated.

Again, following the same principle, the @Delete annotation may receive single or multiple entities and return an int with the total of table rows updated. It also uses the entity’s PrimaryKey to find and remove the register in the database’s table.

Making Queries

Finally, the @Query annotation makes consultations in the database. The queries are constructed in a similar manner to SQLite queries, with the biggest difference being the possibility to receive arguments directly from the methods. But the most important characteristic is that the queries are verified at compile time, meaning that the compiler will find an error as soon as you build the project.

To create a query, annotate a method with @Query and write a SQLite query as value. We won’t pay too much attention to how to write queries since they use the standard SQLite. But generally, you’ll use queries to retrieve data from the database using the SELECT command. Selections may return single or collection values.

It is really simple to pass parameters to queries. Room will infer the parameter’s name, using the method argument’s name. To access it, use : , followed by the name.

Читайте также:  Растения против зомби для android

LiveData Queries

Room was designed to work gracefully with LiveData . For a @Query to return a LiveData , just wrap up the standard return with LiveData and you’re good to go.

After that, it will be possible to observe the query result and get asynchronous results quite easily. If you don’t know the power of LiveData, take some time to read our tutorial about the component.

5. Creating the Database

The database is created by an abstract class, annotated with @Database and extending the RoomDatabase class. Also, the entities that will be managed by the database must be passed in an array in the entities property in the @Database annotation.

Once the database class is implemented, it is time to build. It is important to stress that the database instance should ideally be built only once per session, and the best way to achieve this would be to use a dependency injection system, like Dagger. However, we won’t dive into DI now, since it is outside the scope of this tutorial.

Normally, operations on a Room database cannot be made from the UI Thread, since they are blocking and will probably create problems for the system. However, if you want to force execution on the UI Thread, add allowMainThreadQueries to the build options. In fact, there are many interesting options for how to build the database, and I advise you to read the RoomDatabase.Builder documentation to understand the possibilities.

6. Datatype and Data Conversion

A column Datatype is automatically defined by Room. The system will infer from the field’s type which kind of SQLite Datatype is more adequate. Keep in mind that most of Java’s POJO will be converted out of the box; however, it is necessary to create data converters to handle more complex objects not recognized by Room automatically, such as Date and Enum .

For Room to understand the data conversions, is necessary to provide TypeConverters and register those converters in Room. It is possible to make this registration taking into consideration specific context—for example, if you register the TypeConverter in the Database , all entities of the database will use the converter. If you register on an entity, only the properties of that entity may use it, and so on.

To convert a Date object directly to a Long during Room’s saving operations and then convert a Long to a Date when consulting the database, first declare a TypeConverter .

Then, register the TypeConverter in the Database , or in a more specific context if you want.

7. Using Room in an App

The application we’ve developed during this series used SharedPreferences to cache weather data. Now that we know how to use Room, we’ll use it to create a more sophisticated cache that’ll allow us to get cached data by city, and also consider the weather date during the data retrieval.

First, let’s create our entity. We’ll save all our data using only the WeatherMain class. We only need to add some annotations to the class, and we’re done.

We also need a DAO. The WeatherDAO will manage CRUD operations in our entity. Notice that all queries are returning LiveData .

Finally, it is time to create the Database .

Ok, we now have our Room database configured. All that is left to do is wire it up with Dagger and start using it. In the DataModule , let’s provide the Database and the WeatherDAO .

As you should remember, we have a repository responsible for handling all data operations. Let’s continue to use this class for the app’s Room data request. But first, we need to edit the providesMainRepository method of the DataModule , to include the WeatherDAO during the class construction.

Most of the methods that we’ll add to the MainRepository are pretty straightforward. It’s worth looking more closely at clearOldData() , though. This clears all data older than a day, maintaining only relevant weather data saved in the database.

The MainViewModel is responsible for making consultations to our repository. Let’s add some logic to address our operations to the Room database. First, we add a MutableLiveData , the weatherDB , which is responsible for consulting the MainRepository . Then, we remove references to SharedPreferences , making our cache rely only on the Room database.

To make our cache relevant, we’ll clear old data every time a new weather consultation is made.

Finally, we’ll save the data to the Room database every time new weather is received.

You can see the complete code in the GitHub repo for this post.

Conclusion

Finally, we’re at the conclusion of the Android Architecture Components series. These tools will be excellent companions on your Android development journey. I advise you to continue exploring the components. Try to take some time to read the documentation.

And check out some of our other posts on Android app development here on Envato Tuts+!

Источник

Оцените статью