Room. Type converter
У цьому уроці розглянемо, як використовувати конвертери типів даних, щоб Room міг зберігати не тільки поля-примітиви.
Іноді ваші 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. Для цього можна розглянути можливість додавання Type Adapter.
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.
Якщо у вас кілька конвертерів, вказуйте їх через кому.