Desenvolvimento

25 jan, 2010

Criando um leitor de feeds com Adobe Air

Publicidade

Olá, pessoal!

Nesse meu primeiro artigo aqui no iMasters, irei abordar um assunto que considero bastante interessante e que a Adobe teve um cuidado todo especial quando projetou essa atualização no SDK 1.5 do AIR, que foi a API para uma auto-atualização de sua aplicação.

Hoje é possível que você configure sua  aplicação para que ela procure no servidor se existe uma atualização disponível e, caso exista, se você quer fazer o download e atualizar sua aplicação. Ainda é possível informar ao usuário do sistema quais seriam os motivos para a atualização da aplicação. Tudo isso através de poucas linhas de codificação e apenas um arquivo XML em seu servidor.

Para ilustrar essa situação, vamos criar  uma aplicação que  irá  ler um  feed aqui do iMasters. A primeira versão da aplicação será responsável por ler apenas um dos feeds, e a sua atualização poderá ter mais opções de leitura.

Vou iniciar partindo do  princípio que você já saiba criar um projeto no FlexBuilder para AIR. Essa aplicação tem alguns detalhes interessantes. O primeiro deles é que ela terá seu background transparente, ou seja, você tem a impressão de que a aplicação está rodando como um gadget, outra grande inovação que a Adobe trouxe para o AIR.

Eu criei um projeto chamado AIRAutoUpdate, e o FlexBuilder criou a estrutura abaixo:

Bom, para quem não conhece a estrutura do AIR, o arquivo AIRAutoUpdate-app.xml é responsável por definir diversas informações ao compilador.  O FlexBuilder adiciona um arquivo bem extenso quando você cria um projeto, iremos substituir o arquivo que ele criou por uma versão mais simples, contendo apenas aquilo que for relevante para esse tutorial, conforme informado abaixo:

<?xml version="1.0" encoding="UTF-8"?> 
<application xmlns="http://ns.adobe.com/air/application/1.5">
<id>AIRAutoUpdate</id>
<version>1.0</version>
<filename>AIRAutoUpdate</filename>
<name>AIRAutoUpdate - Tutorial IMasters</name>
<installFolder>Imasters/AIR -
Tutoriais/AIRAutoUpdate</installFolder>
<programMenuFolder>Imasters/AIR -
Tutoriais/AIRAutoUpdate</programMenuFolder>




<description>Um pequeno exemplo de aplicação que faz sua
atualização automática</description>
<copyright>Stefan Horochovec
(stefan@horochovec.com.br)</copyright>
<initialWindow>
<title>AIRAutoUpdate</title>
<content>AIRAutoUpdate.swf</content>
<systemChrome>none</systemChrome>
<transparent>true</transparent>
<visible>true</visible>
<minimizable>true</minimizable>
<maximizable>false</maximizable>
<resizable>false</resizable>
</initialWindow>
</application>

Este artigo não tem como objetivo descrever todas as informações que foram atribuídas aoXML, porém, algumas ressalvas serão feitas aos atributos que são base para este artigo.

  • <version></version> – Utilizado para definir qual a versão da aplicação atualmente, é usado para fazer verificações de versão para atualização.
  • <systemChrome>none</systemChrome> – Definição do tipo do System Chrome, essa opção tem valor default “standard”, porém,
    para criar uma aplicação com fundo transparente, deve-se atribuir o valor “none”.
  • <transparent>true</transparent> – Definição se a janela deverá ser transparente, é apenas aplicável quando systemChrome for do tipo none.
  • <visible>true</visible> – Define se a janela deverá se iniciar como  visível, como o valor default é false, passamos para true.

Assim, o compilador irá receber as informações necessárias a nível de definição da aplicação para que o seu background fique transparente. 

Feito isso, podemos partir para o desenvolvimento da aplicação em si. Eu criei um componente chamado Posts.mxml, ele encontra-se dentro da package:  br.com.horochovec.view.Posts. A idéia desse componente é que ele seja responsável por consumir o  Feed sobre Flex aqui do
site iMasters, passe algumas informações para um Grid e, assim, o usuário possa listar os posts do site e obter mais informações sobre ele.

Esse componente estende de uma TitleWindow, dentro dele há um VBox, AdvancedDataGrid, e um Canvas, que contém dentro dele um Label, um  TextArea e um LinkButton.

O componente VBox é o container que agrupará todos os outros componentes visuais desse componente. 

Ainda no componente Posts.mxml, eu possuo um HTTPService. Esse HTTPService é responsável por consumir o Feed do iMasters em seu momento de criação. Ou seja, quando o componente Posts.mxml for criado, irá consumir o HTTPService para que busque todos os artigos disponíveis no site na seção Flex. O formato do consumo das informações será através de e4x.

O AdvancedDataGrid tem apenas uma única coluna, ela é responsável por imprimir o titulo do artigo. Caso o usuário opte por clicar no item do Grid, eu solicito para a aplicação que me mostre no quadro abaixo (composto por um Canvas, Label, TextInput e LinkButton) algumas informações do post selecionado.

Segue o fonte na integra do componente desenvolvido:


<?xml version="1.0" encoding="utf-8"?>
<mx:TitleWindow
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
width="500"
height="250"
showCloseButton="true"
creationComplete="creationCompleteHandler(event);"
close="onCloseHandler(event);"
title="Feeds Imasters - Seção Flex">

<mx:Script>
<![CDATA[

import flash.desktop.NativeApplication;
import mx.events.CloseEvent;
import mx.events.FlexEvent;
import mx.rpc.events.ResultEvent;

/**
* Dataprovider do grid
*/
private var xmlList : XMLList;

/**
* Evento disparado na finalização da construção do
componente
* Responsavel por iniciar o HTTPService para leitura do
feed
*/
private function
creationCompleteHandler(event:FlexEvent) : void
{
this.service.send();
}

/**
* Evento disparado no termino com sucesso da
leitura do HTTPService
* Responsavel por adicionar ao grid o dataProvider
*/
private function serviceHandler(event:ResultEvent) :
void
{
var result : XML = new XML(event.result);

xmlList

= result..item;








/**





}




this.grid.dataProvider = xmlList;

* Evento disparado no fechar da TitleWindow, ele é
responsável por fechar a aplicação
*/
private function onCloseHandler(event:CloseEvent) :
void
{
NativeApplication.nativeApplication.exit();
}

/**
* Quando ocorrer um click no grid, o botao para
abrir o link do post
* sera habilitado, e coloca os textos para os
componentes do autor
* e descrição do post
*/
private function
onClickGridHandler(event:MouseEvent) : void
{
this.btnLink.visible = true;

this.autor.text
((this.grid.selectedItem) as Object)['author'];
this.descricao.text

=

=

((this.grid.selectedItem) as Object)['description'];
}

/**
* Click do botao para visualizar o artigo
*/
private function
onBtnLinkClickHandler(event:MouseEvent) : void
{
navigateToURL(new
URLRequest(((this.grid.selectedItem) as Object)['guid'].toString()),
"_blank");
}

]]>
</mx:Script>

<mx:HTTPService id="service"
url="https://imasters.com.br/feed/secao/flex"
showBusyCursor="true" resultFormat="e4x"
result="serviceHandler(event)"/>

<mx:VBox bottom="5" left="5" right="5" top="5">
<mx:AdvancedDataGrid id="grid" designViewDataType="flat"
bottom="5" left="5"
right="5" top="5"
height="50%" width="100%"

click="onClickGridHandler(event);">
<mx:columns>
<mx:AdvancedDataGridColumn headerText="Titulo
do artigo" dataField="title"/>
</mx:columns>
</mx:AdvancedDataGrid>






<mx:Canvas height="50%" width="100%"
borderColor="#DADADA" borderStyle="solid">
<mx:Label x="6" y="6" id="autor" fontWeight="bold"/>

<mx:TextArea
bottom="6" left="6" right="6"

height="53" id="descricao"

borderStyle="none"

selectable="false" mouseFocusEnabled="false"
mouseEnabled="false"/>


visible="false"

<mx:LinkButton

x="383" y="4" label="Ler artigo"

id="btnLink"

click="onBtnLinkClickHandler(event);"/>

</mx:Canvas>

</mx:VBox>

</mx:TitleWindow>

Algumas considerações interessantes sobre o componente acima:

  • creationComplete=”creationCompleteHandler(event);” – Quando  o  componente  tiver  sido criado, ele irá disparar a  funçãocreationCompleteHandler(),  que será responsável por iniciar o consumo do HTTPService.
  • onCloseHandler(event:CloseEvent); – Neste evento a classe NativeApplication é acionada sendo responsável por fechar a aplicação pelo botão de fechar da TitleWindow, componente base para o componente Posts;
  • serviceHandler(event:ResultEvent) – Evento responsável por filtrar as informações que o  HTTPService consumiu e as retornaram como string. Essas informações foram passadas para um  XMLList, que será o dataProvider do Grid.

Com  isso temos um componente baseado em uma TitleWindow, que é capaz de buscar informações de um feed do site do iMasters, através de HTTPService e disponibilizar as informações na tela.

Dentro da minha aplicação principal, eu possuo a definição de alguns estilos, para que o fundo transparente funcione sem nenhum transtorno. 

Para uma melhor interpretação, irei disponibilizar todo o fonte do main, depois comentando cada uma das observações necessárias.

<?xml version="1.0" encoding="utf-8"?> 
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:app="br.com.horochovec.view.*"
width="500" height="250"

layout="absolute"
paddingRight="0"
paddingLeft="0"
creationComplete="onCreationCompleteHandler(event);">

<mx:Style>
/**
* Estilo para a aplicação possuir o background transparente
*/
Application
{
background-color:"";
background-image:"";
padding: 0px;
}
</mx:Style>

<mx:Script>
<![CDATA[
import air.update.events.DownloadErrorEvent;

import air.update.events.UpdateEvent;
import air.update.ApplicationUpdaterUI;
import mx.controls.Alert;
import mx.events.CloseEvent;
import mx.events.FlexEvent;

/**
* Objeto para atualizacao automatica
*/
private var autoUpdater : ApplicationUpdaterUI = new
ApplicationUpdaterUI();

/**
* Evento disparado na finalização da aplicação para
adicionar um Listner de MouseDown
* no componente principal da aplicação
*/
private function
onCreationCompleteHandler(event:FlexEvent) : void
{
this.main.addEventListener( MouseEvent.MOUSE_DOWN,
onMouseDownHandler );
this.autoUpdateInit();
}

/**
* Funcao para configuracao do inicio da atualizacao
*/
private function autoUpdateInit() : void
{
this.autoUpdater.updateURL =
"http://www.horochovec.com.br/air/update-config.xml";
this.autoUpdater.isCheckForUpdateVisible = true;

this.autoUpdater.addEventListener(UpdateEvent.INITIALIZED,
onUpdateApp);
this.autoUpdater.addEventListener(ErrorEvent.ERROR,
onErrorUpdate);
this.autoUpdater.initialize();
}
void
]]>

/**
* Evento de inicializacao de atualizacao
*/
private function onUpdateApp(event:UpdateEvent) : void
{
this.autoUpdater.checkNow();
}

/**
* Evento em caso de erro na atualizacao
*/
private function onErrorUpdate(event:ErrorEvent) : void
{
Alert.show(event.toString());
}

/**
* Possibilita arrastar o componente na janela atual
*/
private function onMouseDownHandler(event:MouseEvent) :

{
stage.nativeWindow.startMove();
}

</mx:Script>

<!-- Componente com a aplicação -->
<app:Posts x="0" y="0" id="main"/>

</mx:Application>
  • creationComplete=”onCreationCompleteHandler(event);” – No término da criação da aplicação, a função onCreationCompleteHandler()  será disparada, ela é responsável por adicionar um EventListner de MouseDown em meu componente Posts, e também executa a função    autoUpdateInit(), que será responsável por eu iniciar meu objeto de atualização automática;
  • function autoUpdateInit(); – Nessa função ocorre a configuração do objeto para efetuar a atualização automática. O primeiro atributo informado é a URL do arquivo XML que será consultado para verificar se existe uma atualização disponível para a aplicação (o formato do arquivo será informado na sequência). 

O segundo atributo informado é para que seja visível a busca por novas atualizações, esse atributo está informado como true, para que possamos acompanhar a busca por novas atualizações e ver se realmente está funcionando a implementação.

Em  seguida, dois EventListner são adicionados ao objeto, um quando ele for inicializado, e outro caso aconteça algum erro.  

Por fim, a função inicializa o objeto de atualização através da função initialize();

  • function initialize(); – Essa função é responsável por solicitar ao componente de atualização que verifique na URL informada se o aplicativo possui uma versão nova.

A última configuração que deve ser feita dentro do projeto é que, dentro da pasta src, você deve criar um arquivo chamado update-config.xml. Esse arquivo irá conter as informações de como o processo de atualização deve se comportar.

<configuration
xmlns="http://ns.adobe.com/air/framework/update/configuration/1.0">
<url>http://www.horochovec.com.br/air/update-config.xml</url>
<delay>1</delay>
<defaultUI>
<dialog name="checkForUpdate" visible="true" />
<dialog name="downloadUpdate" visible="true" />
<dialog name="downloadProgress" visible="true" />
<dialog name="installUpdate" visible="true" />
<dialog name="fileUpdate" visible="true" />
<dialog name="unexpectedError" visible="true" />
</defaultUI>
</configuration>

Todas as opções foram marcadas como true, para que você possa acompanhar todas as etapas do processo.

A partir deste momento, o trabalho fica todo por conta do Flex. Não há mais necessidade de você se  preocupar com  nada. Ele será responsável por fazer a verificação, sugerir ou não o download, e caso o usuário opte por baixar a aplicação, ainda irá sou salva-lá em seu HD, ou atualizar automaticamente a aplicação. Fácil, não é?

Mas como ele sabe que existe uma nova versão para ser baixada?

Através de um arquivo XML que deverá ser publicado no servidor na mesma URL foi informada na propriedade URL do objeto autoUpdater.

Vamos dar uma conferida em como deve ser esse XML para que o autoUpdater possa fazer as verificações necessárias.

<?xml version="1.0" encoding="utf-8" ?> 
<update xmlns="http://ns.adobe.com/air/framework/update/description/1.0">
<version>1.1</version>
<url>http://www.horochovec.com.br/air/AIRAutoUpdate1-1.air</url>
<description>
<![CDATA[
v1.1
* Adicionado novos feeds para leitura
]]>
</description>
</update>

Simples, não é? Poucas informações são necessárias para que a atualização ocorra. Você deve informar apenas a versão da aplicação que deverá ser atualizada. Ou seja, minha primeira compilação da primeira versão do projeto era 1.0, a próxima versão, ela foi compilada como 1.1, então é essa versão que eu devo passar para o update-config.xml.

Depois, devo informar a URL em que está o arquivo cujo download deverá ser feito para a atualização. E, por último, posso descrever quais foram as alterações na versão que geraram a nova atualização.

Dessa forma, quando iniciarmos a aplicação, o aplicativo irá ler esse XML, verificará que existe uma nova versão disponível, e irá inicializar o processo de atualização.

De uma forma simples assim, você não precisará mais se preocupar com atualizadores ou em informar seu cliente que existe uma nova versão para o  sistema. Deixe o AIR trabalhar um pouco por você!

Com os códigos informados acima, temos a primeira versão do  sistema completa, exceto o XML de atualização, que só  deve ser publicado quando  houver uma nova versão. Abaixo, seguem os fontes para a versão 1.1 do projeto. Esses, sim, sem comentários adicionais, já que o intuito do artigo era apenas explicar como é feita a atualização de forma automática em aplicações em AIR.

AIRAutoUpdate-app.xml


<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://ns.adobe.com/air/application/1.5">
<id>AIRAutoUpdate</id>
<version>1.1</version>
<filename>AIRAutoUpdate</filename>
<name>AIRAutoUpdate - Tutorial IMasters</name>
<installFolder>Imasters/AIR -
Tutoriais/AIRAutoUpdate</installFolder>
<programMenuFolder>Imasters/AIR -
Tutoriais/AIRAutoUpdate</programMenuFolder>
<description>Um pequeno exemplo de aplicação que faz sua
atualização automática</description>
<copyright>Stefan Horochovec
(stefan@horochovec.com.br)</copyright>
<initialWindow>
<title>AIRAutoUpdate</title>
<content>AIRAutoUpdate.swf</content>
<systemChrome>none</systemChrome>
<transparent>true</transparent>
<visible>true</visible>
<minimizable>true</minimizable>
<maximizable>false</maximizable>
<resizable>false</resizable>
</initialWindow>
</application>

AIRAutoUpdate.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:app="br.com.horochovec.view.*"
width="500" height="280"

layout="absolute"
paddingRight="0"
paddingLeft="0"
creationComplete="onCreationCompleteHandler(event);">

<mx:Style>
/**
* Estilo para a aplicação possuir o background transparente
*/
Application
{
background-color:"";
background-image:"";
padding: 0px;
}
</mx:Style>

<mx:Script>
<![CDATA[

import air.update.events.UpdateEvent;
import air.update.ApplicationUpdaterUI;
import mx.controls.Alert;
import mx.events.CloseEvent;
import mx.events.FlexEvent;

/**
* Objeto para atualizacao automatica
*/
private var autoUpdater : ApplicationUpdaterUI = new
ApplicationUpdaterUI();

/**
* Evento disparado na finalização da aplicação para
adicionar um Listner de MouseDown
* no componente principal da aplicação
*/
private function
onCreationCompleteHandler(event:FlexEvent) : void
{
this.main.addEventListener( MouseEvent.MOUSE_DOWN,
onMouseDownHandler );
this.autoUpdateInit();
}

/**
* Funcao para configuracao do inicio da atualizacao
*/
private function autoUpdateInit() : void
{
this.autoUpdater.updateURL =
"http://www.horochovec.com.br/air/update-config.xml";
this.autoUpdater.isCheckForUpdateVisible = false;

this.autoUpdater.addEventListener(UpdateEvent.INITIALIZED,
onUpdateApp);
this.autoUpdater.addEventListener(ErrorEvent.ERROR,
onErrorUpdate);
this.autoUpdater.initialize();
}

void

]]>

/**
* Evento de inicializacao de atualizacao
*/
private function onUpdateApp(event:UpdateEvent) : void
{
this.autoUpdater.checkNow();
}

/**
* Evento em caso de erro na atualizacao
*/
private function onErrorUpdate(event:ErrorEvent) : void
{
Alert.show(event.toString());
}

/**
* Possibilita arrastar o componente na janela atual
*/
private function onMouseDownHandler(event:MouseEvent) :

{
stage.nativeWindow.startMove();
}
</mx:Script>

<!-- Componente com a aplicação -->
<app:Posts x="0" y="0" id="main"/>

</mx:Application>

Posts.mxml

<?xml version="1.0" encoding="utf-8"?> 
<mx:TitleWindow
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
width="500"
height="280"
showCloseButton="true"
creationComplete="creationCompleteHandler(event);"
close="onCloseHandler(event);"
title="Feeds Imasters - Seção Flex">

<mx:Script>
<![CDATA[
import mx.events.ListEvent;
import mx.collections.ArrayCollection;

import flash.desktop.NativeApplication;
import mx.events.CloseEvent;
import mx.events.FlexEvent;
import mx.rpc.events.ResultEvent;

/**
* Dataprovider do grid
*/







/**




private var xmlList : XMLList;


componente

feed

* Evento disparado na finalização da construção do

* Responsavel por iniciar o HTTPService para leitura do

*/
private function

creationCompleteHandler(event:FlexEvent) : void
{
this.service.send();
}

/**
* Evento disparado no termino com sucesso da
leitura do HTTPService
* Responsavel por adicionar ao grid o dataProvider
*/
private function serviceHandler(event:ResultEvent) :
void
{
var result : XML = new XML(event.result);

xmlList

= result..item;




/**


}

this.grid.dataProvider = xmlList;

* Evento disparado no fechar da TitleWindow, ele é
responsável por fechar a aplicação
*/
private function onCloseHandler(event:CloseEvent) :
void
{
NativeApplication.nativeApplication.exit();
}

/**
* Quando ocorrer um click no grid, o botao para
abrir o link do post
* sera habilitado, e coloca os textos para os
componentes do autor
* e descrição do post
*/
private function
onClickGridHandler(event:MouseEvent) : void
{
this.btnLink.visible = true;

this.autor.text
((this.grid.selectedItem) as Object)['author'];
this.descricao.text

=

=

((this.grid.selectedItem) as Object)['description'];
}

/**
* Click do botao para visualizar o artigo
*/
private function
onBtnLinkClickHandler(event:MouseEvent) : void
{
navigateToURL(new





URLRequest(((this.grid.selectedItem) as Object)['guid'].toString()),
"_blank");
}

/**
* Evento disparado no change do ComboBox
* Responsavel por buscar o novo feed e atualizar no
Grid
*/
private function
onComboChangeHandler(event:ListEvent) : void
{
this.service.url = (this.combo.selectedItem
as Object)['url'].toString();
this.service.send();
}

]]>
</mx:Script>

<mx:HTTPService id="service"
url="https://imasters.com.br/feed/secao/flex"
showBusyCursor="true" resultFormat="e4x"
result="serviceHandler(event)"/>

<mx:VBox bottom="5" left="5" right="5" top="5">
<mx:ComboBox width="100%" labelField="label" id="combo"
change="onComboChangeHandler(event);">
<mx:ArrayCollection>
<mx:Object label="Flex"
url="https://imasters.com.br/feed/secao/flex"/>
<mx:Object label="PHP"
url="https://imasters.com.br/feed/secao/php"/>
</mx:ArrayCollection>
</mx:ComboBox>
<mx:AdvancedDataGrid id="grid" designViewDataType="flat"
bottom="5" left="5"
right="5" top="5"
height="50%" width="100%"

click="onClickGridHandler(event);">
<mx:columns>
<mx:AdvancedDataGridColumn headerText="Titulo
do artigo" dataField="title"/>
</mx:columns>
</mx:AdvancedDataGrid>

<mx:Canvas height="50%" width="100%"
borderColor="#DADADA" borderStyle="solid">
<mx:Label x="6" y="6" id="autor" fontWeight="bold"/>

<mx:TextArea
bottom="6" left="6" right="6"

height="53" id="descricao"

borderStyle="none"

selectable="false" mouseFocusEnabled="false"
mouseEnabled="false"/>


visible="false"

<mx:LinkButton

x="383" y="4" label="Ler artigo"

id="btnLink"

click="onBtnLinkClickHandler(event);"/>

</mx:Canvas>






</mx:VBox>

</mx:TitleWindow>

Espero que tenha ficado claro o processo de atualização automática no AIR. 

Abraços a todos.