БД Realm в Android – простой пример

Что такое Realm?

Изучая хранение данных в БД по урокам на нашем канале, вы могли познакомиться с БД SQLite. SQLite традиционно используется в Android для хранения самых разных типов данных. SQLite демонстрирует надежность, гибкость, она имеет хорошую документацию и много примеров. Но относительно недавно стала популярной новая Бд Realm, разработчики которой позиционируют ее как полноценную замену SQLite. Realm имеет собственное ядро, написанное на C++. Не волнуйтесь, для работы с базой данных Realm учить С++ совсем не обязательно. Таким образом, что очень важно, Realm не использует SQLite. Если вы используете ORMLite или Sugar ORM, которую мы рассматривали в этом видео, ваши данные все равно хранятся в базе данных SQLite. Эти библиотеки дают нам только надстройку над SQLite для более удобной работы с БД. При использовании Realm, SQLite не используется  вообще. При этом Realm работает очень быстро, так что зачастую можно даже сохранять данные в UI потоке.

Основные преимущества Realm:

  • до 10 раз быстрее, чем SQLite
  • проста в использовании
  • удобно для создания и хранения данных на лету
  • хорошая документация и поддержка

Простой пример

Рассмотрим простой пример, который демонстрирует, как работает база данных Realm и как ее использовать. Скачайте пример кода из хранилища GitHub и импортируйте проект в Android Studio.

Gradle

За добавление Realm в проект отвечает такая строка в разделе зависимостей файла сборки build.gradle.

dependencies {

   compile 'io.realm:realm-android:0.87.4'

   //other dependencies

}

 

Рассмотрим код

Приложение состоит из одного активити (контейнера) и двух фрагментов.

Первый фрагмент позволяет нам добавить книгу, которая хранится в базе данных (MyEditionFragment), а второй показывает список всех книг из базы данных (MyListFragment).

Начнем с файлов разметки.

Разметка главного экрана – здесь контейнеры для фрагментов.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".ui.MyMainActivity">

    <FrameLayout
        android:id="@+id/edition_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <FrameLayout
        android:id="@+id/list_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

</LinearLayout>

Файл разметки фрагмента MyEditionFragment содержит поле  ввода текста и две кнопки – добавить и удалить.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/light_green">

    <EditText
        android:id="@+id/edit_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Write a title"/>

    <Button
        android:id="@+id/button_add"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/add"/>

    <Button
        android:id="@+id/button_remove"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/remove"/>

</LinearLayout>

В разметке MyListFragment компонент RecyclerView для отображения списка.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/light_yellow">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"/>

</LinearLayout>

item_book.xml содержит описание пункта списка.

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/text_title"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

В активити мы создаем фрагменты и размещаем их на главном экране.

RealmObject

Для использования объекта в качестве объекта Realm вы должны просто расширить класс RealmObject.

Аннотация @Required означает , что заголовок не может быть пустым.

public class MyBook extends RealmObject {

   @Required

   private String title;

   public String getTitle() {

       return title;

   }

   public void setTitle(final String title) {

       this.title = title;

   }

}

 

Как видите, класс MyBook для простоты содержит только одно поле title с геттером и сеттером, в реальности их может быть гораздо больше.

Операции CRUD

Все стандартные операции с БД, такие как создание, чтение, обновление и удаление записей выполняются с экземпляром класса Realm. Это можно сделать несколькими способами, которые подробно описаны в документации realm, найти ее можно по ссылке в описании видео.

В классе MyEditionFragment

  1. Получаем экземпляр Realm .

 

 private Realm mRealm;

   @Override

   public void onCreate(final Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       mRealm = Realm.getInstance(getContext());

   }

 

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

 

  @OnClick(R.id.button_add)

   public void onAddClick() {

       mRealm.beginTransaction();

       MyBook book = mRealm.createObject(MyBook.class);

       book.setTitle(getTrimmedTitle());

       mRealm.commitTransaction();

   }

   @OnClick(R.id.button_remove)

   public void onRemoveClick() {

       mRealm.beginTransaction();

       RealmResults<MyBook> books = mRealm.where(MyBook.class).equalTo("title", getTrimmedTitle()).findAll();

       if(!books.isEmpty()) {

           for(int i = books.size() - 1; i >= 0; i--) {

               books.get(i).removeFromRealm();

           }

       }

       mRealm.commitTransaction();

   }

   private String getTrimmedTitle() {

       return mEditTitle.getText().toString().trim();

   }

 

Каждая операция с базой данных , кроме операции чтения, должна начинаться с выполнения метода Realm.beginTransaction () и заканчиваться выполнением метода Realm.commitTransaction (). Это гарантирует, что наши данные всегда будут находиться в согласованном состоянии, и обеспечивает безопасность потока. Если вы забыли зафиксировать ваши изменения методом Realm.commitTransaction (), они не будут сохранены. Вы также можете отменить транзакцию вызовом метода Realm.cancelTransaction ().

  1. В соответствии с документацией Realm, необходимо закрыть все экземпляры Realm методом Realm.close(), когда мы закончили работу с ними.

 

  @Override

   public void onDestroy() {

       super.onDestroy();

       mRealm.close();

   }

 

Обратите внимание, для привязки кнопок к методам обработки нажатий мы используем аннотации @OnClick.

Такую возможность нам обеспечивает библиотека Butter Knife, которая используется для быстрой привязки вьюшек. Я ее как-нибудь рассмотрю в одном из выпусков “инструментов разработчика”, а сейчас вернемся к Realm. 

Автообновление

Приведенный пример показывает, как добавить или удалить какой-либо объект в Realm, но реальная магия происходит в классе MyListFragment, который работает со списком книг.

Нужно обновлять наш список каждый раз, когда книга добавляется в базу данных или удаляется из нее. Для этого нужно:

  1. Получить экземпляр Realm в методе onCreate () фрагмента и закрыть его в методе onDestroy () .
  2. Передать все книги адаптеру.

   

@Override

   public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) {

       super.onViewCreated(view, savedInstanceState);

       mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

       mRecyclerView.setAdapter(new MyListAdapter(mRealm.allObjects(MyBook.class)));

   }

 

Запрос Realm.allObjects (ClassName.class) возвращает объекты RealmResults. Важно понимать , что данные в Realm никогда не копируются. Мы получаем список ссылок и работаем над оригинальными объектами.

Теперь посмотрим код адаптера списка MyListAdapter

Здесь мы добавляем слушатель RealmChangeListener к RealmResults .

 

 public MyListAdapter(RealmResults<MyBook> books) {

       mBooks = books;

       mBooks.addChangeListener(this);

   }

   @Override

   public void onChange() {

       notifyDataSetChanged();

   }

 

И теперь, если сли мы добавим новую книгу в MyEditionFragment, список в MyListFragment будет обновляться автоматически. Все , что мы сделали, это добавили слушатель , который сообщит нам, если набор данных изменился. Это все, что нам нужно. Метод OnChange () не имеет объекта (или списка объектов) , которые изменены, потому что нам не нужна эта информация. Она у нас уже есть. Как уже было сказано ранее, данные Realm никогда не копируются. Поэтому , когда новая книга добавляется в  MyEditionFragment , адаптер списка будет проинформирован, что набор книг был изменен , поскольку объект mBooks автомагически обновляется Realm. Все , что нам остается сделать, это обновить представление списка.

Возможные проблемы

Венгерская нотация

Если вы используете префикс ‘m’ для полей  ваших классов, следует помнить ,что геттеры и сеттеры для них должны также иметь символ ‘m’ в именах. Подробнее.

Схема миграции

Во время разработки баз данных схемы все время меняются. Если у вас есть какие – то данные , хранящиеся в базе данных Realm, любые будущие изменения схемы приведут к проблемам миграции. Вы можете либо обрабатывать изменения миграции, либо удалить файл .realm со всеми данными, если Realm обнаруживает проблемы миграции.

RealmConfiguration config = new RealmConfiguration.Builder(context)

   .deleteRealmIfMigrationNeeded()

   .build()

 

Миграция из SQLite

Если у вас есть в настоящее время есть приложение, которое использует SQLite и вы хотите мигрировать в Realm, это потребует достаточно труда. SQLite хранит данные в упорядоченной форме, которая не обязательно будет работать лучше всего с Realm. Лучше пересмотреть свою схему данных и смоделировать их в качестве объектов.

После того, как вы измените схему, для адаптации к Realm, будет гораздо легче переносить любые данные из SQLite. Просто настройте миграцию (начиная с версии от 0 до 1 из базы данных Realm), и в этой миграции, загружайте данные SQLite в объекты Realm, а затем просто сохранить их.

Если ваши данные также находится на удаленном сервере, вы можете просто создать базу данных Realm с нуля.

Перед тем, как остановить ваш выбор на Realm, имейте в виду, что это передовое программное обеспечение, с API, которые может иметь критические изменения в будущих версиях.

Проект можно найти на  DroidsOnRoids Github в хранилище ExampleRealm-Android .

А более тесную работу с Realm мы продемонстрируем в продвинутом курсе по разработке приложения для группы вконтакте, который выйдет уже совсем скоро.

<<<Предыдущий урок      Следующий урок>>>

Коментарі: 2
  1. Александр

    А уже есть приложение для группы ВКонтакте? Или это всё по-прежнему в планах?..

    1. Виталий Непочатов
      Виталий Непочатов

      Приложение в финальной стадии разработки. Этой весной закончим и выпустим курс по нему.

Додати коментар