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
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 {
009private:
010char baseName[256];
011IShell* pIShell;
012IDatabase* pIDatabase;
013IDBMgr* pIDBMgr;
014AEEDBField pDBFields[4];
015
016//4 campos: data, litros , preço , quilometragem
017typedef enum {
018DATE=AEEDBFIELD_NONE,
019GAS,
020GAS_PRIZE,
021TOTAL_KM
022} FUEL_MANAGER_FIELDNAME;
023
024//função privada
025int16 newRecord(AECHAR * date, float* litres, float* prize, int32* totalKm);
026
027public:
028
029DB(char* baseName, FuelManager *fuelManager);
030~DB();
031int16 open();
032int16 close();
033int16 insert(AECHAR * date, float* litres, float* prize, int32* totalKm);
034int16 remove(AECHAR * date);
035int16 update(AECHAR * date, float* litres, float* prize, int32* totalKm);
036boolean isEmpty();
037int16 size();
038void 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
005pIDBMgr = NULL;
006pIDatabase = NULL;
007pIShell = fuelManager->a.m_pIShell;
008
009if (ISHELL_CreateInstance(pIShell,AEECLSID_DBMGR, (void **) 010 &pIDBMgr)== SUCCESS){
011//Define date como char*
012pDBFields[0].fType = AEEDB_FT_STRING;
013pDBFields[0].fName = DATE;
014pDBFields[0].wDataLen = 15 * sizeof(char);
015pDBFields[0].pBuffer = NULL;
016
017//Define gas como float
018pDBFields[1].fType = AEEDB_FT_DWORD;
019pDBFields[1].fName = GAS;
020pDBFields[1].wDataLen = sizeof(float);
021pDBFields[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*
025prize, int32* totalKm){
026IDBRecord* pIDBRecordOut;
027AEEDBField fieldsOfTheRecord [4];
028pIDBRecordOut = NULL;
029int16 errorCode = 0;
030if (MEMCPY(fieldsOfTheRecord,pDBFields,sizeof (pDBFields)) != 031NULL){
032fieldsOfTheRecord[0].pBuffer=(void*)date;
033fieldsOfTheRecord[1].pBuffer=(void*)litres;
034fieldsOfTheRecord[2].pBuffer=(void*)prize;
035fieldsOfTheRecord[3].pBuffer=(void*)totalKm;
036pIDBRecordOut = IDATABASE_CreateRecord(pIDatabase,fieldsOfTheRecord,4);
037//libera o record
038IDBRECORD_Release( pIDBRecordOut );
039}else {
040errorCode = -1;//Problemas com getField!
041}
042return 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(){
002int16 errorCode = 0;
003//Criando o database
004if (pIDatabase == NULL){
005pIDatabase = IDBMGR_OpenDatabase(pIDBMgr,baseName,TRUE); 006
007if (pIDatabase == NULL){//já aberto?
008errorCode = -1;
009}
010}
011return errorCode;
012 }
013
014 int16 DB :: close(){
015int16 errorCode = 0;
016if (pIDatabase != NULL){
017IDATABASE_Release(pIDatabase);
018}
019return 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
004IDBRecord* pIDBRecord= NULL;
005AEEDBField fieldsOfTheRecord [4];
006AEEDBFieldType iFieldType;
007AEEDBFieldName iFieldName;
008uint16 iFieldLength;
009
010boolean found=FALSE;
011AECHAR* dateFound;
012int16 errorCode = 0;
013
014IDATABASE_Reset(pIDatabase);
015//Navega dentro da tabela
016while((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
022fieldsOfTheRecord[0].pBuffer=(void*)date;
023fieldsOfTheRecord[1].pBuffer=(void*)litres;
024fieldsOfTheRecord[2].pBuffer=(void*)prize;
025fieldsOfTheRecord[3].pBuffer=(void*)totalKm;
026IDBRECORD_Update(pIDBRecord,fieldsOfTheRecord,4);
027...
028//Após o update o código também é semelhante ao remove
029...
030IDBRECORD_Release( pIDBRecord );
031}//end while
032if (!found){
033errorCode = -1;
034}
035return 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(){
003return (IDATABASE_GetRecordCount(pIDatabase) == 0) ? TRUE : 004 FALSE;
005 }
006
007 int16 DB:: size(){
008return IDATABASE_GetRecordCount(pIDatabase);
009 }
010 ...