_sqlite Transparecendo a persistência de dados em Android com MVC Conhecendo o funcionamento do SQLite e aplicando as práticas do modelo MVC no desenvolvimento da camada DAO. Douglas Cavalheiro douglas.cavalheiro.br@gmail.com Bacharel em Sistemas de Informação, trabalha com Java desde 2009, focado no desenvolvimento Android a partir de 2011. Desenvolveu a biblioteca DroidPersistence para persistência em SQLite na S.O. para dispositivos móveis do Google. Muitas aplicações comerciais estão sendo voltadas à mobilidade. Empresas já vêm migrando parte de seus módulos para tablets ou celulares, principalmente devido à facilidade de manter esses aparelhos sempre conectados e à mão. A grande vantagem hoje de portar aplicações, ou parte delas, para tablets é todo o ambiente oferecido ao desenvolvedor, mesmo que limitado a desempenho, mas funcional. É o caso do Android SDK que nos permite escrever aplicações na linguagem Java e integrar com o banco de dados SQLite, lembrando que é possível utilizar outras formas de storage como DB4Object. O conceito MVC se aplica muito bem em projetos deste porte, pois nos permite organizar e distribuir parte das camadas para demais projetos ou envolver na nova aplicação outros projetos já amadurecidos que estejam nesta estrutura (Modelo, Visão e Controle). Aplicaremos este conceito de forma transparente e agradável para manutenções claras e com resultados bem consolidados ao longo do tempo. Temos anseio de iniciar o mais breve possível a codificação quando se trata de novas tecnologias como o Android, mas o resultado fica ainda melhor quando programar não é a preocupação principal e sim o objetivo a ser alcançado da forma mais simples possível. Planejar uma aplicação móvel que terá constante interação com base de dados torna-se uma atividade complexa se não houver métricas de desenvolvimento, assim como em qualquer outro sistema, é importante resgatarmos todas as boas práticas que vamos adquirindo ao longo do tempo e adequá-las quando formos ingressar para um novo ambiente. Olhando pelo lado do desenvolvedor, a garantia de um bom resultado no novo projeto é o sucesso das aplicações desenvolvidas anteriormente. Portanto, antes de iniciar o projeto, analise sua aplicação numa linha que inicie do ambiente real com regras de negócio e termine na implementação, esta observação parece filosófica, mas sugere uma visão menos técnica e voltada ao mundo real, tendo por consequência as expectativas do interessado (usuário) sendo atendidas da melhor forma possível pelo analista. Definindo o Projeto Seguindo a arquitetura MVC, além da view, a camada de Business Object ou Service irá realizar validações e repassar para os objetos DAO as funções de CRUD. A layer DAO terá uma estrutura com interfaces que declaram os métodos que serão implementados por cada DAO, isto permite flexibilidade para projetos não Android e homogenização da interação de dados. Também, é claro, o modelo que será nossa classe Cliente. Em qualquer dúvida, o projeto de exemplo está disponível no site da MundoJ. DAO Uma ótima prática para utilização de DAOs é a criação de uma interface que declara os métodos que irão ser utilizados. O motivo? O reúso. O objeto DAO que implementar esta interface poderá ter implementação diferente, mas as assinaturas dos métodos serão as mesmas. Conforme o que é mostrado por Silveira[2], programar voltado à interface e não a implementação é uma das boas práticas de orientação a objetos. Um exemplo numa aplicação Java é criar os métodos na interface, podemos codificar uma classe DAO que utiliza Connection e outra que utiliza o EntityManager da JPA que implementam a interface. São implementações diferentes, mas o objetivo das funções é retornar o mesmo resultado. Além do reúso, usar interfaces explica a linha de partir do menos 33 \
Quem já está acostumado a desenvolver em Java com certeza já se deparou com atividades de acesso aos dados e usou JDBC com suas factories (Connection ou implementações JPA, por exemplo) para desenvolver os famosos DAOs. No sistema Android, não temos o mesmo fluxo oferecido nas aplicações Java web ou desktop, mas podemos adequar boas práticas e minimizar o código para a camada de persistência e consulta de dados. técnico para o mais abrangente (implementação) durante o desenvolvimento para estar mais próximo do negócio neste início de construção. Listagem 1. Criação da Interface DAO. package br.com.cadastroclientes.dao; import java.util.list; import br.com.cadastroclientes.model.cliente; public interface IClienteDao { Cliente getbynome(string nome); Cliente getbyid(long id); List<Cliente> listall(); boolean savecliente(cliente cliente); boolean deletecliente(long id); boolean updatecliente(cliente cliente); O código da Listagem 1 deixa claro que não há dependência nenhuma de bibliotecas ou outras classes fora do contexto do projeto. Para projetos multiplataforma, a interface DAO, a camada modelo e a camada de negócio são candidatas a um projeto separado. Imagine um projeto Web e Android que ambos devem cadastrar clientes, não fica prático copiar e colar o código de um projeto a outro. Numa simples manutenção no modelo, este deverá ser alterado em ambos os projetos, não queremos isso, por fim criamos um projeto core que mantém toda esta estrutura e adicionamos este projeto ao build path do projeto mobile e no projeto web. Implementando o DAO Aqui se inicia a implementação da interface usando as particularidades do SDK Android. Num DAO Android a classe deve, além de implementar a interface, estender SqliteOpenHelper, pois esta classe nos permite interagir com o objeto Database, do qual provém as ações CRUD. Consequentemente o SqliteOpenHelper traz alguns métodos para serem sobrescritos. Listagem 2. Criação da Classe Dao. public class ClienteDao extends SQLiteOpenHelper implements IClienteDao{ private SQLiteDatabase database; /**Construtor*/ public ClienteDao(Context context, String name, CursorFactory factory,int version) { super(context, name, factory, version); database = getwritabledatabase(); No Construtor, encontramos parâmetros que devem ser mantidos, tais como Context, que é o contexto da aplicação, sendo obtido nas classes de Activity e Application do Android. O nome do banco de dados, o CursorFactory (este, é um criador de query, no caso da aplicação de exemplo não será usado, sendo passado null) e a versão do banco de dados, esta versão é importante caso queira realizar um novo build da aplicação com alterações de estrutura ou inserção de dados, a criação de um novo campo na tabela, por exemplo. Logo abaixo temos o objeto privado SQLiteDatabase sendo instanciado pelo método getwritabledatabase() este método abre o banco de dados para uso e o cria se necessário. Como é criado o banco de dados da aplicação? E como criamos as estruturas mencionadas anteriormente? A classe SQLiteOpenHelper nos dá uma visão melhor de como a mágica acontece com os métodos de OnCreate e onupgrade (Listagem 3). Listagem 3. Métodos que criam a tabela e atualizam se necessário. /**Método chamado quando o banco de dados é criado*/ / 34
public void oncreate(sqlitedatabase db) { StringBuilder sb = new StringBuilder(); sb.append( create table if not exists CLIENTE( ); sb.append( ID integer not null primary key autoincrement, ); sb.append( NOME varchar(50), ); sb.append( SOBRENOME varchar(50), ); sb.append( IDADE integer ); sb.append( ); ); db.execsql(sb.tostring()); /**Método lançado caso a versão do banco de dados tenha sido atualizada * na mesma aplicação. Usado geralmente para adicionar novos campos * ou Drop da tabela e criar a nova chamando o oncreate*/ public void onupgrade(sqlitedatabase db, int oldversion, int newversion) { if(newversion > oldversion){ /*inserir aqui scripts de drop ou alter table se necessário de acordo com a versão do banco de dados */ No método oncreate encontramos algo familiar que é a instrução de CREATE TABLE definindo então a estrutura da tabela cliente. O SqliteDatabase usado como parâmetro é o banco de dados aberto na construção do banco de dados. Lembrando que não é necessário invocar o método oncreate, pois ele é disparado no momento que o método getwritabledatabase é chamado. Na função onupgrade informamos instruções de alter table ou inserts e updates no caso de sua versão do banco de dados ter sido alterada como dito anteriormente. Listagem 4. Métodos que auxiliam na interação entre o objeto e os dados. /**Método que rertorna um cursor de um select simples, * mais amigável, se necessário incluir os parametros de * groupby e having*/ private Cursor executeselect(string selection, String[] selectionargs, String orderby){ return database.query( CLIENTE, new String[]{ ID, NOME, SOBRENOME, IDADE, selection, selectionargs, orderby); Cliente cliente = new Cliente(); cliente.setid(cursor.getint(0)); cliente.setnome(cursor.getstring(1)); cliente.setsobrenome(cursor.getstring(2)); cliente.setidade(cursor.getint(3)); return cliente; /**Instancia o objeto ContentValues, facilitando insert e update*/ private ContentValues serializecontentvalues(cliente cliente){ ContentValues values = new ContentValues(); values.put( NOME, cliente.getnome()); values.put( SOBRENOME, cliente.getsobrenome()); values.put( IDADE, cliente.getidade()); return values; Cursor é uma interface que funciona como o ResultSet do java.sql onde obtemos o resultado de uma consulta e podemos interagir entre ela, a prática de construção de um método como o executeselect facilita consultas onde o desenvolvedor tem de trazer todo o objeto da tabela e para transformar os dados em objeto. Outro método como o serializebycursor é recomendado para economia e versatilidade no código do DAO transformando cada resultado em um objeto instanciado, já o método serializecontentvalues faz o contrário, transforma o objeto em values para insert ou update. Com o código apresentado na Listagem 4 podemos fazer qualquer operação de dados. No DAO, basta implementar os métodos da interface descrita anteriormente. Listagem 5. Implementação da interface no objeto DAO. public Cliente getbynome(string nome) { Cliente cliente = null; Cursor cursor = executeselect( NOME =?, new String[]{nome, null); if(cursor!= null && cursor.movetofirst()){ cliente = serializebycursor(cursor); if(! cursor.isclosed()) cursor.close(); /**Método que instancia o objeto e o serializa * com o resultado de um Cursor*/ private Cliente serializebycursor(cursor cursor){ return cliente; 35 \
public List<Cliente> listall() { List<Cliente> list = new ArrayList<Cliente>(); Cursor cursor = executeselect( 1 ); if(cursor!= null && cursor.movetofirst()){ do{ list.add(serializebycursor(cursor)); while(cursor.movetonext()); if(! cursor.isclosed()) cursor.close(); return list; public boolean savecliente(cliente cliente) { ContentValues values = serializecontentvalues(cli ente); //retorno do insert é a quantidade de registros inseridos if(database.insert( CLIENTE, values) > 0) return true; else return false; public boolean deletecliente(long id) { if(database.delete( CLIENTE, ID =?, new String[]{ String.valueOf(id) ) > 0) return true; else return false; de busca, listagem e update Business Object (service) e seu baixo acoplamento Business Object ou Service é um objeto voltado ao controle do negócio, como cita Fowler[3] sobre Design guiado pelo domínio. Neste objeto colocamos regras de negócio antes de persistir ou realizar alguma tarefa na camada visão. Validações também são bem-vindas. No objeto ClienteBO é necessário invocar os métodos do ClienteDao, mas necessitando o mínimo possível do objeto ClienteDao, faremos isso usando a interface DAO. //trecho public class ClienteBO { private IClienteDao clientedao; public ClienteBO(Context context, String databasename, int databaseversion){ clientedao = new ClienteDao( context, databasename, databaseversion); public void savecliente(cliente cliente) throws Exception{ validatecadastrocliente(cliente); if(! clientedao.savecliente(cliente)) throw new Exception( Cliente não pode ser salvo! ); public List<Cliente> getlistcliente(){ return clientedao.listall(); // demais métodos usando o dao e o // validatecadastrocliente omitidos O atributo clientedao utilizado em todos os métodos de interação é a interface instanciada no construtor com sua implementação Android ClienteDao, neste procedimento mantemos a pouca dependência do objeto que implementa a interface. Mas e o construtor? Este tem particularidades do Android usando Dependence Injection, este BO não poderá ser usado como uma lib ou projeto avulso. Isso é verdade, mas a solução é simples: criar um objeto BO para o android etendendo do BO genérico e só implementando o construtor já resolveria o problema. Usando a Classe Application Um recurso muito útil para desenvolvedores Android é poder sobrescrever a classe Application. Sua função é instanciar objetos globais que podem ser usados na aplicação. Conforme a documentação[4], singletons podem resolver isso, porém, se o singleton precisar de um Context ou demais recursos como Broadcast Receivers, já seria mais funcional utilizar a própria classe Application que contém estes atributos. Listagem 6. Classe Application. public class CadastroClientesApp extends Application{ private ClienteBO clientebo; public void oncreate() { super.oncreate(); clientebo = new ClienteBO( this, DATABASE_ CLIENTES, 1 ); / 36
A fim de que o SDK entenda que a classe escrita será a que implementará a aplicação, é necessário informar o AndroidManifest.xml para que ela seja executada nas operações de início e fim. Desta maneira, a tag android:name deve conter o nome da classe: <application android:name= CadastroClientesApp Usando o BO instanciado na Activity Com a classe Application executando, e a Classe BO instanciada, torna-se fácil a chamada do objeto e seus métodos, desde que também se utilize o método getapplication() para buscar a instância do objeto Application dentro da activity Android: //trecho private CadastroClientesApp app; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.cadastro_cliente); //Instancia a aplicação app = (CadastroClientesApp) getapplication(); E, por fim, é possível desfrutar dos BO chamando-o diretamente da Application: //Trecho /* demais métodos omitidos*/ app.getclientebo().savecliente(cliente); /* demais métodos omitidos*/ A grande vantagem de usar um único Business Object instanciado é a minimização de locks no banco de dados, pois não haverá preocupação em abrir e fechar transações durante o runtime da aplicação, mas se precisar abrir e fechar as transações manualmente por questões de otimização ou até mesmo controle próprio, os métodos begintransaction e endtransaction do SQLiteDatabase. Outro benefício é o ganho em heap, mas é interessante monitorá-lo para ver o que pode estar errado numa possível lentidão na inicialização. É importante imaginar que se a utilização de um BO for somente uma vez e chamada pelo usuário esporadicamente, talvez não seja necessária a criação do objeto na classe Application durante a inicialização do sistema e sim dentro da própria activity, porém, um cuidado especial ao parâmetro version no construtor do DAO, pois se muitas criações do BO ficarem espalhadas pela aplicação e um upgrade na versão do BD for necessário, o refactoring poderá ser doloroso. Um meio de resolver o parâmetro version é usando uma classe com constantes ou no modelo singleton. Considerações Finais O modo em que se aplica um banco de dados Sqlite no projeto Android fica muito a cargo do desenvolvedor. Fazer a leitura e atualização de dados pode ser uma prática problemática durante o desenvolvimento ou manutenção posterior. O artigo sugere formas de aproveitar bem os conceitos de desenvolvimento orientado a objetos e grandes facilidades que o Android SDK proporciona. Algumas particularidades do Android podem trazer benefícios à aplicação como a classe Application que é instanciada no momento que a aplicação é executada (não a confunda com Singleton), e esta pode conter classes provedoras de negócio que servem de ponte para o acesso a todos os DAOs do projeto, sendo fácil e rápido chamar os métodos sem ter que ficar instanciando a cada necessidade. Tudo dependerá de como for projetada a aplicação, quantas serão as interações entre os dados, quantas tabelas existirão no banco de dados etc. Uma pergunta a ser feita durante o escopo do projeto é: realmente é necessário usar DAO, BO e criar uma classe gerenciadora de dados para somente usar a tabela Usuário na tela de login? A resposta é implícita se tudo estiver projetado. De qualquer modo o artigo tem foco nas aplicações comerciais, que exigem bastante carga e interação de dados ou outra aplicação do gênero. Para jogos, aplicações como redes sociais, talvez o Sqlite nem seja a melhor recomendação, o Android tem também outras formas de gravar informações como o Shared Preferences e escrita em arquivo. /referências > http://www.sqlite.org/whentouse.html > Introdução à Arquitetura e Design de Software Uma Visão sobre a Plataforma Java Editora: Elsevier Campus > http://martinfowler.com/bliki/anemicdomainmodel.html > http://developer.android.com/reference/android/app/ Application.html /para saber mais > Existem frameworks que auxiliam na escrita do DAO, sendo possível diminuir seu código, principalmente na criação da tabela, métodos de busca e inserção, exclusão e atualização de dados. O DroidPersistence é um projeto que possibilita isso. Mas não descarte o fato de ao menos dominar como trabalha o SDK antes de usar qualquer framework, essa ideia ajudará a resolver possíveis problemas e também servirá de argumento para decisões técnicas de projeto. > O SQLite é um banco de dados relacional simples e a linguagem usada é o SQL (como seu próprio nome indica). Uma das limitações encontradas logo de início em relação a outros SGBDs (Sistemas Gerenciadores de Banco de Dados) é a falta de Stored Procedures, a própria documentação SQLite[1] explica que por ser Lite não tem este recurso e se o desenvolvedor necessita utilizar Stored Procedures em seu BD, procure um que lhe atenda, como o Oracle ou PostgreSql. Contudo, as operações de CRUD (Create, Read, Update e Delete Criar, Ler, Atualizar e Excluir) são feitas normalmente. 37 \