Neste post vou falar sobre uma forma diferente de persistir dados no android, que normalmente é feita com sqlite ou algum framework que utiliza ele, falarei da biblioteca Realm. Em comparação de desempenho fica claro a maior eficiência em relação ao sqlite como mostrado na documentação da Realm:
https://realm.io/news/introducing-realm-react-native/#benchmarks
Nesse post mostrarei como realizar um CRUD em uma aplicação simples com Realm e como fazer a migração quando a versão do banco for alterada. No final do post colocarei o link do repositório com o projeto de exemplo, vamos começar!
Configuração do projeto
Em nosso build.gradle precisaremos acrescentar as configurações para incluir a biblioteca Realm em nosso projeto, segue o código:
apply plugin: 'realm-android'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:1.0.0"
}
}
Será necessário criarmos uma classe herdando de Application aonde vamos configurar a Realm em nossa aplicação, o código ficará assim:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this).build();
Realm.setDefaultConfiguration(realmConfiguration);
}
}
Criando modelo
A aplicação será bem simples, ela terá uma lista, uma tela para inserir dados e uma tela para edição dos dados existentes. A MainActivity da aplicação vai ter uma lista utilizando um RecyclerView. A lista será de guitarras :), e para isso nossa classe modelo precisará herdar de RealmObject, senão nossa classe modelo não será usada para montar nossa tabela no banco, o código da classe está abaixo.
public class Guitar extends RealmObject {
@PrimaryKey
private Long id;
private String name;
private String color;
}
Não esqueça de acrescentar os métodos de acesso getters e setters da nossa classe Guitar, ela tem também a anotação “@PrimaryKey” da biblioteca para o id ser único, porém a Realm não tem um auto increment o que nos obrigará a fazer na unha, faremos dentro da nossa classe Guitar, nela acrescente um método estático.
public static Long autoIncrementId(){
Long key = 1L;
Realm realm = Realm.getDefaultInstance();
try {
key = realm.where(Guitar.class).max("id").longValue() + 1;
} catch (NullPointerException e) {
e.printStackTrace();
}
return key;
}
Criando lista
Na configuração da lista na nossa MainActivity chamaremos todos os itens que existirem na nossa tabela Guitar, exemplo abaixo:
private Realm mRealm;
mRealm = Realm.getDefaultInstance();
RealmQuery guitars = mRealm.where(Guitar.class);
mRecyclerView.setAdapter(new ListGuitarAdapter(this, guitars));
Criei um objeto do tipo Realm na nossa classe para utilizar ele em qualquer método dentro da MainActivity, passei para meu adapter uma RealmQuery e a partir dela temos vários métodos para filtrar a lista que retornar da tabela. No construtor do ListGuitarAdapter chamo o método findAll() para recuperar todos itens que houver na tabela.
private RealmResults mGuitars;
public ListGuitarAdapter(Context context, RealmQuery guitars) {
mGuitars = guitars.findAll().sort("id");
mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
O sort(“id”) é para forçar ordenar por id a lista, já que tive um problema quando excluí items da lista e a ela não ordenou por id depois de excluir um item. O findAll() retorna um RealmResults, e com ele vamos acessar os dados que existirem em nossa tabela. No onBindViewHolder do nosso adapter vamos popular os campos da lista.
final Guitar guitar = mGuitars.get(position);
holder.mTextViewName.setText(guitar.getName());
holder.mTextViewColor.setText(guitar.getColor());
Inserindo dados
Em nossa Activity NewGuitarActivity vamos salvar os itens cadastrados, veja o código exemplo abaixo:
public void onClickSave(View v) {
mRealm.beginTransaction();
Guitar guitar = mRealm.createObject(Guitar.class);
guitar.setId(Guitar.autoIncrementId());
guitar.setName(mEditTextName.getText().toString());
guitar.setColor(mEditTextColor.getText().toString());
mRealm.commitTransaction();
mRealm.addChangeListener(this);
}
@Override
public void onChange(Realm element) {
onBackPressed();
}
Repare que para inserir dados é necessário abrir uma transaction e no final do bloco chamar o método commitTransaction(), na activity implementei a interface RealmChangeListener<Realm> com seu método onChance() para voltar para a activity anterior assim que a alteração for concluída. No onDestroy() é necessário remover o listener implementado para a evitar uma exception, já que a instância única do realm tentará avisar essa classe que houve mudanças no banco mesmo depois que ela não existir mais e por fim, chamamos o método close() do Realm (em nossa MainActivity também é feito isso), veja abaixo:
@Override
protected void onDestroy() {
super.onDestroy();
mRealm.removeChangeListener(this);
mRealm.close();
}
Em nossa MainActivity no onResume() atualizo a lista, veja o exemplo:
@Override
protected void onResume() {
super.onResume();
mRecyclerView.getAdapter().notifyDataSetChanged();
}
Deletando dados
Nossa lista tem dois botões um para excluir e outro para editar o item, o botão para exclusão terá o código a seguir:
holder.mButtonRemove.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Realm.getDefaultInstance().executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
guitar.deleteFromRealm();
}
});
Realm.getDefaultInstance().addChangeListener(new RealmChangeListener() {
@Override
public void onChange(Realm element) {
notifyDataSetChanged();
}
});
}
});
Neste caso utilizei uma outra forma de executar uma transação, através da interface Transaction da Realm e o seu método execute().
Atualizando dados
No evento de clique do botão de edição eu chamo uma nova activity passando o id do item por intent, na EditGuitarActivity preencho os campos recuperando os dados do item salvo.
Long id = getIntent().getLongExtra(INTENT_KEY_EDIT_ID, 0L);
Realm realm = Realm.getDefaultInstance();
mGuitar = realm.where(Guitar.class).equalTo("id", id).findFirst();
mEditTextName.setText(mGuitar.getName());
mEditTextColor.setText(mGuitar.getColor());
O código para salvar os dados preenchido no formulário do botão fica assim:
mRealm.beginTransaction();
mGuitar.setName(mEditTextName.getText().toString());
mGuitar.setColor(mEditTextColor.getText().toString());
mRealm.commitTransaction();
mRealm.addChangeListener(this);
@Override
protected void onDestroy() {
super.onDestroy();
mRealm.removeChangeListener(this);
mRealm.close();
}
Migração
Quando for acrescentado mais algum model ou alterado a estrutura de um que já existe para persistir no nosso banco será gerado uma nova versão, e será preciso colocar as alterações em nossa aplicação. No nosso exemplo acrescentarei um campo na nossa classe Guitar que será chamado de “strings”:
private String strings;
public String getStrings() {
return strings;
}
public void setStrings(String strings) {
this.strings = strings;
}
Vamos criar uma classe que implementará a interface RealMigration, no seu método migrate vamos colocar as alterações que nosso banco sofreu nessa nova versão, veja abaixo o exemplo:
public class MigrationMyData implements RealmMigration {
public static final Long VERSION = 1L;
@Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
RealmSchema schema = realm.getSchema();
if (oldVersion == 0) {
schema.get("Guitar").addField("strings", String.class);
oldVersion++;
}
}
}
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this)
.schemaVersion(MigrationMyData.VERSION)
.migration(new MigrationMyData())
.build();
Realm.setDefaultConfiguration(realmConfiguration);
}
}
Quando iniciamos nossa aplicação a versão do nosso banco é 0, alterei a version para 1 e passei no schemaVersion e uma instância da classe MigrationMyData no migration() em nossa configuração na classe MyApplication. Alterando a versão a interface RealmMigration é chamada, nela recebemos o oldVersion que neste caso será 0, e informo a alteração sofrida da versão 0 para a 1, se houver novas alterações será necessário acrescentar na nossa classe MigrationMyData como foi feito agora. Se em uma futura versão for acrescentado uma nova tabela é necessário criar um model herdando de RealmObject como fizemos antes mudando a versão para 2 e acrescentar a altereção na MigrationMyData, exemplo:
public class MigrationMyData implements RealmMigration {
public static final Long VERSION = 2L;
@Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
RealmSchema schema = realm.getSchema();
if (oldVersion == 0) {
schema.get("Guitar")
.addField("strings", String.class);
oldVersion++;
}
if (oldVersion == 1) {
schema.create("Drums")
.addField("id", Long.class, FieldAttribute.PRIMARY_KEY)
.addField("name", String.class)
.addField("color", String.class);
oldVersion++;
}
}
}
Perceba que é importante manter todas as alterações que ocorrerem com a evolução da aplicação.
O projeto está disponível no github, clique aqui
Qualquer dúvida ou sugestão deixem nos comentários, espero ter ajudado!