Migração de Dados. Realm Library no Android - Parte 6

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes! Você receberá um email de confirmação. Somente depois de confirma-lo é que eu poderei lhe enviar os conteúdos semanais exclusivos. Os artigos em PDF são entregues somente para os inscritos na lista.

Email inválido.
Blog /Android /Migração de Dados. Realm Library no Android - Parte 6

Migração de Dados. Realm Library no Android - Parte 6

Vinícius Thiengo
(2050)
Go-ahead
"O método consciente de tentativa e erro é mais bem-sucedido que o planejamento de um gênio isolado."
Peter Skillman
Prototipagem Android
Capa do curso Prototipagem Profissional de Aplicativos
TítuloAndroid: Prototipagem Profissional de Aplicativos
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
Acessar Curso
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Lendo
TítuloManual de DevOps: como obter agilidade, confiabilidade e segurança em organizações tecnológicas
CategoriaEngenharia de Software
Autor(es)Gene Kim, Jez Humble, John Willis, Patrick Debois
EditoraAlta Books
Edição
Ano2018
Páginas464
Conteúdo Exclusivo
Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba gratuitamente conteúdos Android sem precedentes!
Email inválido

Opa, blz?

Nesse vídeo 6 da série Persistência de Dados com Real Library no Android, é apresentado como migrar os dados da versão antiga do Realm em sua APP Android para a versão mais atual onde as classes que representam tabelas no Realm podem ter sofrido atualização como adição ou remoção de propriedades de classe. Note que esse ainda não é o vídeo onde tem a migração de dados de uma base de dados externa para a base interna Realm.

Seguindo o exemplo da documentação do Realm é possível encontrar algumas dificuldades, pois essa parte de migração de dados ainda não está bem formulada e têm já com a versão 0.84.2 (a mais atual até esse post) um bug na utilização do annotation @PrimaryKey quando adicionando uma nova classe a estrutura Realm da APP.

Vamos seguir com o código de exemplo, que apesar de simples deve ser entendido nos detalhes, pois se errar a migração falaha e então terá de voltar a base no modo original (1ª versão) para tentar novamente.

O primeiro passo é adicionar a propriedade "age" em nossa classe Student e assim:

import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;

public class Student extends RealmObject {
public static final String ID = "br.com.thiengo.realmexample.domain.Student.ID";

@PrimaryKey
private long id;
private String name;
private String email;
private RealmList<Grade> grades;
private int age;

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public RealmList<Grade> getGrades() {
return grades;
}

public void setGrades(RealmList<Grade> grades) {
this.grades = grades;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

 

O próximo passo é adicionar a classe MigrationData em nosso domínio do problema, essa será a classe responsável por todo o código de migração de versão. Essa implementará a interface RealmMigration e então teremos de implementar o método execute. Segue código da classe:

import io.realm.Realm;
import io.realm.RealmMigration;
import io.realm.internal.ColumnType;
import io.realm.internal.Table;

/**
* Created by viniciusthiengo on 11/16/15.
*/
public class MigrationData implements RealmMigration {
public static final int VERSION = 1;

@Override
public long execute(Realm realm, long version) {

if( version == VERSION - 1 ){
Table studentTable = realm.getTable(Student.class);
long idKey = studentTable.getColumnIndex("id");
studentTable.addSearchIndex(idKey);

Table disciplineTable = realm.getTable(Discipline.class);
idKey = disciplineTable.getColumnIndex("id");
disciplineTable.addSearchIndex(idKey);

Table gradeTable = realm.getTable(Grade.class);
idKey = gradeTable.getColumnIndex("id");
gradeTable.addSearchIndex(idKey);

studentTable.addColumn(ColumnType.INTEGER, "age");
}

return ++version;
}
}

 

Note que quando estavamos utilizando a base Realm sem a especificação de versão (schemaVersion) ela se manteve sempre em versão 0, porém com a migração de dados em mudança deversão temos de setar a versão correta e então criar o script de migração de acordo com a versão (que será o script de migração para a ultima versão da base).

O parâmetro versio do método execute na verdade é a versão atual da base Realm no sistema (a versão antiga) e não a versão mais nova que devemos setar no script de configuração que se encontra na classe CustomApplication (será apresentado o código dela no decorrer do post). Logo no return do método temos de retornar a versão mais nova, então ++version se encarrega de atualizar antes do retorno.

O condicional nos informa que somente será atualizado a base Realm se a versão atual for igual a versão mais nova subtraido de um. Essa constante VERSION foi colocada nessa classe para evitar que tenhamos de ficar atualizando sempre dois lugares na APP, a classe MigrationData e a classe CustomApplication, assim é atualizada a versão em apenas um ponto da APP.

Os trechos de códigos repetidos para cada classe em nossa base Realm é necessário, pois todas estão utilizando o annotation @PrimaryKey, é uma espécie de bug que para poder manter os primaries keys é necessário realizar esse script para cada uma das classes que tem o @PrimaryKey, as que não tem não há necessidade.

No final do condicional, enfim, adicionamos o campo age a tabela student em Realm, onde setamos no método addColumn() o tipo da propriedade, ColumnType.INTEGER e logo depois o label da propriedade na classe / tabela Student, "age".

Próximo passo e último é modificar nossa classe CustomApplication para ao invés de deletar a base e criar uma nova ainda com a versão 0, utilizar a nova versão e chamar uma instancia da classe MigrationData para realizar a migração corretamente Segue código:

import android.app.Application;

import br.com.thiengo.realmstudents.domain.MigrationData;
import io.realm.Realm;
import io.realm.RealmConfiguration;

public class CustomApplication extends Application {
@Override
public void onCreate() {
super.onCreate();

RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this)
.name("realm-students.realm")
.schemaVersion(MigrationData.VERSION)
.migration(new MigrationData())
.build();

Realm.setDefaultConfiguration( realmConfiguration );
}
}

 

Note a constante VERSION de MigrationData sendo utilizada para a modificação de versão de base, nesse caso como a versão estava em 0, a constante é 1. Antes de rodar é importante que verifique o estado atual da APP com a base Realm, digo, para ver logo depois da execução que nenhum dado foi perdido, pois esse deve ser o comportamento.

Esses DISCIPLINAS (1) e ESTUDANTES (1) devem continuar depois de executado nosso exemplo (pois essa era a base atual do device utilizado no exemplo, como apresentado em vídeo, em seu caso será o estado atual de sua base Realm).

Note a vantagem do script de migração, nós não temos de obter todos os dados da base e então inserí-los novamente, caso necessário quando em SQLite ou interfaces de acesso a ele. Abaixo o printscreen do RealmBrowser (que por enquanto somente para sistemas Mac está disponível) com o resultado, a propriedade "age" adicionada:

Agora vamos para o exemplo onde iremos remover a propriedade "age" e adicionar as propriedades "address" e "friends". "address" será do novo tipo Address adicionado ao dominio do problema e que também extends RealmObject. "friends" será do tipo RealmList<Student>, pois será uma lista de estudantes amigos do estudante atual do objeto.

Primeiro passo é o novo código da classe Student:

import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;

public class Student extends RealmObject {
public static final String ID = "br.com.thiengo.realmexample.domain.Student.ID";

@PrimaryKey
private long id;
private String name;
private String email;
private RealmList<Grade> grades;
private Address address;
private RealmList<Student> friends;

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public RealmList<Grade> getGrades() {
return grades;
}

public void setGrades(RealmList<Grade> grades) {
this.grades = grades;
}

public Address getAddress() {
return address;
}

public void setAddress(Address address) {
this.address = address;
}

public RealmList<Student> getFriends() {
return friends;
}

public void setFriends(RealmList<Student> friends) {
this.friends = friends;
}
}

 

Sem age e com as propriedades "address" e "friends" adicinadas juntamente com seus getters e setters.

Próximo passo incluir a nova classe Address que deve herda de RealmObject. Segue código:

import io.realm.RealmObject;

public class Address extends RealmObject {
private long id;
private String street;
private String neighborhood;
private String city;
private String state;
private String country;

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getStreet() {
return street;
}

public void setStreet(String street) {
this.street = street;
}

public String getNeighborhood() {
return neighborhood;
}

public void setNeighborhood(String neighborhood) {
this.neighborhood = neighborhood;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getState() {
return state;
}

public void setState(String state) {
this.state = state;
}

public String getCountry() {
return country;
}

public void setCountry(String country) {
this.country = country;
}
}

 

Note que dessa vez não foi adicionado o annotation @PrimaryKey, pois como comentado no inicio do post, ha ainda um bug a ser resolvido quando utilizando classes novas com esse annotation, veja a discussão aqui: Primary key not defined for field '_id' in existing Realm file. Add @PrimaryKey.

O próximo passo e atualizar o código da classe MigrationData para que seja possível remover a propriedade "age" da base Realm e também adicionar as propriedades "address" e "friends". Segue código:

import io.realm.Realm;
import io.realm.RealmMigration;
import io.realm.internal.ColumnType;
import io.realm.internal.Table;

public class MigrationData implements RealmMigration {
public static final int VERSION = 2;

@Override
public long execute(Realm realm, long version) {

if( version == VERSION - 1 ){
Table studentTable = realm.getTable(Student.class);
long idKey = studentTable.getColumnIndex("id");
studentTable.addSearchIndex(idKey);

Table disciplineTable = realm.getTable(Discipline.class);
idKey = disciplineTable.getColumnIndex("id");
disciplineTable.addSearchIndex(idKey);

Table gradeTable = realm.getTable(Grade.class);
idKey = gradeTable.getColumnIndex("id");
gradeTable.addSearchIndex(idKey);

/* REMOVE */
idKey = studentTable.getColumnIndex("age");
studentTable.removeColumn(idKey);

Table addressTable = realm.getTable(Address.class);
addressTable.addColumn( ColumnType.INTEGER, "id", false );
idKey = addressTable.getColumnIndex("id");
addressTable.addSearchIndex(idKey);

long addressStreetKey = addressTable.addColumn(ColumnType.STRING, "street", true);
long addressNeighborhoodKey = addressTable.addColumn(ColumnType.STRING, "neighborhood", true);
long addressCityKey = addressTable.addColumn(ColumnType.STRING, "city", true);
long addressStateKey = addressTable.addColumn(ColumnType.STRING, "state", true);
long addressCountryKey = addressTable.addColumn(ColumnType.STRING, "country", true);

long addressKey = studentTable.addColumnLink(ColumnType.LINK, "address", addressTable);
studentTable.addColumnLink(ColumnType.LINK_LIST, "friends", studentTable);
}

return ++version;
}
}

 

O código de remoção é autocomentado, precisamos acessar o indice da coluna "age" na classe / tabela Realm Student e então remover com o indice entrando como parâmetro no método removeColumn.

Note que toda atualização de versão e consequentemente migração de dados ou estrutura da base Realm é necessário acessarmos novamente as classes que têm @PrimaryKey para colocarmos os indexes novamente. Na classe Address somente precisamos fazer isso na criação dela, nas posteriores atualizações na classe MigrationData não repita esse processo para as classes que não têm o annotation @PrimaryKey, será lançada uma Exception, esse também é, provavelmente, um outro bug encontrado.

Depois de instanciada a table addressTable e adicionadas as colunas da classe Address, é somente linkar a tabela studentsTable com os valores de address e de friends utilizando os corretos tipos, valores e tabelas como no código acima. Veja que o VERSION constante foi modificado, para 2 agora.

Antes de executar vamos adicionar outro trecho de código responsável por já incluir um endereço no estudante "Jake" de nossa base Realm de testes. Esse trecho de código virá logo abaixo da linha studentTable.addColumnLink(ColumnType.LINK_LIST, "friends", studentTable);

Segue trecho de código de inserção de endereço em estudante:

...
for( int i = 0; i < studentTable.size(); i++ ){

if( studentTable.getString( studentTable.getColumnIndex("name"), i ).equalsIgnoreCase("jake") ){
long row = addressTable.addEmptyRow();

addressTable.setLong( idKey, row, studentTable.getLong( studentTable.getColumnIndex("id"), i ) );
addressTable.setString( addressStreetKey, row, "Rua 1");
addressTable.setString( addressNeighborhoodKey, row, "Bairro 1");
addressTable.setString( addressCityKey, row, "Cidade 1");
addressTable.setString( addressStateKey, row, "Estado 1");
addressTable.setString( addressCountryKey, row, "País 1" );

studentTable.getUncheckedRow(i).setLink(addressKey, row);
}
}
...

 

Primeiro observe que no código completo da classe MigrationData, o último apresentado, nós sempre estamos obtendo como retorno um long, esse long é o indice da coluna (e não do dado inserido) na classe / tabela Realm. Foi colocado dessa forma justamente para serem utilizados no código de adição de endereço acima.

O condicional do código acima certifica que estamos realmente acessando o estudante correto, no caso, o estudante "Jake". Utilizamos um getString() que precisa do indice da coluna e então o indice da linha para podermos, no caso, retornar o nome do aluno e comparar se é ou não "Jake".

Sendo "Jake" obtemos uma linha vazia da tabela address, na verdade o indice dessa linha vazia criada. Então colocamos dados para as colunas dessa linha vazia, logo depois acessamos a linha do estudante e linkamos a ele o endereço. Note que o id do address adicionado é o mesmo que o id do estudante devido as entidades terem uma composição de 1 para 1, porém se quiser manter um id independente para o Address, pode manter sem problemas, foi o que fizemos das aulas anteriores com a classe Grade. Segue printscreen da base Realm em RealmBrowser com a nova configuração na classe Student e a classe Address já adicionada ao schema:

Outro ponto negativo da library Realm, ela ainda não está com o código completamente comentado, digo, os blocos de comentário de inicio dos métodos, explicando os parâmetros e o que o método faz, pois pressionando Ctrl e clicando no método setLink() aapresentado no código acima, nada de bloco de comentário.

Enfim, com isso terminamos o código de migração após mudança de versão na base Realm. Para treino, tente colocar um novo campo em Discipline e então continuar com as novas entidades e configurações sem problemas. Até mesmo adicione as views de formulário necessárias para manter a nova configuração das classes que você setou.

Para acesso completo ao código do projeto vá ao GitHub: https://github.com/viniciusthiengo/realm-students

Se está com sistema Mac segue link do Realm Browser: Realm Browser AppStore

Segue vídeo de implamentação da parte 6 da série:

 

Vlw

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes!
Email inválido

Relacionado

Persistência de Dados Com Realm no Android - Parte 2Persistência de Dados Com Realm no Android - Parte 2Android
Persistência de Dados Com Realm no Android - Parte 3Persistência de Dados Com Realm no Android - Parte 3Android
Persistência de Dados Com Realm no Android - Parte 4Persistência de Dados Com Realm no Android - Parte 4Android
Persistência de Dados Com Realm no Android - Parte 5Persistência de Dados Com Realm no Android - Parte 5Android

Compartilhar

Comentários Facebook

Comentários Blog

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...