Iniciação ao Realm

Douglas Drumond Kayama
Android Dev BR
Published in
5 min readMay 9, 2016

--

Inevitavelmente, exceto nos aplicativos extremamente triviais e que geralmente só servem de exemplo, é necessária alguma forma de persistência de dados no aparelho do usuário. No Android temos diversas formas de persistir dados. A mais simples é o SharedPreferences, que nada mais é que um arquivo contendo chave e valor. Também é possível escrever e ler de arquivos no sistema de arquivos. Mas para grande quantidade de dados estruturados, a saída é banco de dados.

SQLite vs ORM vs Realm

O banco de dados padrão do Android é o SQLite. SQLite é um sistema gerenciador de banco de dados relacional e autocontido. Ou seja, não é necessário um complexo sistema cliente-servidor para funcionar, o que o torna uma ótima escolha para embarcados. Mas, o SQLite no Android exige muito código boilerplate e a terrível mistura de SQL em strings no meio de código Java. Isso faz com que os desenvolvedores tenham que escolher entre repetir nomes de tabelas e campos em vários lugares (o que é propenso a erros) ou colocar esses nomes em constantes e conviver com uma quantidade grande de concatenação de texto, tornando a leitura desagradável.

Para suprir essa deficiência, diversos frameworks de ORM foram desenvolvidos para Android, como GreenDAO ou OrmLite.

Um ORM, de object-relational mapping, é uma camada que converte entre objetos, usados em alto nível, e as tabelas de um banco de dados relacional. Com isso, o programador lida apenas com classes e objetos já conhecidos da programação orientada a objetos.

No entanto, um ORM adiciona uma camada de processamento extra e o desempenho nem sempre é aceitável. Além, claro, de aumentar complexidade do código.

Por fim, ainda há uma outra alternativa de banco de dados no Android, o Realm.

O que é o Realm?

Devido à sintaxe similar a de ORMs, algumas pessoas pensam que Realm é um ORM para SQLite. Realm não é um ORM, é um banco de dados orientado a objetos criado especificamente para mobile. É um substituto para SQLite no Android e Core Data no iOS. O núcleo foi feito em C++ e os benchmarks indicam performance até 20x superior ao SQLite em algumas operações.

Adicionando o Realm

Para usar o Realm no Android, seguimos o caminho tradicional, ou seja, via Gradle. No arquivo build.gradle no nível superior, adicione as seguintes linhas:

buildscript {
repositories {
jcenter()
}
dependencies {

classpath 'io.realm:realm-gradle-plugin:1.0.0'

}
}

E no build.gradle do módulo da aplicação, adicione:

apply plugin: 'realm-android'

Por exemplo:

apply plugin: 'com.android.application'

apply plugin: 'realm-android'

android {
compileSdkVersion 23
buildToolsVersion "23.0.3"

defaultConfig {
applicationId "com.cafelinear.realmsample"
minSdkVersion 16
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.3.0'
}

Só isso. Seu aplicativo está pronto para usar o Realm.

Definindo tabelas

No Realm, não dizemos realmente tabelas, são modelos (Models), mas o comportamento é semelhante ao das tabelas. Para criar um Model, basta estender a classe RealmObject. Por exemplo:

public class TaskModel extends RealmObject {

private int taskId;
private String description;
private boolean checked;

public int getTaskId() {
return taskId;
}

public void setTaskId(final int taskId) {
this.taskId = taskId;
}

public String getDescription() {
return description;
}

public void setDescription(final String description) {
this.description = description;
}

public boolean isChecked() {
return checked;
}

public void setChecked(final boolean checked) {
this.checked = checked;
}
}

Crie getters e setters para seus campos. (N.A.: na versão anterior, afirmei que não poderia haver código customizado nos getters e setters, mas desde a versão 0.88 isso tornou-se falso. Obrigado, @chrmelchior pelo aviso.)

Construtores parametrizados podem ser adicionados, se desejar, mas o construtor default deve estar presente.

Além dos tipos primitivos (boolean, byte, short, int, long, float, double), Realm também suporta String, Date e byte[]. Também podem ser usadas as classes equivalentes aos tipos primitivos (Boolean, Byte, etc), para caso algum campo seja nulo. Em caso de relacionamento entre models, é permitido criar campos do tipo RealmObject and RealmList<? extends RealmObject>. Para os tipos numéricos inteiros (byte, short, int e long), internamente Realm irá utilizar long, mas convenientemente suporta todos os quatro tipos no modelo.

Também podemos fazer uso de certas anotações para indicar características especiais nos campos:

  • @Required: campo não pode ser nulo;
  • @Index: campo será indexado;
  • @PrimaryKey chave primária;
  • e, por fim, @Ignore para campos não persistidos em disco.

Alternativa

Em vez de estender a classe RealmObject, uma alternativa é implementar a interface RealmModel. Essa opção é recente, foi adicionada na versão 0.89. O jeito recomendado ainda é estendendo RealmObject. Ao implementar RealmModel, também devemos adicionar a anotação @RealmClass, pois herança de anotações de uma interface ainda não é suportado no Android.

No exemplo acima, teríamos:

@RealmClass
public class
TaskModel implements RealmModel {

}

Essa interface não contém métodos, não há nada a ser de fato implementado.

Acessando o banco de dados

Agora que temos um Model, como proceder para efetivamente usar o banco de dados?

O primeiro passo para efetuar qualquer operação no Realm é obter uma instância de RealmConfiguration para, em seguida, obter a instância do banco. A partir dessa instância do banco, podemos finalmente efetuar buscas, inserir dados, etc., e a sintaxe é semelhante a de ORMs.

RealmConfiguration realmConfig = new RealmConfiguration                  
.Builder(context).build();
Realm realm = Realm.getInstance(realmConfig);

Pronto, agora todas as operações no banco serão feitas a partir do objeto realm.

Por exemplo, para listar todas as tarefas cadastradas, é possível usar findAll:

RealmResults<TaskModel> tasks =     
realm.where(TaskModel.class).findAll();

Se quiséssemos recuperar somente as tarefas já concluídas, poderíamos adicionar restrições:

tasks = realm.where(TaskModel.class)
.equalTo("checked", true).findAll();

Muito fácil, não?

Para inserir um dado, basta criar um objeto do tipo do Model e inseri-lo, colocando a chamada de inserção dentro de uma transação:

// Cria um novo objeto
TaskModel newTask = new TaskModel();
newTask.setTaskId(1); // Use um ID válido
newTask.setDescription("Hew task description");
newTask.setChecked(false);

// Insere no banco
realm.beginTransaction();
realm.copyToRealm(newTask);
realm.commitTransaction();

No caso de atualizações, após recuperar o objeto numa busca, bastaria alterar o campo desejado e efetuar a mesma transação de inserção. Por exemplo, vamos marcar a primeira tarefa:

tasks = realm.where(TaskModel.class)
.equalTo("checked", false).findAll();

TaskModel changedTask = tasks.get(0);
changedTask.setChecked(true);
realm.beginTransaction();
realm.copyToRealm(changedTask);
realm.commitTransaction();

Somente a exclusão é levemente diferente, feita a partir da instância do modelo:

changedTask.deleteFromRealm();

Caso tenha implementado a interface RealmModel, use o método estático em RealmObject:

RealmObject.deleteFromRealm(changedTask);

Muito mais simples que SQLite.

Também é possível executar operações assíncronas com executeTransactionAsync:

realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realmDb) {
TaskModel taskModel = realmDb.createObject(TaskModel.class);
taskModel.setChecked(true);
taskModel.setDescription("Limpar o quarto");
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
// Transação foi um sucesso
}
}, new Realm.Transaction.OnError() {
@Override
public void onError(Throwable error) {
// Transação foi cancelada devido a algum erro.
}
});

Os listeners de OnSuccess() e OnError() são opcionais.

Conclusão

Esses são os primeiros passos para colocar o Realm em um aplicativo e vimos algumas de suas funcionalidades. Porém, ainda temos uma gama de outras funcionalidades interessantes e tópicos para abordarmos em um próximo artigo.

Experimente o Realm e será convencido da sua facilidade.

--

--

Douglas Drumond Kayama
Android Dev BR

Engineering Manager. Half-mathematician, half-computer scientist, using photography to blend with humans and be less robot.