14 jun 2016

Persistência de dados com Realm – Android

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:

1
2
3
4
5
6
7
8
9
10
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:

1
2
3
4
5
6
7
8
9
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.

1
2
3
4
5
6
7
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.

1
2
3
4
5
6
7
8
9
10
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:

1
2
3
4
5
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.

1
2
3
4
5
6
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.

1
2
3
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    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:

1
2
3
4
5
6
@Override
protected void onDestroy() {
    super.onDestroy();
    mRealm.removeChangeListener(this);
    mRealm.close();
}

Em nossa MainActivity no onResume() atualizo a lista, veja o exemplo:

1
2
3
4
5
@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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.

1
2
3
4
5
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
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”:

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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!

CTA-ebook-transformação-digital

Leave a Comment