Iniciação ao Realm
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.