Desenvolvendo em BREW - Parte 3 – Etapa 03
Estudo de Caso: Persistência no “Fuel Manager”
Após apresentarmos as principais funcionalidades de cada uma dessas APIs, vamos agora aplicá-las no contexto do estudo de caso Fuel Manager (o código completo da aplicação pode ser encontrado para download no portal da WebMobile).
Para facilitar o manuseio da persistência na nossa aplicação, vamos criar uma classe C++ que será responsável pelo gerenciamento da persistência em registro, de modo que as classes que controlam as telas da aplicação não tenham que conhecer as funções da API de registros em BREW. Para tal, vamos criar um arquivo de cabeçalho DB.h onde definiremos todas as estruturas necessárias para o funcionamento da nossa classe, que também vai se chamar DB.
Como vimos no começo deste artigo, nossos registros possuirão quatro campos: data de abastecimento, litros de gasolina, preço do litro da gasolina, e quilometragem. Para facilitar as buscas, usaremos o campo de data como chave (Data e hora), ou seja, vamos restringir o uso da aplicação permitindo apenas um abastecimento por dia/hora.
Na Listagem 2 temos o código de DB.h. Neste arquivo criamos a classe DB com suas funções-membro, e um tipo enumeração FUEL_MANAGER_FIELDNAME (Listagem 2 - linha 17) para facilitar a criação dos campos de cada registro.
O construtor e o destrutor servirão para inicializar e liberar as variáveis. As funções open e close servem para abrir e fechar a tabela; insert serve para inserir um novo registro e update para atualizações (Listagem 2 – seção public:). Para remoções usaremos o método remove e deleteDB para remover a tabela fisicamente do aparelho (Listagem 2 – seção public:). O leitor pode criar novas funções para realizar buscas pelos outros campos da tabela. Para consultar se a tabela está vazia e a quantidade de registros, devem ser chamados os métodos isEmpty e size (Listagem 2 – seção public:). Grande parte dos métodos retorna valores do tipo int16, como uma forma de indicar ao usuário da classe se a operação foi bem sucedida (retornando zero) ou mal sucedida (retornando -1).
Listagem 2. Conteúdo do DB.h
001 #ifndef DB_HEADER
002 #define DB_HEADER
003
004 #include "AEEStdLib.h"
005 #include "AEEDB.h"
006 #include "FuelManager.h" // Header principal da aplicação
007
008 class DB {
009 private:
010 char baseName[256];
011 IShell* pIShell;
012 IDatabase* pIDatabase;
013 IDBMgr* pIDBMgr;
014 AEEDBField pDBFields[4];
015
016 //4 campos: data, litros , preço , quilometragem
017 typedef enum {
018 DATE=AEEDBFIELD_NONE,
019 GAS,
020 GAS_PRIZE,
021 TOTAL_KM
022 } FUEL_MANAGER_FIELDNAME;
023
024 //função privada
025 int16 newRecord(AECHAR * date, float* litres, float* prize, int32* totalKm);
026
027 public:
028
029 DB(char* baseName, FuelManager *fuelManager);
030 ~DB();
031 int16 open();
032 int16 close();
033 int16 insert(AECHAR * date, float* litres, float* prize, int32* totalKm);
034 int16 remove(AECHAR * date);
035 int16 update(AECHAR * date, float* litres, float* prize, int32* totalKm);
036 boolean isEmpty();
037 int16 size();
038 void deleteDB(const char* baseName);
039 };
040
041 #endif //DB_HEADER
Um arquivo BD.cpp é criado para oferecer a implementação das funções declaradas na classe BD definida na Listagem 2. Note que no construtor, precisamos do parâmetro do tipo FuelManager, cuja definição foi dada no primeiro artigo da série “Desenvolvendo em BREW” (Edição 12 da WebMobile). É esse objeto que nos dá acesso ao objeto IShell, necessário para criar uma instância de IDBMgr.
No construtor nós também criamos cada campo de um registro, com auxílio do array pDBFields (Listagem 3). Quando um registro for criado na função newRecord (Listagem 3), bastará tirar uma cópia deste array com MEMCPY, preencher os buffers com o valor do registro e inserir na tabela com IDATABASE_CreateRecord. O destrutor libera o objeto IDBMgr.
Listagem 3. Implementação do contrutor e método newRecord do arquivo DB.cpp
001 ...
002
003 DB :: DB(char* bName, FuelManager *fuelManager){
004 //Criando o DB manager
005 pIDBMgr = NULL;
006 pIDatabase = NULL;
007 pIShell = fuelManager->a.m_pIShell;
008
009 if (ISHELL_CreateInstance(pIShell,AEECLSID_DBMGR, (void **) 010 &pIDBMgr)== SUCCESS){
011 //Define date como char*
012 pDBFields[0].fType = AEEDB_FT_STRING;
013 pDBFields[0].fName = DATE;
014 pDBFields[0].wDataLen = 15 * sizeof(char);
015 pDBFields[0].pBuffer = NULL;
016
017 //Define gas como float
018 pDBFields[1].fType = AEEDB_FT_DWORD;
019 pDBFields[1].fName = GAS;
020 pDBFields[1].wDataLen = sizeof(float);
021 pDBFields[1].pBuffer = NULL;
022 ...
023 //O mesmo procedimento para valor e quilometragem
024 }
025 }
026 ...
027 Outros Métodos
028 ...
029 int16 DB :: newRecord(AECHAR * date, float* litres, float*
025 prize, int32* totalKm){
026 IDBRecord* pIDBRecordOut;
027 AEEDBField fieldsOfTheRecord [4];
028 pIDBRecordOut = NULL;
029 int16 errorCode = 0;
030 if (MEMCPY(fieldsOfTheRecord,pDBFields,sizeof (pDBFields)) != 031 NULL){
032 fieldsOfTheRecord[0].pBuffer=(void*)date;
033 fieldsOfTheRecord[1].pBuffer=(void*)litres;
034 fieldsOfTheRecord[2].pBuffer=(void*)prize;
035 fieldsOfTheRecord[3].pBuffer=(void*)totalKm;
036 pIDBRecordOut = IDATABASE_CreateRecord(pIDatabase,fieldsOfTheRecord,4);
037 //libera o record
038 IDBRECORD_Release( pIDBRecordOut );
039 }else {
040 errorCode = -1;//Problemas com getField!
041 }
042 return errorCode;
043 }
Na função open, fazemos uso da função IDBMGR_OpenDatabase para abrir nossa tabela (Listagem 4). Caso a mesma já esteja aberta retornamos um código de erro (em nosso exemplo, -1). Já a função close chama IDATABASE_Release para liberar a tabela.
Listagem 4. Implementação da função open de DB.cpp
001 int16 DB :: open(){
002 int16 errorCode = 0;
003 //Criando o database
004 if (pIDatabase == NULL){
005 pIDatabase = IDBMGR_OpenDatabase(pIDBMgr,baseName,TRUE); 006
007 if (pIDatabase == NULL){//já aberto?
008 errorCode = -1;
009 }
010 }
011 return errorCode;
012 }
013
014 int16 DB :: close(){
015 int16 errorCode = 0;
016 if (pIDatabase != NULL){
017 IDATABASE_Release(pIDatabase);
018 }
019 return errorCode;
020 }
Para a remoção, busca e atualização de um registro, é necessário navegar entre os registros da tabela iterativamente através de um laço while, chamando IDATABASE_GetNextRecord (Listagem 5, linha 016). Para cada registro, o campo de data (escolhido como chave) é recuperado através da função IDBRECORD_GetFieldString que nos retorna uma string do tipo AECHAR* que representa a data do abastecimento. Esse valor recuperado é comparado com a data a ser encontrada (nosso critério de busca).
Listagem 5. Implementação do método update no DB.cpp
001 ...
002 int16 DB :: update(AECHAR * date, float* litres, float* prize, int32* totalKm){
003
004 IDBRecord* pIDBRecord= NULL;
005 AEEDBField fieldsOfTheRecord [4];
006 AEEDBFieldType iFieldType;
007 AEEDBFieldName iFieldName;
008 uint16 iFieldLength;
009
010 boolean found=FALSE;
011 AECHAR* dateFound;
012 int16 errorCode = 0;
013
014 IDATABASE_Reset(pIDatabase);
015 //Navega dentro da tabela
016 while((pIDBRecord=IDATABASE_GetNextRecord(pIDatabase))!=NULL)
017 {
018 ...
019 //Código de busca do registro semelhante ao remove
020 ...
021 //Uma vez achado o registro atualizamos seu conteúdo
022 fieldsOfTheRecord[0].pBuffer=(void*)date;
023 fieldsOfTheRecord[1].pBuffer=(void*)litres;
024 fieldsOfTheRecord[2].pBuffer=(void*)prize;
025 fieldsOfTheRecord[3].pBuffer=(void*)totalKm;
026 IDBRECORD_Update(pIDBRecord,fieldsOfTheRecord,4);
027 ...
028 //Após o update o código também é semelhante ao remove
029 ...
030 IDBRECORD_Release( pIDBRecord );
031 }//end while
032 if (!found){
033 errorCode = -1;
034 }
035 return errorCode;
036 }
037 ...
No caso da função remove, ao encontrar o registro desejado chamamos IDBRECORD_Remove; para atualização, tiramos uma cópia do array pDBFields e chamamos IDBRECORD_Update; para as buscas por litros de gasolina e de álcool, os valores dos campos correspondentes eram obtidos com IDBRECORD_GetField e retornados pela função. Note que sempre que chamamos IDATABASE_GetNextRecord, uma instância de IDBRecord é criada. Isso significa que sempre que esta instância não for mais utilizada pela função temos sempre que liberá-la com IDBRECORD_Release, pois do contrário, o emulador indicará um problema de memory leak ou vazamento de memória (uma vez alocada memória é preciso desalocar quando não for mais necessária) e a aplicação não será encerrada corretamente.
Para as funções isEmpty e size (Listagem 6), fazemos uso da função IDATABASE_GetRecordCount para verificar o número de registros da nossa tabela. Finalmente, a função deleteDB se encarrega de fechar a tabela com IDATABASE_Release e removê-la fisicamente com IDBMGR_Remove.
Listagem 6. Implementação de isEmpty() e size() no DB.cpp.
001 ...
002 boolean DB:: isEmpty(){
003 return (IDATABASE_GetRecordCount(pIDatabase) == 0) ? TRUE : 004 FALSE;
005 }
006
007 int16 DB:: size(){
008 return IDATABASE_GetRecordCount(pIDatabase);
009 }
010 ...