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.

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.

Interagindo com seus MIDlets: Commands

1 Comentário

Figura Demonstrativa

Olá pessoal! Já está na hora de começarmos a interagir com nossos MIDlets. Mas como fazemos isso utilizando a MIDP?

A API MIDP processa eventos através do uso da classe Command, que encapsula a forma como o usuário se comunica com a aplicação. Esse encapsulamento é feito através do registro de listeners, muito parecido com o processamento de eventos do Java AWT. Resumidamente pense em um Command como um botão do controle remoto da sua televisão: assim que você aperta um botão ela troca de canal; você não sabe e nem te interessa como ela faz isso, só interessa o resultado (isso do ponto de vista do usuário, mas interessa como é feito através do ponto de vista de nós programadores).

Para utilizar um Command, os objetos MIDP que estiverem interessados a interagir com eventos devem implementar a interface CommandListener, que provê o método commandAction. Dentro desse método é definido o que irá acontecer assim que uma instância de um Command for acionada, ou seja, é onde definimos o comportamento que as classes que se utilizam de Commands irão assumir. Voltando a associação com o controle remoto, o método commandAction é onde iremos definir que canal será escolhido, ou seja, o comportamento que será assumido pela tecla assim que for pressionada.

Commands podem se adicionados a qualquer objeto Displayable com exceção de instancias da classe Alert. Eles são organizados como botões ou entradas em um sistema de menu (ver figura abaixo). [1]

Commands em um Displayable

Figura 1

Um Command possui três atributos que são passados quando ele é instanciado:

  • Label: O nome com o qual o Command será apresentado ao usuário
  • Type: Uma constante que indica a espécie do comando, tanto para a aplicação quanto para a UI do MIDP em tempo de execução. Pode apresentar informação adicional (como um ícone) sobre o Command. Os principais tipos podem ser vistos na tabela 1. Alguns tipos especiais são mapeados diretamente para as teclas apropriadas do dispositivo, como BACK, CANCEL, etc.
  • Priority: um int que indica a importância do Command em relação aos outro Commands na UI. Quanto menor o valor, maior a prioridade (0 é mais importante que 1, por exemplo).
Tabela 1
Command Type Description Descrição
BACK Retorna para a tela anterior
CANCEL Reposta negativa padrão para um diálogo
EXIT Sai da aplicação
HELP Requisita ajuda
ITEM Exibe um pequeno hint sobre um item selecionado
OK Resposta de confirmação padrão a um diálogo
SCREEN Um commando definido e mostrado pela própria aplicação
STOP Pára uma operação em execução

Explicado tudo isso, vamos para a prática. Crie um novo MIDlet e chame-o de RemoteControlMIDlet e adicione a interface CommandListener.

Selecionando uma interface

Selecionando a interface CommandListener

As figuras utilizadas no MIDlet exemplo são apenas um enfeite e uma forma de não fugir da comparação com o controle remoto ;).

Olhe o código completo abaixo:

import java.io.IOException;

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.Image;
import javax.microedition.lcdui.StringItem;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class CommandExampleMIDlet extends MIDlet implements CommandListener
{

	private Display display;
	private Form channelOne;
	private Form channelTwo;
	private Form channelThree;

	private Command cmdGoSBT;
	private Command cmdGoGlobo;
	private Command cmdGoRecord;

	private Image plimimg;
	private Image supernaturalimg;
	private Image csiimg;

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

		try
		{
			plimimg = Image.createImage("/plim-plim.jpg");
			supernaturalimg = Image.createImage("/supernatural.jpg");
			csiimg = Image.createImage("/csi.jpg");
		} catch (IOException e) {
			// TODO: handle exception
		}

		cmdGoGlobo = new Command("Globo", Command.SCREEN, 0);
		cmdGoSBT = new Command("SBT", Command.SCREEN, 1);
		cmdGoRecord = new Command("Record", Command.SCREEN, 2);

		channelOne = configureScreen(channelOne, "Globo", new StringItem(null, "Plim-plim"), cmdGoSBT, cmdGoRecord, plimimg);
		channelTwo = configureScreen(channelTwo, "SBT", new StringItem(null, "Sobrenatural - Supernatural"), cmdGoGlobo, cmdGoRecord, supernaturalimg);
		channelThree = configureScreen(channelThree, "Record", new StringItem(null, "C.S.I. - Investigação Criminal"), cmdGoSBT, cmdGoGlobo, csiimg);
	}

	protected void destroyApp(boolean unconditional)
			throws MIDletStateChangeException
	{

	}

	protected void pauseApp()
	{

	}

	protected void startApp() throws MIDletStateChangeException
	{
		display.setCurrent(channelOne);
	}

	private Form configureScreen(Form fm, String title,
			StringItem text, Command cmd1, Command cmd2, Image img)
	{
		fm = new Form(title);

		fm.append(text);
		fm.append(img);
		fm.addCommand(cmd1);
		fm.addCommand(cmd2);
		fm.setCommandListener(this);

		return fm;
	}

	public void commandAction(Command c, Displayable d)
	{
		if (c == cmdGoSBT)
			display.setCurrent(channelTwo);
		else
			if (c == cmdGoGlobo)
				display.setCurrent(channelOne);
			else
				display.setCurrent(channelThree);
	}

}

Observe alguns screenshots do aplicativo funcionando:

Aplicativo Exemplo em Ação

Aplicativo Exemplo em Ação

Aplicativo Exemplo em Ação

Aplicativo Exemplo em Ação

Aplicativo Exemplo em Ação

Agora vamos as principais partes do código. No construtor do MIDlet são instanciados 3 Commands, cada um com seu Label, todos com o Type SCREEN e com prioridades diferentes. As prioridades diferentes são uma questão especial aqui, pois foi criado o método configureScreen que serve como um lugar comum para a configuração dos Forms utilizados. O ideal seria colocar prioridades diferentes apenas para os Commands que fazem parte da mesma instância de um determinado Displayable.

cmdGoFirstScreen = new Command("Globo", Command.SCREEN, 0);
cmdGoSecondScreen = new Command("SBT", Command.SCREEN, 1);
cmdGoThirdScreen = new Command("Record", Command.SCREEN, 2);

O método configureScreen, como já foi dito acima, serve como um lugar comum para a configuração dos Forms utilizados. O que nós podemos perceber de novidade nele é:

fm.addCommand(Command cmd)
fm.setCommandListener(this)

O primeiro comando adiciona um Command a nossa instância de Form para que o mesmo possa interagir com o usuário. O segundo comando faz com que os eventos disparados sejam capturados e tratados por nosso próprio MIDlet uma vez que ele é quem implementa a interface CommandListener. O outro método de nosso interesse é o actionListener:

public void commandAction(Command c, Displayable d)
{
	if (c == cmdGoSBT)
		display.setCurrent(channelTwo);
	else
		if (c == cmdGoGlobo)
			display.setCurrent(channelOne);
		else
			display.setCurrent(channelThree);
}

Ele é responsável por tratar os eventos capturados pelos listeners. Ele recebe dois parâmetros: um Command e seu respectivo “dono”, que sempre é uma instancia de Displayable. O resto é trivial: existe uma sequencia de testes if…else que perguntam qual o Command acionado e realizam a ação ao qual ele foi designado, que no nosso caso é simplesmente mudar de uma tela para outra.

Uma coisa interessante e importante a ser notada quando desenvolvemos para diverssos dispositivos diferentes é que a maneira como os Commands serão apresentados e por quais teclas eles serão disparados dependem do fabricante. Celulares da Motorola trantam os Commands de maneira diferente dos celulares da SonyEricsson e assim por diante.

Até a próxima.

Referências

[1] Wireless J2ME Plataform Programming

Beginning Java ME Platform.

Componentes Visuais CLDC – parte 2

Deixe um comentário

proj1-07

Dando sequencia a nossa série sobre o desenvolvimento para Java ME e, como prometido no episódio passado, serão explorar algumas das classes que podem compor uma instância da classe Form.

Olhando a figura abaixo podemos perceber que todas as classes que representam componentes gráficos, com exceção de Canvas, são filhas da classe Item.

Relação entre Form e as subclasses de Item

Relação entre Form e as subclasses de Item

Item

A classe Item encapsula as seguintes responsabilidades:

  • Gerenciamento de Commands: falerei de Commands em um outro post.
  • Desenho: uma instancia de Item e qualquer uma de suas subclasse sabe como desenhar a si próprio.
  • Manipulação de Eventos: manipular eventos de baixo nível como o digitar de tecas.
  • Preferências de Layout: uma instancia de Item sinaliza ao Form em que está contido sobre como ele dever ser ‘acomadado’. Essa sinalização é utilizada pela política de layout do Form para apresentar todos os itens da forma mais coesa possível.

A seguir encontra-se o básico de algumas classes que estendem Item. Para isso, crie um novo MIDlet project no Eclipse e um pacote chamado gui. Dentro desse pacote iremos criar algumas classes de demonstração dos componentes visuais CLDC.

StringItem

Um StringItem é uma classe que permite adicionar a um Form um componente de texto read-only com um rótulo (Label) e um texto (String). Ao instanciar um StringItem você pode definir três parâmetros: um Label (String), um Texto (String) e um Appearence (constante). Esse último parâmetro pode ser um dos seguintes valores: StringItem.PLAIN, StringItem.HYPERLINK ou StringItem.BUTTON.

No código abaixo não irei passar nenhum parâmetro do tipo appearence pois, para que eles fiquem realmente visíveis é necessários que estejam associados a algum Command. Também estou procurando deixar as coisas o mais simples possível 😉 .


public class StringItemExemplo extends MIDlet
{

	private Display display;
	private Form myForm;
	private StringItem si1, si2, si3;

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

		si1 = new StringItem("Nome: ", "Rafael Viana Lopes Araújo");
		si2 = new StringItem(null, "Esse é um StringItem sem um Label");
		si3 = new StringItem("Um Label sem um Texto", null);

        	// Array de Item que será passado ao construtor de myForm.
		Item[] stringItems = new Item [] {si1, si2, si3};

		// Outro modo de instanciar um Form.
		myForm = new Form("StringItems", stringItems);</strong>
	}

	protected void destroyApp(boolean arg0) throws MIDletStateChangeException
	{
		// nada aqui
	}

	protected void pauseApp()
	{
		// nada aqui
	}

	protected void startApp() throws MIDletStateChangeException
	{
		display.setCurrent(myForm);
	}

}

Exemplos de StringItem

Exemplos de StringItem

Spacer

A classe Spacer existe para que você possa especificar uma distância (altura e largura) entre os componentes gráficos. Bem simples.

Olhe o código abaixo e veja a simplicidade. É só especificar uma altura e largura no construtor e pronto!

public class SpacerExemplo extends MIDlet
{

	private Display display;
	private Form myForm;
	private StringItem si1, si2;
	private Spacer mySpacer;

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

		si1 = new StringItem("si1", "String item 1");
		si2 = new StringItem("si2", "String item 2");

                // 0 pixels na horizontal e 20 pixels na vertical</strong>
		mySpacer = new Spacer(0, 20);

		myForm = new Form("Spacer");
		myForm.append(si1);
		myForm.append(si2);
		myForm.append(mySpacer); // adicione o Spacer por último!</strong>
	}

	protected void destroyApp(boolean unconditional)
			throws MIDletStateChangeException
	{
		// nada aqui
	}

	protected void pauseApp()
	{
		// nada aqui
	}

	protected void startApp() throws MIDletStateChangeException
	{
		display.setCurrent(myForm);
	}

}
Pequeno espaçamento entre os dois StringItems

Pequeno espaçamento entre os dois StringItems

TextField

Classe utilizada para a entrada de texto. Ela pode conter algumas restrições, como a entrada somente de texto, somente de números, texto com números etc. TextFiled também permite que o texto seja formatado automaticamente através do uso de algumas constantes. Você também pode utilizar uma combinação de constantes com formatações com a utilização do operador | .

Olhando o código abaixo isso ficará mas evidente.

public class TextFieldExemplo extends MIDlet
{

	private Display display;
	private Form myForm;
	private TextField simpleTextField;
	private TextField restrictedTextField;
	private TextField formatedTextField;
	private TextField mixedTextField;

	public TextFieldExemplo()
	{

		display = Display.getDisplay(this);

		simpleTextField = new TextField("Simples", "", 30, TextField.ANY);

		restrictedTextField = new TextField("Restricted", "", 30, TextField.NUMERIC);

		formatedTextField = new TextField("Formated", "", 35, TextField.EMAILADDR);

		mixedTextField = new TextField("Mixed", "", 30,
				TextField.SENSITIVE | TextField.PASSWORD);

		Item[] items = new Item[] {simpleTextField, restrictedTextField,
				formatedTextField, mixedTextField};

		myForm = new Form("Exemplos de TextField", items);
	}

	protected void destroyApp(boolean unconditional)
			throws MIDletStateChangeException
	{
		// nada aqui
	}

	protected void pauseApp()
	{
		// nada aqui
	}

	protected void startApp() throws MIDletStateChangeException
	{
		display.setCurrent(myForm);
	}

}

Exemplos de TextField

Exemplos de TextField

Observe que no construtor de um TextField são passados quatro parâmetros: um rótulo (Label), um texto inicial quando o TextField for iniciado, o número máximo de caracteres armazenados no TextField e o tipo restrições e formatações aceitas.

DateField

Utilizando essa classe você pode obter do usuários entradas no formato de data e/ou hora, especificando que modo de entrada (input mode), que pode ser DATE, TIME ou DATE_TIME.

public class DateFieldExemplo extends MIDlet
{

	private Display display;
	private Form myForm;
	private DateField calendar;
	private DateField currentDate;

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

		calendar = new DateField("Calendario", DateField.DATE_TIME);

		currentDate = new DateField("Data atual", DateField.DATE);
		currentDate.setDate(new Date());

		myForm = new Form("Date Field");
		myForm.append(calendar);
		myForm.append(currentDate);
	}

	protected void destroyApp(boolean unconditional)
			throws MIDletStateChangeException
	{
		// nada aqui
	}

	protected void pauseApp()
	{
		// nada aqui
	}

	protected void startApp() throws MIDletStateChangeException
	{
		display.setCurrent(myForm);
	}

}
DateField inicial

DateField inicial

Escolhendo uma hora pelo DateField

Escolhendo uma hora pelo DateField

Escolhendo uma data

Escolhendo uma data

Gauge

Representa um intervalo entre números inteiros, geralmente zero (0) e algum número máximo pré-definido. Ela pode ser interativa ou apenas mostrar informações sem qualquer tipo de interação.

Essa classe é como uma espécie de marcador ou controle analógico. Geralmente quando você vai aumentar o volume do seu celular ela está lá, lembra?

public class GaugeExemplo extends MIDlet
{

	private Display display;
	private Form myForm;
	private Gauge interactiveGauge;
	private Gauge staticGauge;

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

		interactiveGauge = new Gauge("Interativo", true, 10, 0);

		staticGauge = new Gauge("Estático", false, 8, 5);

		Item[] items = new Item[] {interactiveGauge, staticGauge};

		myForm = new Form("Gauge", items);
	}

	protected void destroyApp(boolean unconditional)
			throws MIDletStateChangeException
	{
		// nada aqui.
	}

	protected void pauseApp()
	{
		// nada aqui.
	}

	protected void startApp() throws MIDletStateChangeException
	{
		display.setCurrent(myForm);
	}

}
Exemplos de Gauge interativo e estático

Exemplos de Gauge interativo e estático

O construtor da classe recebe quatro parâmetros: um rótulo, um valor boolean para definir se ela será interativa (true) ou não (false), um valor máximo e um valor inicial.

ChoiceGroup

Permite mostrar listas de escolha para o usuário. Essas listas podem tomar a forma de Radio Buttons, Check Boxes ou Pop-up, dependendo dos parâmetros passados ao construtor da classe.

Quando instanciamos um ChoiceGroup podemos escolher entre os sequintes tipos:

  • EXCLUSIVE: apenas um item pode ser selecionado por vez, mas vários itens serão mostrados. A lista toma forma de Radio Buttons.
  • MULTIPLE: zero ou mais itens podem ser selecionados de uma vez. A lista toma forma de Check Boxes.
  • POP-UP: apenas um elemento é selecionado e apenas o elemento selecionado é mostrado. A lista toma forma de de um Pop-up.

Você também pode especificar imagens para cada item da lista, mas não iremos mostrar isso agora. Irei falar em outro post sobre imagens e a classe ImageItem que necessitam de uma atenção especial.

Para adicionar elementos a uma lista você utiliza o método append(String stringPart, Image imagePart) em que o primeiro parâmetro é o nome do elemento da lista e o segundo é uma imagem vinculada a esse elemento. Olhe o código abaixo e tudo ficará mais claro.

public class ChoiceGroupExemplo extends MIDlet
{

	private Display display;
	private Form myForm;
	private ChoiceGroup exclusiveCG;
	private ChoiceGroup multipleCG;
	private ChoiceGroup popupCG;

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

		exclusiveCG = new ChoiceGroup("Exclusive", ChoiceGroup.EXCLUSIVE);

		// adicionando elementos à lista
		exclusiveCG.append("Choice 1", null);
		exclusiveCG.append("Choice 2", null);
		exclusiveCG.append("Choice 3", null);
		exclusiveCG.append("Choice 4", null);

		multipleCG = new ChoiceGroup("Multiple", ChoiceGroup.MULTIPLE);

		// adicionando elementos à lista
		multipleCG.append("Choice 1", null);
		multipleCG.append("Choice 2", null);
		multipleCG.append("Choice 3", null);
		multipleCG.append("Choice 4", null);

		popupCG = new ChoiceGroup("Pop-up", ChoiceGroup.POPUP);

                popupCG.append("Choice 1", null);
		popupCG.append("Choice 2", null);
		popupCG.append("Choice 3", null);
		popupCG.append("Choice 3", null);

		Item[] items = new Item[] {exclusiveCG, multipleCG, popupCG};

		myForm = new Form("ChoiceGroup", items);
	}

	protected void destroyApp(boolean unconditional)
			throws MIDletStateChangeException
	{
		// nada aqui.
	}

	protected void pauseApp()
	{
		// nada aqui.
	}

	protected void startApp() throws MIDletStateChangeException
	{
		display.setCurrent(myForm);
	}

}
Exemplos de ChoiceGroup

Exemplos de ChoiceGroup

Aí estão algumas das classes de GUI mais utilizadas em Java ME. Espero que essa pequena introdução tenha ajudado. Para um melhor proveito, tentem modificar os códigos apresentados e façam testes, mudem métodos e observem como os MIDlets se comportam.

A API completa da classe Item e das suas subclasses pode ser encontrada no link abaixo:

http://java.sun.com/javame/reference/apis/jsr118/

Foi mostrado o básico de StringItem, Spacer, TextField, DateField, Gauge e ChoiceGroup. Ainda faltaram ImageItem e CustomItem. No próximo post irei falar sobre ImageItem e deixarei CustomItem um pouco de lado, pois estou apenas fazendo uma breve introdução à Java ME. As classes de GUI CLDC serão melhor exploradas após conhecer algo que irá transformar o modo de interagir com seus MIDlets: Commans.

Até a próxima. 😉

Referências:

[1] Beginning Java ME Platform.

Older Entries