BlackBerry API e RSS Parser Resumo Na criação de aplicativos para dispositivos móveis, na grande maioria dos casos, teremos acesso a alguns recursos externos, sejam eles um arquivo XML, uma fonte de feeds RSS ou um JSON. Até as versões anteriores a 6.0 do BlackBerry OS, os desenvolvedores de aplicativos para a plataforma da canadense RIM tinham que fazer o código de leitura destas fontes externas totalmente via código, ou seja, até mesmo para um feed RSS, era preciso criar um parser que leria cada tag do documento e faria os tratamentos necessários. Porém, da versão 6.0 em diante, o trabalho do desenvolvedor sofre uma mudança radical, e para melhor. Isso acontece porque a BlackBerry API já fornece classes auxiliares que fazem todo o trabalho pesado do parser de um feed RSS para nós programadores. Neste artigo é exatamente isso que veremos. A diferença na forma pré 6.0 e na pós 6.0 para criar um parser RSS.
A Aplicação Para testarmos as teorias apresentadas neste artigo/tutorial, vamos criar um aplicativo muito simples, ele mostrará os 10 últimos posts do blog www.mobilidadetudo.com, nada mais. Além disso, não apresentará uma interface gráfica produzida, contendo apenas os componentes padrões da BlackBerry API. Veja a Figura 1. Logo ao iniciar o aplicativo, o usuário verá o texto “Lendo feeds, aguarde!”. Depois que nosso código busca as informações corretamente, os posts são colocados na tela do aplicativo. Sendo que, criamos a instância de LabelField para mostrar o título, a data de publicação (sem nenhum tratamento) e a descrição. Além disso, coloca-se um botão “Ler”. Quando o usuário clica neste botão é direcionado para o browser nativo do aparelho, visualizando aquele post no próprio blog (www.mobilidadetudo.com).
Figura 1: Aplicativo sendo testado em um BlackBerry Torch.
Codificando a classe UiApplication e MainScreen Em um aplicativo BlackBerry API, obrigatoriamente, devemos ter uma classe que herda de uma destas classes: Application ou UiApplication. A única diferença entre as duas está no fato de que a segunda opção é destinada a aplicações com interface de usuário. Logo, aplica-se ao nosso caso. Listagem 1: Classe ReadingRSS public class ReadingRSS extends UiApplication { public ReadingRSS() { pushScreen(new Feeds()); } public static void main(String[] args) { ReadingRSS reading = new ReadingRSS(); reading.enterEventDispatcher(); }
}
O método main é o ponto de entrada da aplicação, algo como o startApp no Java ME, a Activity configurada no config XML do Android, ou ainda, o próprio método main dentro de uma aplicação Java SE. No main estamos criando uma instância da própria classe e invocando o método enterEventDispatcher(). Este método faz com que a thread que a chamou, seja responsável pelo desenho e tratamento dos eventos dentro da aplicação. No construtor estamos jogando uma instância da única tela da aplicação no display do device. A BlackBerry API utiliza um conceito de pilha de telas, por isso a presença do método pushScreen e, como é de imaginar, também temos um método popScreen disponível. Caso o leitor não conheça bem este conceito, indico uma visita a documentação da RIM para sanar este problema. Veremos na Listagem 2 a classe Feeds: Listagem 2: Classe Feeds public class Feeds extends MainScreen { private Vector feeds; public Feeds(){ add(new LabelField("Lendo Feeds, aguarde!")); try {
feeds = ParserOperations.getInstance().getFeeds(); preencheTela();
} catch (Exception e) { add(new LabelField(e.toString())); } } public void preencheTela(){ … }
}
public static void showURL(String url){ … }
No construtor, fica claro que o primeiro passo é inserir a mensagem de texto para o usuário, informando que os feeds estão sendo capturados e que logo os verá na tela do aparelho. Posteriormente, o trabalho de busca é enviado a classe ParserOperations. A mesma retorna uma instância da classe Vector assim que o trabalho é feito. Com os dados em mãos, podemos chamar o método preencheTela e apresentar o conteúdo ao usuário.
Mas vamos por partes.
Codificando a busca, parser dos feeds (Pré-6.0) e mostrando os resultados na tela. Antes de continuarmos com a construção da tela de nosso aplicativo, vamos criar a classe e os métodos responsáveis por retornar os feeds diretamente do site Mobilidade é Tudo. Neste primeiro momento iremos trabalhar com as possibilidades oferecidas pelas versões do sistema operacional do BlackBerry antes da 6.0. Ou seja, da maneira mais difícil. 0:import … 1: 2:public class ParserOperations { 3: 4: public static Vector getFeeds() { 5: InputStream is = null; 6: HttpConnection conn = null; 7: 8: try { 9: conn =(HttpConnection) Connector.open("http://www.mobilidadetudo.com/feed;interface=wifi"); 10: 11: KXmlParser parser = new KXmlParser(); 12: parser.setInput(new InputStreamReader(conn.openInputStream(), "utf-8")); 13: parser.nextTag(); 14: 15: parser.require(XmlPullParser.START_TAG, null, "rss"); 16: parser.nextTag(); 17: parser.require(XmlPullParser.START_TAG, null, "channel"); 18: 19: Vector output = new Vector(); 20: 21: while (parser.nextTag() == XmlPullParser.START_TAG) { 22: Post post = new Post(); 23: 24: if (parser.getName().equals("item")) { 25: while (parser.nextTag() == XmlPullParser.START_TAG) { 26: if (parser.getName().equals("title")) { 27: post.setTitle(parser.nextText()); 28: }else if (parser.getName().equals("link")) { 29: post.setLink(parser.nextText()); 30: }else if (parser.getName().equals("pubDate")) { 31: post.setPubDate(parser.nextText()); 32: }else if (parser.getName().equals("description")) { 33: post.setDescription(parser.nextText()); 34: output.addElement(post); 35: post = new Post(); 36: } else { 37: parser.nextText(); 38: } 39: } 40: 41: parser.require(XmlPullParser.END_TAG, null, "item");
42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: } 66:}
} else { parser.nextText(); } } return output; } catch (XmlPullParserException e) { return null; } catch (IOException e) { return null; } finally { try { if (is != null) { is.close(); is = null; } if (conn != null) { conn.close(); conn = null; } } catch (Exception e) { } }
A classe responsável pela tarefa de recuperar os dados do feed e, posteriormente, efetuar o parser das informações, será a classe ParserOperations. Esta, por sua vez, possui um método estático chamado getFeeds(). Nas linhas 5 e 6 cria-se os objetos que serão responsáveis pela conexão com a fonte de dados externa e pelo stream dos dados, respectivamente. Na linha 9 utilizamos o método open da classe Connector para criar uma conexão com a fonte externa, na mesma linha efetuamos um cast para forçar essa comunicação a ser tratada dentro dos padrões HTTP. Perceba que estamos jogando um parâmetro para esta string de URL. O parâmetro é interface=wifi. Esse valor indica que a comunicação externa será Tt feita através de um hotspot wi-fi. Porém, em um aplicativo real, deve-se testar qual a forma de conexão disponível no momento (wi-fi, operadora, bis, bes, mds, etc) e configurar com o parâmetro adequado. Saiba mais visitando a documentação oficial da RIM. Uma boa leitura inicial está aqui: http://devmobile.blog.br/2008/03/29/conexoes-http-e-socket-com-bis-beswap-wi-fi-e-tcp-apn-em-aplicacoes-blackberry/.
A partir da linha 21 que o trabalho começa ficar divertido. O RSS nada mais é do um documento XML. Sendo assim, estamos utilizando a API kXML2. Para saber mais, visite a página oficial aqui: http://kxml.sourceforge.net/kxml2/. Baixe a API no mesmo site e ]aa configure-se no classpath de seu projeto.
Na linha 11, cria-se a instância de KXmlParser. Caso não tenha lido o quadro de texto acima, peço que o leia. Na linha 12 informamos qual será o input de dados deste parser, além de, configurar o tipo de codificação Unicode. O trecho de código da linha 13 até a linha 46 navega pelo documento XML e faz os tratamentos necessários. A parte mais importante está dentro do segundo while. Neste laço, criamos vários testes IF para ver se a tag atual é uma daquelas que iremos utilizar em nosso aplicativo. Caso afirmativo, recuperamos o texto desta tag e configuramos o parâmetro de mesmo nome na classe Post, instanciado na linha 22. Infelizmente a documentação da API kXML é deficiente, sendo impossível fornecer uma descrição detalhada de cada método. Veja o código da classe Post: public class Post { private private private private
String String String String
title; link; pubDate; description;
public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getLink() { return link; } public void setLink(String link) { this.link = link; } public String getPubDate() { return pubDate; } public void setPubDate(String pubDate) { this.pubDate = pubDate; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
Basicamente a classe é um JavaBean, com os métodos getter´s e setter´s para cada propriedade. Voltando a classe ParserOperations, foque sua atenção na linha 33. Neste momento, estamos lendo a nossa última
propriedade, sendo assim, inserimos a instância da classe Post em um vetor e instanciamos o objeto novamente. Depois dos laços while, na linha 47, retornamos o vetor com instâncias da classe Post. No momento em que o return é chamado, o método que originou esta chamada recebe o controle da thread novamente. Volte sua atenção à classe Feed. Na listagem desta classe podemos ver claramente que depois da chamada ao método getFeeds() existe uma chamada ao método preencheTela(). Anteriormente, deixamos este método de lado, porém, agora é a hora de estudá-lo: 0:public void preencheTela(){ 1: for (int i = 0; i < feeds.size(); i++){ 2: final Post post = (Post)feeds.elementAt(i); 3: 4: add(new LabelField("Título: "+post.getTitle())); 5: add(new LabelField("Publicado em: "+post.getPubDate())); 6: add(new LabelField("Descrição: "+post.getDescription())); 7: add(new ButtonField("Ler"){ 8: protected boolean navigationClick(int status, int time) { 9: showURL(post.getLink()); 10: return super.navigationClick(status, time); 11: } 12: }); 13: add(new SeparatorField()); 14: } 15:}
Logo na linha 1 temos um laço que vai do primeiro elemento do vetor, até o último. Como temos certeza que cada item da instância de Vector é uma instância de Post, forçamos um cast para esta classe na linha 2. Com a instância em mãos, criamos instâncias de LabelField nas linhas 4, 5, e 6. Para as informações de título, data de publicação e descrição, respectivamente. Já para a informação do endereço web do post, jogamos dentro da ação de um ButtonField criado na linha 7. Ou seja, quando o usuário clicar no botão, o navigationClick será chamado, sendo assim, invocaremos o método showURL(). Finalmente, colocamos um SeparatorField (linha 13) entre cada feed. Veja abaixo o código do método showURL: public static void showURL(String url){ BrowserSession bs = Browser.getDefaultSession(); bs.showBrowser(); bs.displayPage(url); }
Para saber mais sobre chamadas a aplicação nativa de Browser, leia o artigo/tutorial “Trabalhando com Browser em Java ME, Android e BlackBerry”. O mesmo está disponível na seguinte URL: http://issuu.com/ricardoogliari/docs/broswer_android_e_blackberry.
Codificando a busca, parser dos feeds (6.0 e superior) e mostrando os resultados na tela. Agora que conhecemos a forma mais complexa de fazer o parser e o tratamento dos dados, vamos conhecer a maneira BlackBerry OS 6.0 e posteriores. Primeiramente, veja as mudanças no getFeeds() da classe ParserOperation. 1:public static Vector getFeeds() { 2: InputStream is = null; 3: HttpConnection conn = null; 4: 5: try { 6: conn = (HttpConnection) Connector.open("http://www.mobilidadetudo.com/feed;interface=wifi"); 7: is = conn.openDataInputStream(); 8: 9: RSSParser parser = new RSSParser(); 10: RSSChannel channel = parser.parseRSS(is, 10); 11: Vector vetItens = channel.getRSSItems(); 12: 13: is.close(); 14: conn.close(); 15: 16: return vetItens; 17: } catch (SAXException e) { 18: return null; 19: } catch (ParserConfigurationException e) { 20: return null; 21: } catch (IOException e) { 22: return null; 23: } finally { 24: try { 25: if (is != null) { 26: is.close(); 27: is = null; 28: } 29: if (conn != null) { 30: conn.close(); 31: conn = null; 32: } 33: } catch (Exception e) { 34: } 35: } 36:}
As mudanças começam a acontecer na linha 7, onde recuperamos uma instância de InputStream da classe HttpConnection.
Na linha 9 a mágica começa. Lembram-se da forma como o parser foi feito antes, com aquelas dezenas de linhas e vários testes IF? Preste bem atenção, porque agora faremos tudo aquilo em simplesmente 3 linhas. A linha 9 cria a instância de RSSParser. Na linha 10 indicamos à instância criada anteriormente de onde os dados serão lidos, ou seja, do InputStream e, a quantidade de feeds que desejamos ler. No nosso caso configuramos este valor com o inteiro 10. Este método retorna uma instância da classe RSSChannel. Esta classe contém os métodos no formato javabean para recuperar as informações daquele RSS. Para recuperar cada um dos feeds, basta chamar o método getRSSItems da classe RSSChannel. Pronto, teremos uma instância de Vector com os posts do blog. Agora basta mudar a forma como trataremos este retorno de dados.
Para saber mais sobre a classe RSSChannel, visite a página http://www.blackberry.com/developers/docs/7.0.0api/net/rim/device/ api/io/parser/rss/model/RSSChannel.html. Para saber mais sobre a página RSSParser, visite a página http://www.blackberry.com/developers/docs/7.0.0api/net/rim/device/ api/io/parser/rss/RSSParser.html.
Veja como fica o método preencheTela() da classe Feeds: 1:public void preencheTela(){ 2: for (int i = 0; i < feeds.size(); i++){ 3: final RSSItem post = (RSSItem)feeds.elementAt(i); 4: 5: add(new LabelField("Título: "+post.getTitle())); 6: add(new LabelField("Publicado em: "+post.getPubDate())); 7: add(new LabelField("Descrição: "+post.getDescription())); 8: 9: add(new ButtonField("Ler"){ 10: protected boolean navigationClick(int status, int time) { 11: Hashtable hash = post.getMediaContents(); 12: RSSMediaContent mediaContent = (RSSMediaContent)hash.get("link"); 13: showURL(mediaContent.getLink()); 14: return super.navigationClick(status, time); 15: } 16: }); 17: 18: add(new SeparatorField()); 19: } 20:}
O vetor de dados que recuperamos na ParserOperations, contém instâncias da classe RSSItem. Logo, na linha 3, mudamos nosso cast para esta classe. As linhas 5, 6, e 7 criamos instâncias de LabelField. Porém, recuperando as informações dos próprios métodos getters da própria classe RSSItem, e não da classe Post. Uma mudança significativa ocorre na busca da URL deste post. Anteriormente já tínhamos feito o tratamento no próprio parser. Aqui, devemos recuperar uma instância de Hashtable através do método getMediaContents(). Como o próprio nome indica, todas as propriedades relacionadas à mídia estão nesta tabela de hash. Próximo passo, na linha 12, é buscar o hash identificado pela chave link. O retorno será a instância da classe RSSMediaContent. Sendo que, esta classe tem um método chamado getLink. O resto do processo continua igual ao anterior.
Conclusão O pequeno aplicativo de exemplo que construímos aqui não é muito útil, não pode ser transformado em produto, porém, nos mostrou uma importante lição: a diferença entre a API da versão pré e pós 6.0 para fazer parser de uma fonte RSS. Antes da versão 6.0 tínhamos que criar um código que navegasse por todo o arquivo XML e então tratasse os dados da maneira desejável. Por outro lado, as classes RSSParser, RSSChannel e RSSItem tornam o trabalho do desenvolvedor extremamente simples.
Sobre Mim Graduado em Ciência da Computação, pós-graduado em Web e analista de sistemas mobile na MobMidia | Grupo Pontomobi, Ricardo da Silva Ogliari é autor de dezenas de artigos que foram publicados em anais de congressos nacionais e internacionais, sites especializados e revistas. Palestrante em eventos nacionais e internacionais, como JustJava, Java Day, GeoLivre, ExpoGPS, FISL e FITE, sempre aborda temas relacionados a computação móvel.