В этом уроке рассмотрим, как использовать конвертеры типов данных, чтобы Room мог сохранять не только поля-примитивы.
Полный список уроков курса:
- Урок 1. Lifecycle
- Урок 2. LiveData
- Урок 3. LiveData. Дополнительные возможности
- Урок 4. ViewModel
- Урок 5. Room. Основы
- Урок 6. Room. Entity
- Урок 7. Room. Insert, Update, Delete, Transaction
- Урок 8. Room. Query
- Урок 9. Room. RxJava
- Урок 10. Room. Запрос из нескольких таблиц. Relation
- Урок 11. Room. Type converter
- Урок 12. Room. Миграция версий базы данных
- Урок 13. Room. Тестирование
- Урок 14. Paging Library. Основы
- Урок 15. Paging Library. PagedList и DataSource. Placeholders.
- Урок 16. Paging Library. LivePagedListBuilder. BoundaryCallback.
- Урок 17. Paging Library. Виды DataSource
- Урок 18. Android Data Binding. Основы
- Урок 19. Android Data Binding. Код в layout. Доступ к View
- Урок 20. Android Data Binding. Обработка событий
- Урок 21. Android Data Binding. Observable поля. Двусторонний биндинг.
- Урок 22. Android Data Binding. Adapter. Conversion.
- Урок 23. Android Data Binding. Использование с include, ViewStub и RecyclerView.
- Урок 24. Navigation Architecture Component. Введение
- Урок 25. Navigation. Передача данных. Type-safe аргументы.
- Урок 26. Navigation. Параметры навигации
- Урок 27. Navigation. NavigationUI.
- Урок 28. Navigation. Вложенный граф. Global Action. Deep Link.
- Урок 29. WorkManager. Введение
- Урок 30. WorkManager. Критерии запуска задачи.
- Урок 31. WorkManager. Последовательность выполнения задач.
- Урок 32. WorkManager. Передача и получение данных
- Урок 33. Практика. О чем это будет.
- Урок 34. Практика. TodoApp. Список задач.
- Урок 35. Практика. TodoApp. Просмотр задачи
Иногда ваши Entity объекты могут содержать поля, которые не являются примитивами, и не могут быть сохранены в БД.
В качестве примера рассмотрим класс работника. У него вполне может быть поле, в котором мы хотим перечислить его хобби. Используем для этого поле hobbies с типом List<String>
@Entity() public class Employee { @PrimaryKey public long id; public String name; public int salary; public List<String> hobbies; }
Если мы попытаемся сейчас скомпилировать проект, то получим ошибку: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
Room справедливо замечает, что понятия не имеет, как ему такое поле сохранить в базу, и предлагает использовать type converter.
Ок, давайте создадим конвертер. Он должен уметь конвертировать List<String> в какой-нибудь простой тип, который может быть сохранен в базу, например, String. Также конвертер должен уметь конвертировать в обратную сторону, т.е. из String в List<String>, чтобы Room мог прочесть данные из базы в поле Entity объекта.
Создаем конвертер:
public class HobbiesConverter { @TypeConverter public String fromHobbies(List<String> hobbies) { return hobbies.stream().collect(Collectors.joining(",")); } @TypeConverter public List<String> toHobbies(String data) { return Arrays.asList(data.split(",")); } }
Первый метод преобразует List<String> в String. Второй - наоборот. Оба метода помечаем аннотацией TypeConverter.
Осталось указать этот конвертер для поля hobbies. Это делается аннотацией TypeConverters с указанием класса конвертера.
@Entity() public class Employee { @PrimaryKey public long id; public String name; public int salary; @TypeConverters({HobbiesConverter.class}) public List<String> hobbies; }
Теперь Room будет знать, что для поля hobbies он может использовать конвертер HobbiesConverter.
Конвертер также можно указать для всего Entity объекта. Это может быть полезно, если у вас в Entity несколько полей требуют конвертеры. Вы создаете один класс, там прописываете все необходимые методы преобразования полей, и указываете этот класс для всего Entity.
@Entity() @TypeConverters({EmployeeConverter.class}) public class Employee { @PrimaryKey public long id; public String name; public int salary; public List<String> hobbies; }
Бывают случаи, когда преобразование может быть необходимо не только для Entity объекта. Рассмотрим пример.
Есть Entity класс
@Entity() public class Employee { @PrimaryKey public long id; public String name; public int salary; public long birthday; }
У работника все поля являются простыми, и Room без проблем может их сохранить/прочесть. Этим полям не нужны конвертеры.
Но что если мы хотим в Dao сделать так:
@Dao public interface EmployeeDao { @Query("SELECT * FROM employee WHERE birthday = :birthdayDate") Employee getByDate(Date birthdayDate); }
Т.е. нам для поиска по полю birthday (с типом long) удобнее использовать объект Date.
При попытке собрать проект получаем ошибку: Query method parameters should either be a type that can be converted into a database column or a List / Array that contains such type. You can consider adding a Type Adapter for this.
Room сообщает, что типы не совпадают и снова предлагает использовать конвертеры.
Создаем конвертер:
public class DateConverter { @TypeConverter public Long dateToTimestamp(Date date) { if (date == null) { return null; } else { return date.getTime(); } } }
В нашем случае необходимо Date конвертировать в long, чтобы Room мог выполнить query запрос. Создаем для этого метод dateToTimestamp.
Обратная конвертация нам не нужна. У Room нет необходимости конвертировать long в Date. Объект Employee будет содержать дату в формате long.
Конвертер прописываем в Dao, прямо для конкретного параметра конкретного метода
@Dao public interface EmployeeDao { @Query("SELECT * FROM employee WHERE birthday = :birthday") Employee getByDate(@TypeConverters({DateConverter.class}) Date birthday); }
Теперь Room конвертирует Date в long и запрос будет выполнен.
Также конвертер можно прописать для всего метода, а не отдельного параметра
@Dao public interface EmployeeDao { @Query("SELECT * FROM employee WHERE birthday BETWEEN :birthdayFrom and :birthdayTo") @TypeConverters({DateConverter.class}) Employee getByDate(Date birthdayFrom, Date birthdayTo); }
В этом случае Room сможет использовать конвертер для преобразования всех параметров метода.
Если же прописать конвертер для Dao, то он будет доступен всем методам этого Dao
@Dao @TypeConverters({DateConverter.class}) public interface EmployeeDao { ... }
Ну и самое глобальное решение - прописать конвертер для Database
@Database(entities = {Employee.class}, version = 1) @TypeConverters({DateConverter.class}) public abstract class AppDatabase extends RoomDatabase { public abstract EmployeeDao employeeDao(); }
В этом случае Room сможет использовать его во всех Entity и Dao.
Если у вас несколько конвертеров, указывайте их через запятую.
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня