segunda-feira, 11 de janeiro de 2010

Android: Passando objetos em Intents

Olá povo,

Algumas pessoas me perguntaram como passar objetos entre telas no Android ao invés de passar apenas primitivos. Um exemplo típico é uma tela que lista algum tipo de informação e quando algum item dessa lista é selecionado, o objeto da classe referente aquela informação deveria ser passado para uma tela de detalhes do mesmo.

Para resolver isso, eu passava para a tela de detalhamento apenas um identificador do objeto (um código, por exemplo) e lá eu fazia uma consulta ao controlador que me retornava o objeto desejado.

Essa é uma abordagem boa, e até aconselho. Mas quem quiser mesmo passar obejtos entre telas podemos utilizar a interface android.os.Parcelable. A classe que implementar essa interface pode ser adicionada a uma Intent.

Vamos fazer um pequeno exemplo aqui criando uma classe Cliente com dois atributos (código e nome) e ela implementará a interface Parcelable.
import android.os.Parcel;
import android.os.Parcelable;

public class Cliente implements Parcelable {
 private int codigo;
 private String nome;

 public Cliente(int codigo, String nome) {
   this.codigo = codigo;
   this.nome = nome;
 }

 private Cliente(Parcel p){
   codigo = from.readInt();
   nome = from.readString();
 }

 public static final Parcelable.Creator<Cliente>
   CREATOR = new Parcelable.Creator<Cliente>() {

   public Cliente createFromParcel(Parcel in) {
     return new Cliente(in);
   }

   public Cliente[] newArray(int size) {
     return new Cliente[size];
   }
 };

 public int getCodigo() {
   return codigo;
 }

 public void setCodigo(int codigo) {
   this.codigo = codigo;
 }

 public String getNome() {
   return nome;
 }

 public void setNome(String nome) {
   this.nome = nome;
 }

 @Override
 public int describeContents() {
   return 0;
 }

 @Override
 public void writeToParcel(Parcel dest, int flags) {
   dest.writeInt(codigo);
   dest.writeString(nome); 
 }
}
Vejam que a classe Cliente implementa a interface Parcelable que tem dois métodos que devem ser implementados: writeToParcel e describeContents. O primeiro método serve para serializar as informações da classe, já o segundo é um inteiro qualquer que identifica a classe :)

A segunda coisa importante a destacar é o atributo estático CREATOR que é do tipo Parcelable.Creator. Segundo a própria documentação, todas as classes que desejem implementar Parcelable devem ter essa variável. É ela que cria os objetos a partir de um Parcel, classe que a grosso modo, junta as funcionalidades de um DataInputStream e DataOutputStream para serializar e deserializar objetos. Veja que ela chama o construtor privado de Cliente que recebe um Parcel que nos permite ler dados dele e passando para os atributos.
Agora vamos ver como passar nosso objeto via Intent:
Cliente cliente = new Cliente(1, "Glauber");

Intent it = new Intent(this, Teste2Activity.class);
   
it.putExtra("cliente", cliente);
   
startActivity(it);
E para ler o conteúdo é tão simples quando enviar:
Cliente c = 
  getIntent().getExtras().getParcelable("cliente");

EDITADO em 08/08/2012:
Achei uma maneira muuuito mais simples (porém mais lenta em tempo de execução) de fazer a passagem de objetos entre Activities. Basta fazer com que eles implementem a interface java.io.Serializable.
public class Pessoa implements Serializable {
  // Seus atributos, gets e sets ...
}
Dessa forma você pode passar os objetos da classe Pessoa para outras activities via Intent normalmente, inclusive adicionando-os a listas (a classe ArrayList é serializada).
 
ArrayList<Pessoa> pessoas = new ArrayList<Pessoa>();
pessoas.add(new Pessoa(1, "Glauber"));
pessoas.add(new Pessoa(2, "Nelson"));
        
Intent it = new Intent(this, Tela2Activity.class);
// passando a lista
it.putExtra("pessoas", pessoas); 
// passando um objeto
it.putExtra("pessoa", pessoas.get(0)); 
startActivity(it);


E pra recuperar:
Pessoa pessoa = 
  (Pessoa) getIntent().getSerializableExtra("pessoa");

ArrayList<Pessoa> pessoas = (ArrayList<Pessoa>) 
  getIntent().getSerializableExtra("pessoas");
  
System.out.println("Pessoa: "+ pessoa.getNome());
System.out.println(
  "Pessoas: "+ pessoas.get(0).getNome());

4br4ç05,
nglauber

14 comentários:

Opeste disse...

Opa Glauber post bastante interessante. A minha dúvida é a seguinte, com relação a peformace qual seria a melhor solução, armazenar a referência e no controlador buscar ela via base de dados ou passando o objeto em intent ?

Nelson Glauber de Vasconcelos Leal disse...

Olá Opeste,

Como disse no início do post, prefiro a abordagem de um Controlador implementado usando o padrão Singleton, servindo assim, ambas as telas que necessitam do objeto desejado. Dessa forma, a partir de uma tela eu só precisaria passar um identificador para a outra tela, e buscaria o objeto no contralor a partir desse ID.

Júnior Pires disse...

Ajudou muito, valew!

ribeiro disse...

Valeu!
Muito bons os seus posts!
me ajudaram bastante!

Thomaz disse...

Rápido e fácil.

Pra quem teve problema em serializar um objeto que continha um bitmap dentro dele, Bitmap não é serializable.. perdi mó tempão com isso... =|

willian disse...

Olá, sei que o post é antiguinho, mas estou precisando usar uma arraylist dentro do parcelable. Isso é possível?

Nelson Glauber de Vasconcelos Leal disse...

Oi William,

Aqui tem a solução:
http://stackoverflow.com/questions/6300608/how-to-pass-a-parcelable-object-that-contains-a-list-of-objects

Basicamente você pode usar o método writeList e readList da classe Parcel, desde que os objetos que estejam na lista implementem Parceable.

4br4ç05,
nglauber

AJ disse...

Já sei que é um post antigo mas gostava de perguntar que tratamento poderíamos dar a um array multidimensional([][]) neste contexto? Já implementei com Serializable em classe e passando no intent, mas a actividade que faz o get ou que recebe o intent fica com as cálulas do array a null.

Nelson Glauber de Vasconcelos Leal disse...

Oi AJ,

Nesse caso, seria melhor usar o Parceable mesmo.

4br4ç05,
nglauber

Anônimo disse...

Primeiro, parabens o post esta excelente.

So tenho um problema, fiz todos os passos, mas ainda nao consigo passar minha Lista pelo intent.

Alguma dica?

Obrigado

Nelson Glauber de Vasconcelos Leal disse...

Oi Anônimo,

Passa uma ArrayList de Parceable.
http://developer.android.com/reference/android/content/Intent.html#putParcelableArrayListExtra(java.lang.String, java.util.ArrayList)

4br4ç05,
nglauber

Diego Silva disse...

Funciona para dados abaixo de 1MB.
Porém consegue resolver com dados acima de 1MB utilizando a classe já explicada aqui chamada Bundle \o/.

Posso compartilhar aqui professor?

Nelson Glauber disse...

Oi Diego,

Pode postar sim por curiosidade, mas não é recomendado passar uma quantidade tão grande de dados de uma tela para outra....
Melhor passar apenas algo que identifique essa informação (um ID ou um path de um arquivo por exemplo) e carrega-la novamente na outra tela.

4br4ç05,
nglauber

Hildebrando Pedro disse...

Obrigado, Muito bom o poste me ajudou muito.