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 ...