Disparando eventos customizados no Android

Na plataforma Android, o uso de Fragments para flexibilizar o desenvolvimento é uma prática cada vez mais difundida, em especial para aplicações que precisam rodar em tablets. Através dos Fragments é possível isolar o comportamento de pequenos trechos da tela, ajudando a reduzir a quantidade de código nas activities e permitindo maior flexibilidade no momento de fazer a mesma aplicação rodar em Smartphones e Tablets.

Uma questão relevante nesse cenário é: O que devemos fazer quando um evento em um Fragment impacta em outros Fragments?

Suponha uma tela de um Tablet no qual existam 3 Fragments. À esquerda uma listagem de posts de um blog, no canto superior direito um Fragment que mostra os detalhes do post selecionado, no canto inferior direito para a criação de uma resposta para o post selecionado da listagem:

tela_3_fragmentsTela da aplicação com 3 fragments

Quando clicamos em um item do ListView precisamos passar esse item para os demais Fragments, podemos fazer isso diretamente no Fragment que apresenta a listagem na tela:

getListView().setOnItemClickListener(new OnItemClickListener() {
	public void onItemClick(AdapterView<?> adapter, View view, int posicao,	long id) {
		BlogPost postSelecionado = 
			(BlogPost) adapter.getItemAtPosition(posicao);
		
		FragmentManager manager = getFragmentManager();
		PostSelecionadoFragment telaDetalhesPost = (PostSelecionadoFragment)
			manager.findFragmentById(R.id.direita_superior);
				
		telaDetalhesPost.lidaCom(postSelecionado);
		
		NovoPostFragment telaNovoPost = (NovoPostFragment) 
			manager.findFragmentById(R.id.direita_inferior);
				
		telaNovoPost.lidaCom(postSelecionado);
		
		PostsActivity activity = (PostsActivity) getActivity();
		activity.lidaComSelecao(postSelecionado);
	}
});

Mas será que é responsabilidade de um Fragment conhecer os demais? Podemos delegar a responsabilidade de lidar com a tela como um todo para a Activity que já a está gerenciando , isolando todo o código acima:

getListView().setOnItemClickListener(new OnItemClickListener() {
	public void onItemClick(AdapterView<?> adapter, View view, int posicao,	long id) {
		BlogPost postSelecionado = 
			(BlogPost) adapter.getItemAtPosition(posicao);
		
		PostsActivity activity = (PostsActivity) getActivity();
		activity.lidaComSelecao(postSelecionado);
	}
});

O problema é que acoplamos nosso Fragment de listagem com uma Activity em específico, dificultando seu reaproveitamento. Se resolvermos dar suporte a smartphones, inclusive de tamanho de tela pequena, precisaremos quebrar essa tela em duas ou até três, tornando o método da Activity que lida com a seleção cada vez mais complexo.

Uma maneira de reduzir o acoplamento é trabalhar com mensagens, de maneira semelhante à que os desenvolvedores no iOS fazem com o NSNotificationCenter.

Na seleção de um item, podemos usar o LocalBroadcastManager para disparar uma mensagem de broadcast que não sairá dos limites de nossa aplicação (ajudando nos quesitos segurança e performance).

getListView().setOnItemClickListener(new OnItemClickListener() {
	public void onItemClick(AdapterView<?> adapter, View view, int posicao,	long id) {
		BlogPost postSelecionado = 
			(BlogPost) adapter.getItemAtPosition(posicao);
		
		Intent intent = new Intent("post-foi-selecionado");

		intent.putExtra("blogpost", post);
		LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
	}
});

Agora podemos registrar os Fragments interessados nesse evento:

public class BlogPostSelecionado extends BroadcastReceiver {
	private  FragmentObservador observador;

	public static BlogPostSelecionado registraParaLidarComASelecao(FragmentObservador o) {
		BlogPostSelecionado receiver = new BlogPostSelecionado();
		receiver.observador = o;
		
		LocalBroadcastManager.getInstance(o.getContext())
			.registerReceiver(receiver, new IntentFilter("post-foi-selecionado"));

		return receiver;
	}
}

Agora os Fragments interessados em nosso evento apenas precisam implementar a interface FragmentObservador:

public interface FragmentObservador {
	Context getContext();
	void lidaCom(Post post);
}

Dessa forma invertemos a responsabilidade de propagar o evento para a plataforma, reduzindo o acoplamento e facilitando a manutenção.

Querendo aprender desenvolvimento Android? Conheça o nosso curso!

15 Comentários

  1. Fabio Costa Jr 23/04/2013 at 15:36 #

    obrigado pelas dicas! é realmente um excelente blog para todo o tipo de desenvolvedor.

  2. Anderson André 26/04/2013 at 09:33 #

    Olá Erich Egert ! parabéns pelo post….

    Sempre que utilizo muitos fragments, e esses precisam de informações uns dos outros, eu centralizo na Activity que os controla, mais sempre achei que o codigo fica altamente acoplado, está idéia de mensagens foi uma sacada interessante, teria como você disponibilizar um exemplo mais pratico, eu não conseguir implementrar a idéia a classe de BroadCast ficou meio nebulosa pra mim..rsrsr

  3. Erich Egert 26/04/2013 at 20:14 #

    Olá Anderson!! Muito obrigado!
    Realmente passar uma idéia tão complexa com um post tão pequeno é meio desafiador!
    Dê uma olhada nesse Gist: https://gist.github.com/erichegt/5471041 para um exemplo mais prático!

  4. Anderson André 29/04/2013 at 12:39 #

    Olá novamente Erich Egert !

    Primeiro, obrigado pela atenção, sua dica veio em uma ora oportuna, estou padronizando algumas implementações, para trabalhar em equipe, e a API fragment e muito utilizada, já fiz um refactory em projeto pessoal utilizando essa idéia, e realmente, um código mas coeso e fácil de migrar…

    novamente parabens…

  5. Eder Clei de Freitas 07/05/2013 at 05:01 #

    Muito bom o Post Erich,

    uma otima forma de não deixar o fragment acoplado a nenhuma activity especifica e também deixar um codigo mais facil de se entender…
    Parabens !

  6. Elcon Costa 07/05/2013 at 11:55 #

    Olá Erich Egert, parabéns pelo seu post.
    Rapa eu estava procurando uma forma mais fácil de fazer com que minhas fragments se atualizarem, pois eles ficavam ligados uns ao outros, hehehe. Mais com esse seu post ficou muito mais fácil e pratico.

  7. Erich Egert 07/05/2013 at 14:07 #

    Opa Elson, muito obrigado! Espero que ajude nos próximos projetos!

  8. Filipe 07/05/2013 at 23:55 #

    Parabéns pelo post.

    Estou desenvolvendo um Guia de TV para Tablet e estou tendo um problema de performance utilizando fragment. Não sei se há realmente a necessidade de se usar os Fragments ou se é melhor utilizar apenas uma Activity comum e ainda assim manter a ActionBar que a aplicação possui.

    O que acha? A aplicação possui uma lista de canais e uma lista de programas conectadas.

  9. Elcon Costa 22/05/2013 at 16:16 #

    Olá Erich.

    Estou fazendo os testes e gostaria de saber se tenho que resitrar a classe que extends BroadcastReceiver, no manisfest?. Alias eu já registrei ela no manifest mais não obtive resultado. será que você poderia nos dar um projeto exemplo. hehehe

  10. Elcon Costa 23/05/2013 at 16:27 #

    Olá Erich,

    Fiz um projeto de teste, se você poder olhar para saber se é essa a forma que você quis passar no teu post.
    Há no meu projeto tem um bug. Porque para funcionar LocalBroadcastManager ele precisa ser registro na fragment que vai receber a mensagem, mais essa fragment ainda não foi iniciada. como faço.

    https://github.com/elsoncosta/LocalBroadcastManager

  11. Erich Egert 28/05/2013 at 14:20 #

    Olá Elson!

    Olhei o projeto de teste e parabéns, você conseguiu usar o LocalBroadcastManager com código da maneira que foi proposta pelo post! O único detalhe é a finalidade do uso. No caso do post eu propus o uso quando não sabemos exatamente como está a tela: se existe um ou mais Fragments interessados no resultado de uma operação, especialmente quando essa operação é executada em paralelo (como a AsyncTask). No caso de você saber exatamente se um Fragment deve ou não estar na tela essa tarefa deve ser executada usando os meios tradicionais mesmo!

  12. Erich Egert 28/05/2013 at 14:23 #

    Olá Elson!

    O registro do BroadcastReceiver no Manifest faz mais sentido quando o Receiver estiver interessado em receber eventos fora da aplicação (chegada de SMS, bateria acabando…). Quando criamos um LocalBroadcastReceiver faz mas sentido instanciar dinamicamente na aplicação, registrá-lo e desregistrá-lo quando necessário!

  13. Erich Egert 28/05/2013 at 14:27 #

    Olá Filipe, muito obrigado, espero que o post seja útil.

    Humm estranho o problema de performance. Qual tarefa os fragments estão executando? Vou lhe enviar um email para que possa descrever o problema com mais detalhes para mim!

  14. Elcon Costa 30/05/2013 at 23:07 #

    Olá Erich Engert, Valeu velho pelas discas e por sua atenção muito obrigado.

  15. Iomar 03/07/2014 at 16:55 #

    porque quando disparo um broadcast do onCreate não chega no destino?

Deixe uma resposta