Adicionando imagens via CSS em aplicações JSF 2.0

Deixe um comentário

Introdução

Uma boa prática para o desenvolvimento de aplicações web é a utilização do padrão MVC (Model-View-Controller) onde a aplicação (e suas responsabilidades) é separada entre seu domínio (Model), sua apresentação (View) e como as ações de entrada do usuário, através da View, refletem no domínio (Controller).

No mundo Java, existem algumas centenas (ou milhares) de frameworks para desenvolvimento web que seguem esse padrão, entre os mais conhecidos estão o Apache Struts, o Spring e o JavaServer Faces (JSF). Para entender melhor a diferença entre eles, sugiro a leitura do artigo “Entenda os mvcs e os frameworks action e component based” que encontra-se nas referências. Entre algumas dessas diferenças, encontra-se a maneira como os recursos (css, javascript, imagens, etc.) são carregados por eles para a camada de visão (a View do MVC). Aí ocorreu um problema muito chato comigo ao desenvolver aplicações utilizando o JSF 2.0.

Problema

Ao utilizar o padrão do JSF 2.0 para gerar a camada de visão (arquivos .xhtml), simplesmente as imagens de fundo ou imagens carregadas pelos arquivos CSS não apareciam! Pensei que fosse algum problema com meus arquivos, mas ao utilizar o Twitter Bootstrap em outro projeto, framework front-end para desenvolvimento de páginas web, verifiquei que o mesmo problema continuava. Mesmo seguindo os padrões de desenvolvimento do JSF (colocar todos os recursos dentro de uma pasta /resource, separando-os em pastas de acordo com seu tipo), as imagens nunca eram carregadas!

Solução

Em seus arquivos CSS, sempre que for utilizar uma imagem através deles, simplesmente troque:

url("../images/caminhoParaOArquivo/arquivo.png");

por:

url("#{facesContext.externalContext.requestContextPath}/caminhoParaOArquivo/arquivo.png");

onde caminhoParaOArquivo/arquivo.png é o caminho onde está o arquivo da imagem. Em um projeto JSF 2.0, geralmente fica “/resources/images/nomeDoArquivo.png“.

Essa mudança faz-se necessária devido ao funcionamento do ciclo de vida do JSF, como visto aqui.

Referências:

MVC

JavaServer Faces

Outras

Anúncios

Artigo Easy Java Magazine

1 Comentário

Easy Java Magazine 7

Pessoal, venho comunicar que meu primeiro artigo para a Easy Java Magazine foi publicado semana passada (o aviso está meio atrasado por conta das muitas atribuições do mestrado :p).

Adivinhem sobre o que é o artigo? Quem chutou que é sobre Java ME acertou!

É a matéria da capa: “Primeiros Passos com o Java ME”. Para acessá-la, basta seguir o link: http://www.devmedia.com.br/post-21378-Revista-easy-Java-Magazine-7.html

Gostaria de agradecer ao Eduardo Spínola, editor da DevMedia, pela oportunidade de escrever esse artigo, e ainda mais por ser a matéria da capa.

Dúvidas e sugestões são bem-vindas.

Apagando o histórico do navegador do Eclipse

3 Comentários

Eclipse IDE

O Eclipse IDE possui um componente bem útil e simples para desenvolvimento Web: um Web Browser interno. Essa simplicidade me trouxe um problema. Eu não estava conseguindo fazer algo que é bastante fácil e direto em outros navegadores, como o Firefox, que é limpar a barra de navegação.

Veja uma pequena parte do resultado disso na imagem abaixo:

urls demais...

Para resolver esse inconveniente faça o seguinte:

  1. Vá ao diretório do seu Workspace.
  2. Entre na pasta oculta .metadata (no Linux basta apertar Ctrl+h para ver as pastas ocultas)
  3. Agora siga o seguinte caminho .plugins org.eclipse.core.runtime.settings. Nessa pasta você vai encontrar vários arquivos.
  4. Abra o arquivo org.eclipse.ui.browser.prefs.
  5. Apague as URLs que vem depois de internalWebBrowserHistory=

Pronto!

Testes com LWUIT

Deixe um comentário

logotio LWUIT

Recentemente estive fazendo alguns testes com o Lightweight User Interface Toolkit (LWUIT), uma API da SUN/Oracle para substituição da antiga LCDUI do Java ME. Atualmente ela encontra-se na versão 1.3 e seus componentes serão utilizados como base para a construção de interfaces gráficas no Ginga-J, que é middleware onde irão rodar as aplicações para TV Digital no Brasil.

Não irei falar muito sobre LWUIT agora. Além de meu tempo estar meio curto, gostaria de falar sobre ele aplicado mais especificamente à TV Digital, algo que pretendo fazer em artigos futuros. Para mais informações sobre o LWUIT, dê uma olhada nos links no fim deste artigo que irão falar melhor do que eu sobre ele.

Desses primeiros testes que realizei nasceu um penqueno aplicativo bem simples, o Flex Mobile, que serve para indicar a escolha entre álcool ou gasolina na hora de abastecer seu carro. Ele não está completo, mas encontra-se usável.

Vantagens e Desvantagens

LWUIT lembra muito o SWING. É bastante fácil desenvolver interfaces gráficas utilizando esse framework. A seguir algumas vantagens e desvantagens que pude perceber ao utilizá-lo:

Vantagens:

Melhorias a alguns componentes já existentes:

  • Algumas classes foram completamente reformuladas como List e Form por exemplo. Algumas outras foram substituídas ou extintas como a classe StringItem.

Suporte a Touch Screen:

  • Aplicações feitas em LWUIT são suportadas por dispositivos equipados com telas sensíveis ao toque. É algo que o framework faz sozinho.

Transições de telas animadas:

  • Algo bastante fácil de configurar e que deixam sua aplicação com um visual bastante legal. Algumas dessas transições lembram muito as utilizadas pelo Gnome (interface gráfica para Linux).

Botões como componentes nativos:

  • Nada mais de ter que fazer gambiarras com a classe StringItem ou ter um enorme trabalho em desenhar botões “de verdade” utilizando a classe Canvas. Em LWUIT  existe a classe Button que é praticamente igual a classe Button do SWING.

Suporte a temas personalizados:

  • Uma das melhores caracteríscas do LWUIT na minha opinião. Você pode personalizar toda a parte de UI em sua aplicação, desde a tela de fundo até componentes como SoftButton e Menus, de maneira simples e fácil. Uma mesma aplicação pode ter suporte a vários temas sem muito esforço.

ResourceEditor:

  • O ResourceEditor é um pequeno aplicativo que acompanha o framework. Ele é um aplicativo que ajuda bastante na criação de temas de forma visual. Além de criar temas através dele é possível importar imagens, animações, arquivos SVG, datasources e gerar um único arquivo de extenão .res, que pode armazenar todos os recursos do projeto e é acessado de maneira bastante simples por toda a aplicação.

Layouts:

  • Utiliza o mesmo conceito de Layouts do SWING para organização dos componentes visuais em um container. Os layouts disponíveis são: BorderLayout, BoxLayout, FlowLayout, GridLayout, GroupLayout, Coordinate Layout e Table Layout. Quem já os utilizou SWING não irá estranhar nenhum dos nomes citados anteriormente.

TabbedPane e Dialog:

  • As famosas Abas e caixas de dialog agora estão disponíveis também em Java ME através dos componentes TabbedPane e Dialog, respectivamente.

3D:

  • Suporte a 3D através da M3G (Scene Graph or Immediate Mode 3D API).

Internacionalização e Localização:

  • Suporte a Internationalization (I18N) e Localization (L10N).

Personalização Flexível

  • A personalização de temas completos e/ou componentes isolados pode ser feita através do ResourceEditor, programaticamente ou através de ambos.

Desvantagens

Tamanho final da aplicação tende a ficar maior:

  • Algumas aplicações tendem a ficar um pouco (ou muito) pesadas e consequentemente grandes

Dispositivos mais fracos não rodam bem aplicativos feitos em LWUIT:

  • Mesmo diminuindo os recursos utilizados, como o tamanho dos temas e outros recursos, alguns celulares ainda não irão conseguir rodas aplicativos feitos em LWUIT de maneira satisfatório, como aconteceu com meu Motorola W388. Já em um SonyEricsson W910i consegui rodar os exemplos fornecidos no site do projeto e um pequeno aplicativo que fiz, o Flex Mobile (que irei falar em seguida), de maneira bastante satisfatória.

Flex Mobile

Flex Mobile é um aplicativo bem simples que indica se você deve abastecer com álcool ou gasolina. Simplesmente digite o preço do litro dos combustíveis, sem vírgula, no local indicado e ele sugere qual combustível utilizar.

Ele é OpenSource e o site do projeto é: http://sourceforge.net/projects/flexmobile/. Lá você irá encontrar os fontes do aplicativo (que ainda necessitam de uma última refatoração) e um .jar no ponto para ser instalado no seu celular.

Ele ainda está em uma versão beta, necessitando de algumas melhorias em relação aos botões de ação e algumas coisinhas a mais que dependem de testes em outros dispositvos. Caso queiram testar o aplicativo sintam-se à vontade. Surgindo alguma dúvida ou sugestão podem mandar um email para mim ou postar um comentário neste artigo que estão lendo. Sugestões são sempre bem-vindas.

Algumas das características a serem consideradas antes de rodar o aplicativo:

  • Configurado para rodar em dispositivos com display de 240×320 pixels.
  • O dispositivo deve ter suporte a imagens de fundo.
  • O dispositivo deve suportar a CLDC 1.1 e MIDP 2.0 (ou MIDP 2.1)

O aplicativo foi testado em um SonyEricsson W910i, onde rodou sem nenhum problema.

Conclusão

LWUIT é uma boa API para o desenvolvimento em Java ME. Lembra muito o SWING, diminuindo muito a curva de aprendizagem desse framework. Mesmo não resolvendo por completo o problema de um mesmo aplicativo não funcionar exatamente igual em dispositivos de diferentes fabricantes, arrisco a dizer que ele resolve 90% desse problema. Recomendo a utilização desse bom framework Java ME caso não queira usar a classe Canvas diretamente.

Links

LWUIT site project: https://lwuit.dev.java.net/

Shai’s Java & LWUIT blog: http://lwuit.blogspot.com/

Indrodução ao LWUIT – site da Oracle/Sun: http://java.sun.com/developer/technicalArticles/javame/lwuit_intro/

Flex Moblie: http://sourceforge.net/projects/flexmobile/

Testes Unitários em Java ME com JMUnit

2 Comentários

" "

Testes automatizados são algo fundamental no desenvolvimento de software orientado a objetos. Eles permitem uma melhor evolução do software agregando confiança e qualidade ao produto final. No mundo Java SE e Java EE existem muitas ferramentas de testes. Quando olhamos para Java ME o número de ferramentas torna-se menor, devido tanto aos requisitos das aplicações quanto as restrições impostas pela própria API Java ME.

Nesse post farei uma pequena introdução aos tipos de teste, dando maior importância aos testes unitários e como eles podem ser utilizados em Java ME com framework JMUnit e a metodologia Test-Driven Development.

Diferentes tipos de testes em aplicações Java ME

Existem vários tipos de testes e eles variam de acordo com a aplicação e com os requisitos desejados pelo cliente. Em aplicações Java ME os principais tipos de teste são:

Testes de Usabilidade: tem foco na navegação entre as telas da aplicação e na maneira que os componentes visuais comunicam-se com o usuário.

Teste de performance e trafego na rede: testa se a aplicação funciona bem em redes que apresentam condições extremas. Aplicativos móveis devem funcionar de maneira satisfatória mesmo em redes muito congestionadas.

Testes do lado servidor: muitos aplicativos Java ME comunicação com servidores remotos. Para tais aplicativos testar as interfaces entre a aplicação que roda no dispositivo móvel e o servidor remoto é muito importante.

Testes unitários automatizados: estes é um dos mais importantes testes, seja em aplicativos móveis, Web ou Desktop. Ele feito durante o desenvolvimento do aplicativo, juntamente com o código-fonte. Geralmente esses testes são criados com algum framework da família xUnit (jUnit, nUnit, etc).

TDD

Test-Driven Development é uma maneira diferente de escrever software. Nela você primeiro escreve o teste e depois escreve o código a ser testado. O TDD está conseguindo cada vez mais adeptos graças a sua simplicidade e alto grau de qualidade do código ao durante todo o processo de desenvolvimento.

Algumas vantagens dessa abordagem são:

  • Simplicidade
  • Aumento da confiança no código
  • Ajuda na documentação
  • Falicita refatorações

O processo de criação no TDD é simples:

  1. Escreva um teste que falhe
  2. Faça o teste passar o mais rápido possível
  3. Refatore
  4. Faça o teste passar
  5. Refatore seus testes
  6. Faça os passos 1 a 5 para cada novo teste
  7. Repita todos os passos para cada novo método adicionado

Framworks para Testes Unitários

Para escrever testes unitários existem alguns frameworks, sendo o mais famosos deles o JUnit. Infelizmente o JUnit só pode ser utilizado em ambientes Java SE e Java EE, devido principalmente a algo que ele usa e é totalmente inexistente da API Java ME: reflexão. Isso ficou mais complicado depois do lançamento da versão 4 do JUnit, que agora também tem suporte a Annotations. Mesmo com essas barreiras, existem frameworks para testes unitários em Java ME, sendo os principais o JMUnit e o J2MEUnit.

O framework JMUnit

O JMUnit é um framework nos mesmos moldes do JUnit voltada para Java ME. Possui uma versão para configuração CLCD (1.0 e 1.1), é open-source e foi criado pelo brasileiro Bruno Silva.

JMUnit na prática

Chegou a hora de utilizar o JMUnit. Crie um novo MIDlet Project no Eclipse com o nome Java ME unit testes. Dentro da pasta src crie dois pacotes: model e jmunit.tests. A sua estrutura de diretórios deve ficar parecida com a da figura abaixo:

Estrutura dos pacotes no projeto

Vamos fazer algo bem simples somente para demonstração: uma classe que fará a conversão de temperatura de Celsius para Kelvin. Lembre-se que iremos utilizar TDD, então vamos escrever os testes primeiro.

Escrevendo os Testes

O JMUnit possui dois conceitos principais:

Test Case: classe onde os casos de testes são escritos.

Test Suite: classe onde os Test Cases são executados.

A primeira coisa que faremos é criar um Test Case para nossa classe de conversão. Para fazer isso siga os passos abaixo:

1 – Clique em o botão direito sobre o pacote jmunit.tests

2 – Selecione NewJMUnit Test Case

JMUnit Test Case

3 – Caso uma mensagem de alerta apareça na parte inferior do Wizard, clique onde diz Click here. Coloque o nome da classe de ConversionTest. Clique em Finish.

A classe gerada apresenta um construtor e um método:

public ConversionTest()
{
	super(0, "TemperatureConversionTest");
}

public void test(int testNumber) throws Throwable
{
	switch (testNumber)
	{
	}
}

O construtor possui dois parâmetros: o número de testes existentes no Test Case e o nome do Test Case. No método test nos passamos o número de testes que serão executados que servirá para o switch.

Agora vamos adicionar nossos testes. Irei seguir o padrão do JUnit onde cada método não terá retorno (void) e começa com o nome test seguido do nome do método que irá ser testado. O primeiro test será esse:

public void testcelsiusToKelvin() throws AssertionFailedException
{
	float result = TemperatureConversion.celsiusToKelvin(); // apresenta erro.
	assertEquals(0, result);
}

Para o teste acima funcionar você deve alterar o construtor da classe e o método test. Eles ficarão assim:

public ConversionTest()
{
	// Passe o número de testes no construtor, nesse caso o número 1
	super(1, "ConversionTest");
}

public void test(int testNumber) throws Throwable
{
	switch (testNumber)
	{
		// Também modificamos o "case". Lembre-se que o “case” em Java começa em 0.
		case 0: testcelsiusToKelvin(); break;
		default: break;
	}
}

Para rodar o teste é necessário criar um Test Suite. É bem simples criar um:

1 – Clique com o botão direito em cima da pacote “jmunit.tests”.

2 – Vá em New → JMUnit Test Suite.

New Test Suite

3 – No Wizard que irá aparecer apenas clique em Finish

Agora siga os passos abaixo para rodar o teste:

1 – Clique com o botão direito em cima do Test Suite AllTestSuite.

2 – Vá em Run AsEmulated Java ME MIDlet.

3 – Surgirá uma janela de alerta do Eclipse. Clique no botão Proceed.

Rode o teste clicando em Test.

Opa! O testes falhou. Olhando o resultado do teste pode-se observar que a classe TemperatureConversion não existe. Realmente ela não existe ainda já estamos usando TDD. Escrevemos primeiro o teste e depois o código que irá ser testado, lembre-se disso.

Saída erro JMUnit

Crie a classe:

1 – Utilize o Quick Fix do eclipse (Ctrl+1 ou clique no ‘x’ no começo da linha) e escolha a primeira opção: Create Class TemperatureConversion.

2 – Quando o Wizard aparacer, escolha o pacote model. Para isso clique no botão Browser, escolha o pacote model.

Escolhendo o pacote

Package selection

3 – Clique em Finish.

4 – Faça o mesmo processo para criar o método celsiusToKevin na classe TemperatureConversion . Não mude o conteúdo do método gerado.

Rodando o teste novamente ele irá passar. Isso é visto com a barrinha verde apresentada no MIDlet Emulator.

Primeiro teste passou JMUnit

Agora vamos fazer mais um teste para nosso método. Vá na classe TemperatureConversionTest e adicione o sequinte método:


public void testcelsiusToKelvin(float c, float k) throws AssertionFailedException
{
	k = TemperatureConversion.celsiusToKelvin(c);
	assertEquals(298, k);
}

Não esqueça de mudar o construtor e o método test da classe:


public TemperatureConversionTest()
{
	super(2, "TemperatureConversionTest");
}

public void test(int testNumber) throws Throwable
{
	switch (testNumber)
        {
	        case 0: testcelsiusToKelvin();break;
		case 1: testcelsiusToKelvin(25, 298);
		default: break;
	}
}

Rode a classe AllTestSuite novamente. Perceba que agora temos dois testes. Execute os testes.

Saida erro teste 2

Olhando o resultado dos testes percebemos que um deles passou e outro falhou. Vamos refatorar o método responsável pela conversão das temperaturas na classe TemperatureConversion colando a fórmula para conversão de Celsius para Kelvin, que é K = ºC + 273. Modifique o método para:


public static float celsiusToKelvin(float celsius)
{
	float kelvin = celsius + 273;
	return kelvin;
}

Parece tudo OK agora, não é? Só para garantir, rode os testes novamente.

Saida erro teste 2

Opa! Barrinha vermelha de novo. Mas o que será que está errado?

Vá na nossa classe de testes. Você esqueceu de refatorar o nosso primeiro método de testes. Modifique-o para:


public void testcelsiusToKelvin() throws AssertionFailedException
{
        // Agora é necessário passar um parâmatro para o método que nesse caso é 0 (zero).
	float result = TemperatureConversion.celsiusToKelvin(0);
	assertEquals(273, result);
}

Rode os testes e barrinha verde novamente aparecerá.

Todos os testes passaram

Conclusão

Testes unitários são algo de extrema importância no desenvolvimento de software. Como podemos ver, escrever testes unitários é algo bem simples e não há desculpas para não fazê-lo, seja em que ambiente for. No mundo Java ME existem outros frameworks para testes unitários, mas prefiro o JMUnit por sua simplicidade, por seguir o padrão xUnit e também por já estar integrado ao Eclipse Pulsar.

Espero que todos tenham gostado. Sugestões e críticas são bem-vindas. Até a próxima!

Referências:

Brunno Silva Weblog

JMUnit – JUnit for Java ME

Testing Wireless Java Applications

JUnit Testing Using Java ME JUnit Frameworks

Introdução ao Desenvolvimento Orientado a Testes (TDD) – Conding Dojo Floripa

Persistência em Java ME – uma pequena introdução

Deixe um comentário

images decorativa

Algo bastante relevante na grande maioria das aplicações é a persistência. Persistência em aplicações Web e Desktop é geralmente conseguida através do uso de Sistemas Gerenciadores de Bancos de Dados (DBMSs – Database Management Systems), que são responsáveis por gerenciar e garantir a integridade dos dados, onde esses dados ficam guardados em HDs com bastante espaço (muitos GBs ou alguns TBs). No mundo mobile, especialmente para celulares, as coisas não funcionam bem assim (embora hajam exceções). Não temos DBMSs propriamente ditos, mas outras formas de armazenamento bastante parecidas com sistemas de arquivos tradicionais, e os dados são persistidos em regiões não-voláteis dos dispositivos: HDs pequenos (alguns GBs) ou em memória Flash (cartões de memória).

Voltando nossas atenções para Java ME, a versão oficial do MIDP não oferece acesso direto ao sistema de arquivos de dispositivo, mas usa uma abordagem complementar: Record Stores. Diferentemente de uma arquivo tradicional, que pode guardar qualquer coisa, um Record Store guarda um grupo de itens que possuem estruturas parecidas, lembrando muito o conceito de tabelas e registros em um banco de dados relacional.

O MIDP suporta persistência de dados através do Record Management System (RMS).

RMS

A API RMS abstrai os detalhes de acesso e persistência dos dados, provendo uma maneira uniforme de criar, destruir e modificar esses dados de forma independente do dispositivo utilizado. A classe responsável por essa abstração é a classe RecordStore localizada no pacote javax.microedition.rms.

RecordStores

Um Record Store é uma coleção de registro em que os MIDlets tem acesso localmente, ou seja, no próprio dispositivo. Cada Record Store é identifica por uma String normal java: é case-sensitive (diferenciação de maiúsculas e minúsculas) e também é Unicode, com um tamanho variável entre 1 e 32 caracteres. O nome de Record Store deve ser único para um conjunto de MIDlets (MIDlet Suite). Record Stores podem ser compartilhados entre MIDlets que pertencem ao mesmo MIDlet Suite (mas isso não se aplica a MIDlets pertencentes a MIDlets Suites diferentes).

O RMS define as seguintes operações sobre um registro dentro de um Record Store:

  • Adicionar um registro
  • Deletar um registro
  • Atualizar um registro
  • Recuperar um registro
  • Navegar entre os registros (Enumerate)

Uma outra característica interessante da classe RecordStore é que ela garante atomicidade das transações single thread, mas não garante essa atomicidade em multithread.

Trabalhando com RecordStore

Abrindo e fechando Record Stores

Para criar ou abrir um um Record Store você utiliza o método estático:

RecordStore.openRecordStore(String nameOfRecordStore, boolean create);

No primeiro parâmetro você especifica o nome do Record Store e no segundo você informa se ele deve ser criado ou não. Esse método retorna o Record Store aberto/criado. Ele pode lançar as sequintes exceptions:

  • RecordStoreException: lançada em resposta a alguns erro durante a criação do Record Store
  • RecordStoreNotFoundException: lançada se o Record Store não for encontrado e a flag create estiver false
  • RecorStoreFullException: lançad se o Record Store estiver cheio.
  • IllegalArgumentExceptions: o método recebeu argumentos inválidos.

Após terminar de utilizar o Record Store você deve fechá-lo utlizando o método:

closeRecordStore()

Removendo um Record Store

Para deletar um Record Store você utiliza o método estático:

RecordStore.deleteRecordStore(String nameOfRecordStore)

Aqui não tem mistério, você simplesmente chama esse método passando o nome do Record Store a ser deletado. Esse método pode lançar as seguintes exceptions:

  • RecorStoreException: um exceção genérica indicando erros ao deletar um Record Store
  • RecordStoreNotFoundException: o Record Store não foi encontrado.

Ao apagar um Record Store você deve seguir as sequintes regras:

  • Um MIDlet só pode deletar um Record Store pertencente ao seu MIDlet Suite
  • O Record Store a ser deletado deve estar fechado. Caso isso seja falso uma RecordStoreException será lançada.

Obtendo informações sobre um Record Store

Você pode obter informações sobre um Record Store específico. Aqui estão alguns do métodos mais utilizados:

  • getName: retorna o nome de um Record Store
  • getLastModified: retorna o tempo decorrido desde a última modificação ocorrida no recor store. Esse tempo retorna o mesmo valor do método System.currentTimeMillis().
  • getVersion: retorna um inteiro que é modificado toda vez que um novo registro é inserido, deletado ou modificado no Record Store
  • getSize: retorna o número de bytes de um Record Store
  • getSizeAvailable: retorna a quantidade de bytes que ainda estão disponíveis para o Record Store no dispositivo. Esse valor não informa a quantidade correta de bytes disponíveis, pois não leva em consideração o tamanho dos metadados do Record Store em questão.

Registros (Records)

Um Record Store contem um ou mais registros. Cara registro é formado por um array de bytes (byte []) e possuem um valor inteiro que representa um identificados, como um espécie de ID, utilizado para diferenciar um registro de outro dentro de um Record Store. O identificador de um registro não faz parte da informação que ele representa e é atribuído ao registro assim que ele é criado. Identificadores obedecem as sequintes regras:

  • O identificador atribuído ao primeiro registro criado em um Record Store possui sempre o valor 1.
  • O identificador atribuído a um novo registro seque a regra n+1, onde n representa o valor do identificador do último registro adicionado no Record Store.
  • Identificadores nunca são reutilizados. Toda vez que um registro é deletado seu identificador é perdido.

Adicionando Registros

Um novo registro é criado através do método addRecord, que retorna o valor inteiro representado o valor do identificador do registro recém-criado:

public int addRecord(byte[] data, int offset, int size)

Para utilizar esse método com suas classes Java, você deve transformar a informação contidas nelas em um array de bytes. Uma maneira bem legal de fazer isso é utilizar a classe DataOutPutStream que irá os valores que você precisa de uma determinada classe e então utilizar a classe ByteArrayOutputStream para transformar essas informações em um array de bytes para você.

Uma maneira legal de entender como manipular registro é através de um pequeno exemplo. Irei pegar o exemplo emprestado do livro J2ME in a Nutshell. Nesse exemplo você tem um objeto que representa os pontos marcados por um jogador em um jogo e você quer salvar esses dados em um Record Store chamado Scores. A classe que você quer armazenar é parecida com essa:

public class ScoreRecord
{

	private String name;
	private int score;

	public ScoreRecord()
	{

	}

	public ScoreRecord(String name, int score)
	{
		super();
		this.name = name;
		this.score = score;
	}

	public String getName() {
		return name;
	}

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

	public int getScore() {
		return score;
	}

	public void setScore(int score) {
		this.score = score;
	}

}

Aqui está o trecho de código que mostra como você faria para armazenar os pontos de um jogador

// Create an object to be written
ScoreRecord record = new ScoreRecord( );
record.playerName = "TopNotch";
record.score = 12345678;

// Create the output streams
ByteArrayOutputStream baos = new ByteArrayOutputStream( );
DataOutputStream os = new DataOutputStream(baos);

// Write the values to be saved to the output streams
os.writeUTF(record.playerName);
os.writeInt(record.score);
os.close( );

// Get the byte array with the saved values
byte[] data = baos.toByteArray( );

// Write the record to the Record Store
int id = recordStore.addRecord(data, 0, data.length);

Recuperando registros

Para recuperar o registro de um Record Store basta fazer o caminho inverso do código anterior utilizando o método getRecord:

public byte[] getRecord(int recordId)

Ao recuperar um registro você utiliza as classes DataInputStream e ByteArrayInputStream, bastante parecido com o exemplo anterior, onde utilizamos as classes ByteArrayOutputStream e DataOutPutStream. Uma dica legal para não confundir essas classes: Output = gravar e Input = recuperar.

Agora suponha que gostaríamos de recuperar o nome e os pontos de um jogador armazenados no nosso Record Stores Scores. Para fazer isso utilizamos o código abaixo:

byte[] data = recordStore.getRecord(recordId);
DataInputStream is = new DataInputStream(new ByteArrayInputStream(data));
ScoreRecord record = new ScoreRecord( );
record.playerName = is.readUTF( );
record.score = is.readInt( );
is.close( );

Atualizando Registros

Para atualizar um registro você deve conhecer o seu identificador. Com o identificador em mão, você pode atualizar o registro sem medo através do método setRecord:

public void serRecord(int recordId, byte[] data, int offset, int size)

Esse método recebe 4 argumentos, onde os dois primeiros são os mais interessantes:

  • O identificador (ID) do registro que será modificado
  • Os novos dados do registro

Voltando ao nosso exemplos do jogador, suponha que ele vez mais 10 pontos e nós devemos atualizar seu registro. O seguinte código faz isso:

// Modify the score
record.score += 10;
ByteArrayOutputStream baos = new ByteArrayOutputStream( );
DataOutputStream os = new DataOutputStream(baos);
os.writeUTF(record.playerName);
os.writeInt(record.score);
os.close( );
byte[] data = baos.toByteArray( );
// Write the record to the Record Store, overwriting the existing record
recordStore.setRecord(recordId, data, 0, data.length);

Removendo um registro

Para remover um registro você simplesmente chama o método deleteRecord, passando o identificador do registro a ser deletado:

public void deleteRecord(int recordId)

Lembre-se que ao deletar um registro seu ID não é reciclado!

Record Enumerations

Os métodos que acabamos de mostrar supõem que você conhece o identificador de cada registro. O grande problema é que isso não é verdade na maioria das vezes. O que fazer então??

Para superar esse problema a classes RecordStore possui o método chamado enumeratedRecords que você pode usar para buscar eficientemente em um Record Store o registro que você precisa:

	public RecordEnumeration enumerateRecords(RecordFilter filter, RecordComparator comparator, boolean keepUpdated)

O argumento filter é responsável por selecionar quais registros irão ser incluídos na enumeração (falarei sobre ele já, já). O segundo argumento, comparator, é utilizado para comparar dois registros; ele é utilizado para ordenar os registros na enumeração. O último argumento, keepUpdate, quando true indica que a enumeração irá manter-se atualizada toda vez que alguma mudança acontecer no Record Store.

Um RecordEnumeration é uma interface que contém um conjunto de métodos que são utilizados para iterar sobre registros. Diferentemente de uma Enumeration Java tradicional permite que você avance ou recue, podendo mudar a direção a qualquer momento. Você consegue isso através dos métodos hasNextElement e nextRecordId para avançar; para voltar são utilizados os métodos hasPreviousElement e previousRecordId. Um pequeno exemplo de como avançar e recuar através da RecordEnumeration:

RecordEnumeration enum = recordStore.enumerateRecords(null, null, false);

// Traverse forwards
while (enum.hasNextElement( )) {
    int id = enum.nextRecordId( );
    // Do something with this record id (not shown)
}

// Traverse backwards
while (enum.hasPreviousElement( )) {
    int id = enum.previousRecordId( );
    // Do something with this record id (not shown)
}

Após obter o identificador de um registros, você geralmente gostaria de acessar o conteúdo desse registro. Você pode fazer isso utilizando uma combinação dos métodos mostrados acima com o método getRecord:

// Traverse forwards
while (enum.hasNextElement( )) {
    byte[] record = enum.nextRecord( );

    // Do something with this record (not shown)
}

// Traverse backwards
while (enum.hasPreviousElement( )) {
    byte[] record = enum.previousRecord( );

    // Do something with this record (not shown)
}

Uma outra diferença entre um Enumeration normal e uma RecordEnumeration é que a última possui o método reset que permite mover o cursor para o começo da iteração:

// Traverse forwards
while (enum.hasNextElement( )) {
    byte[] record = enum.nextRecord( );
    // Do something with this record (not shown)
}
enum.reset( );  // Reset to initial state
// Read all the records again
while (enum.hasNextElement( )) {
    byte[] record = enum.nextRecord( );
    // Do something with this record (not shown)
}

Trabalhando com RecordEnumerations atualizadas

Quando chamamos o método enumerateRecords e passamos o último argumento (boolean keepUpdate) como false, mudanças feitas no Record Store não serão refletidas na enumeration. Isso traz duas consequências:

  • Novos registros adicionados não estão disponíveis na enumeration.
  • Se um registro for deletado antes de seu identificador ser recuperado pela enumeration, uma InvalidRecordIDException será lançada quando seu registro for utilizado através dos métodos getRecord(enum.nextRecordId( )) ou enum.nextRecord.

Uma forma de prevenir isso é passar o último argumento como true no método enumerateRecords como mostrado abaixo:

RecordEnumeration enum = recordStore.enumerateRecords(null, null, true);

Agora toda vez que um novo registro for adicionado, deletado ou atualizado, essas mudanças serão refletidas imediatamente na enumeration. A única desvantagem dessa abordagem é um consumo de memória um pouco maior (você não deve se preocupar com isso na maioria das vezes).

Quando você terminar de usar a RecordEnumeration, você deve utilizar o método destroy:

public void destroy()

Esse método irá liberar os recursos utilizados pela enumeration.

Record Filteres e Comparators.

Caso você não queira iterar sobre todo os registros em um Record Store, você pode criar um objeto que implemente a interface RecordFilter. Esse objeto agora poderá filtrar os resultados obtidos por uma RecordEnumeration.

A interface RecordFilter exige que você implemente apenas um método:

public boolean matches(byte[] data)

Após criar um filtro e implementar o método matches, basta passá-lo como parâmetro do método enumerateRecord que foi mostrado anteriormente. Ao iterar sobre a Enumeration, ela irá retornar apenas os registros que passarem pelo filtro implementado no método matches.

Voltando ao nosso exemplo do jogo, suponha que agora queremos recuperar apenas os jogadores que marcaram mais de 10,000 pontos. Para isso criamos o seguinte RecordFilter:

RecordFilter filter = new RecordFilter( ) {
    public boolean matches(byte[] data) {
        try {
            DataInputStream is = new DataInputStream(
            new ByteArrayInputStream(data));
            is.readUTF( ); // Skip name
            int score = is.readInt( );
            // Match scores over 10000
            return score > 10000;
        } catch (IOException ex) {
            // Cannot read - no match
            return false;
        }
    }
};

Uma vez que o filtro foi criado, precisamos apenas passá-lo como o primeiro argumento do método enumerateRecors:

// Use the filter to get an enumeration that contains only
// a subset of the records in the Record Store
RecordEnumeration enum = store.enumerateRecords(filter, null, false);
// Print those players whose scores match the filter
while (enum.hasNextElement( )) {
    byte[] record = enum.nextRecord( );
    ByteArrayInputStream bais = new ByteArrayInputStream(record);
    DataInputStream is = new DataInputStream(bais);
    System.out.println("Name: <" + is.readUTF( ) + ">");
    System.out.println("Score: <" + is.readInt( ) + ">\n");
    is.close( );
}
enum.destroy( );

Para impor uma ordem os registros em uma RecordEnumeration você deve implementar a interface RecordComparator. Essa interface também só exige que você implemente o seguinte método:

public int compare(byte[] first, byte[] second)

Quando você passa um RecordComparator como parâmetro na construção de um RecordEnumeration, esse método é chamado toda vez que um par de registro é comparado. Como essa comparação é feita depende da estrutura do registro e dos critérios utilizados na ordenação da enumeration, ou seja, depende de você. Os seguintes valores podem ser retornados dependendo do resultado da comparação entre os registros:

  • RecordComparator.EQUIVALENT: os dois registros são iguais de acordo com os critério de ordenação
  • RecordComparator.PRECEDES: o primeiro registros deve vir primeiro que o segundo
  • RecordComparator.FOLLOWS: o segundo registro deve vir primeiro

Usando mais uma vez o Record Store do jogo, suponha que nós agora queremos que a enumeration esteja ordenada pela número de pontos de cada jogador em ordem decrescente (primeiro os jogadores que fizeram mais pontos até os que fizeram menos pontos). Aqui está um RecordComparator para essa ordenação:

// Sort an enumeration using a RecordComparator
RecordComparator comparator = new RecordComparator( ) {
    public int compare(byte[] first, byte[] second) {
        try {
            DataInputStream isFirst = new DataInputStream(
                        new ByteArrayInputStream(first));
            DataInputStream isSecond = new DataInputStream(
                        new ByteArrayInputStream(second));
            // Use descending order of scores.
            String firstName = isFirst.readUTF( );
            int firstScore = isFirst.readInt( );
            String secondName = isSecond.readUTF( );
            int secondScore = isSecond.readInt( );
	        if (firstScore != secondScore) {
             return firstScore > secondScore ?
                         RecordComparator.PRECEDES :
                         RecordComparator.FOLLOWS;
         }
         // When the scores are equal, sort based
         // on the player name.
         int comp = firstName.compareTo(secondName);
         if (comp == 0) {
             return RecordComparator.EQUIVALENT;
         } else if (comp < 0) {
             return RecordComparator.PRECEDES;
         } else {
             return RecordComparator.FOLLOWS;
         }
     } catch (IOException ex) {
         // Cannot read - claim that they match
         return RecordComparator.EQUIVALENT;
     }
   }
};

Algumas Limitações dos Record Stores

Record Stores são uma boa alternativa para contornar os problemas de portabilidade e recursos encontrados ao desenvolver aplicativos utilizando a plataforma Java ME. Porém eles não são perfeitos. 😥

O primeiro grande problema é que um dispositivo com uma certa quantidade de espaço disponível para persistência não dá nenhuma garantia que seus MIDlets podem usar muito desse espaço. Algumas chamadas de método podem não responder da forma esperada dependendo do dispositivo.

O segundo grande problema é o limite do tamanho de cada registro. Mais uma vez isso depende do dispositivo que será utilizado e algumas chamadas de métodos podem não retornar algo esperado.

Vendo essas limitações, tenha em mente que você deverá testar seus MIDlets em alguns dispositivos que sejam o alvo da sua aplicação. Mostrei uma maneira bacana de fazer isso no post anterior a esse: https://rvlaraujo.wordpress.com/2009/11/20/trabalhando-com-midlet-um-pouco-mais-reais/.

Espero que tenham entendido o importante conceito de persistência em dispositivos móveis na plataforma Java ME. Persistência é sempre importante em qualquer tipo de aplicação, então estudem mais um pouco esse conceito. Alguns links úteis:

http://java.sun.com/javame/reference/apis/jsr037/ – API javax.microedition.rms

http://developer.sonyericsson.com/getDocument.do?docId=66103 – site SonyEricsson

http://developer.motorola.com/docstools/articles/Optimizing_Applications_2_20061101.pdf/ – Optimizing a Java ME Application Part 2: RMS Sorting

http://developer.motorola.com/docstools/articles/RMS_Sharing_20060401.pdf/ – Sharing Record Stores in MIDlet Suites

http://developer.motorola.com/docstools/articles/RMS.pdf/ – Using RMS on Motorola Java-Enable Handsets

Referências:

J2ME in a Nutshell – Kim Topley

Wireless J2ME Plataform Programming – Vartan Piroumian

Beginning Java ME Platform – Ray Rischpater

Trabalhando com MIDlet um pouco mais “reais”.

1 Comentário

img ilustrativa

Desenvolver aplicativos para dispositivos móveis utilizando Java ME é bem simples. Primeiro você cria seu MIDlet, roda pelo Wireless Toolkit e se ele funcionar do modo esperado tudo bem, não é? Tudo isso muda até o dia em que você decide instalar seu MIDlet em um celular, ou melhor, dois celulares de modelos e fabricantes diferentes. Aí você percebe pequenas (ou grandes) diferenças na forma de navegação e como o MIDlet se comporta, às vezes fazendo algo que com certeza não era esperado. Mas como isso é possível se você testou ele antes utilizando os emuladores do Wireless Toolkit? A resposta é bem simples: A plataforma Java ME é apenas uma especificação, onde quem define realmente a maneira como o MIDlets irão se comportar dentro dos dispositivos são seus fabricantes (Motorola, SonyEriccson, Nokia, etc).

Então vem a pergunta: “Mas como diabos eu vou saber se meu MIDlet irá se comportar da forma adequada no meu celular ou no dos meus clientes?”. Existem duas formas, uma fácil e outra difícil:

  • Difícil: Testar em cada dispositivo e fazer os ajustes, testar novamente…
  • Fácil: Testar seus MIDlets nos SDKs que os fabricantes disponibilizam e que se aproximam muito da forma como os celulares reais irão tratar seus MIDlets.

Qual você prefere? Se a resposta foi a maneira fácil, acompanhe os passos abaixo e aprenda a utilizar o SDK da Motorola dentro do próprio Eclipse. Neste post estou levando em consideração que você já tem os plugins Java ME e/ou Pulsar instalados.

Instalando o MOTODEV SDK

Abra o Eclipse na perspectiva Java ME. Na parte de baixo (ver figura 1) clique na aba Mobile SDKs. Lá você irá encontrar uma série de SDKs dos grandes fabricantes como Motorola, Ericsson, Sony e Nokia. Vá até MOTODEV SDKs e dê um duplo clique no plugin MOTODEV Studio (figura 2).

Mobile SDKs

Figura 1

SDKs disponíveis

Figura 2

Após o Eclipse calcular tudo o que é preciso baixar, irá aparecer uma janela para instalação do plugin. Faça todo o processo de instalação norma de plugins e, assim que terminar, escolha para aplicar as mudanças.

Testando nosso MIDlets

Agora veja algo interessante: vá em WindowPreferencesJava MEDevice Management. O SDK da Motorola já está carregado com todos os seus dispositivos. Caso queira importar dispositivos de outros SDKs, como o Wireless Toolkit por exemplo, sinta-se a vontade.

Agora vamos ver como isso funciona na prática. Crie um novo MIDlet Project e coloque o nome de Teste Motorola SDK. Quando o projeto for criado, crie um novo pacote e dentro dele coloque um MIDlet chamado TesteMotorola e adicione a interface CommandListener. Olhe o código abaixo:

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.TextField;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class TesteMotorola extends MIDlet implements CommandListener
{

	private Display display;
	private Form loginForm;
	private Form sucessfulForm;
	private TextField tfUser;
	private TextField tfPassword;
	private StringItem stSucessful;
	private Command cmdLogin;
	private Command cmdExit;

	public TesteMotorola()
	{
		display = Display.getDisplay(this);

		tfUser = new TextField("User", " ", 30, TextField.ANY);
		tfPassword = new TextField("Password", "", 30, TextField.PASSWORD | TextField.ANY);

		cmdLogin = new Command("Login", Command.SCREEN, 0);
		cmdExit = new Command("Exit", Command.EXIT, 1);

		stSucessful = new StringItem(null, "Login Sucessful");

		loginForm = new Form("Login");
		loginForm.append(tfUser);
		loginForm.append(tfPassword);
		loginForm.setCommandListener(this);
		loginForm.addCommand(cmdLogin);
		loginForm.addCommand(cmdExit);

		sucessfulForm = new Form("Login Susceseful");
		sucessfulForm.append(stSucessful);
		sucessfulForm.setCommandListener(this);
		sucessfulForm.addCommand(cmdExit);

	}

	protected void destroyApp(boolean arg0)
		throws MIDletStateChangeException
	{
		// TODO Auto-generated method stub

	}

	protected void pauseApp()
	{
		// TODO Auto-generated method stub

	}

	protected void startApp() throws MIDletStateChangeException
	{
		// TODO Auto-generated method stub
		display.setCurrent(loginForm);
	}

	public void commandAction(Command c, Displayable d)
	{
		if (c == cmdLogin)
			display.setCurrent(sucessfulForm);
		else
			if (c == cmdExit)
			{
				// o aplicativo irá pedir para ser desativado.
				display.setCurrent(null);
				notifyDestroyed();
			}
	}

}

Até agora nada de novo, não é? Bem, agora clique com o botão direito no código-fonte e escolha Run AsRun Configuration. Clique na aba Emulation e marque a opção Specific Device. No menu Device escolha o dispositivo FLIP. Clique em Apply e depois em Run.

Escolhendo preferências de runtime

Emulador SDK Motorola

Legal não é? Agora podemos ver como nosso MIDlet iria se comportar em alguns modelos de celulares da Motorola. Quer mudar de dispositivo? Quer ver como ficariam seus MIDlets antigos nesses SDKs? Faça o mesmo processo de quando você clicou com o botão direito em cima do código e escolha o SDK e o dispositivo que você quiser.

Emulador MOTODEV Studio - FLIP

Assim como a Motorola, outros fabricantes também disponibilizam seus SDKs. Alguns sites interessantes:

Infelizmente a maioria é somente para o Microsoft Windows 😥 .

Este post vai ficando por aqui pessoal. Qualquer dúvida postem nos comentário ou mandem um email. Ficarei muito feliz em ajudar.

Até a próxima.

Older Entries