sábado, 10 de dezembro de 2016

Usando SQLite e padrão DAO para persistir dados

Uma das formas de persistir os dados em um aplicativo nativo Android é por meio do SQLite. Ele é um mini-SGBD que vai acoplado junto a sua aplicação. Para usá-lo, você pode criar uma classe padrão DAO (Data Access Object), utilizada para manipulação de dados, que irá gerenciar os dados conjuntamente com o SQLite. Para exemplificar, vamos utilizar um model Usuario que possui um id do tipo long, um nome do tipo String e um e-mail do tipo String como variáveis de instância. Então, vamos criar um UsuarioDAO, que irá criar o banco de dados, uma tabela de usuários e cuidará da manipulação e recuperação de usuarios do banco da dados.

Para isso, crie uma classe UsuarioDAO e faça com que ela herde de SQLiteOpenHelper. Depois sobrescreva os métodos onCreate e onUpgrade. O método onCreate é executado sempre que a aplicação é instalada e utilizada pela primeira vez. O método onUpgrade é executado sempre que ocorre uma mudança de versão do banco de dados. Tendo em vista essa características, vamos criar a tabela de Usuarios no onCreate. O código abaixo servirá para exemplificar o que foi dito até aqui:

public class UsuarioDAO extends SQLiteOpenHelper {
    private static final String DATABASE = "appExemplo";
    private static final String TABELA = "Usuarios";
    private static final int VERSAO = 1;

    public UsuarioDAO(Context context) {
        super(context, DATABASE, null, VERSAO);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE " + TABLE + " ("
            + COL_ID + " INTEGER PRIMARY KEY, "
            + COL_NOME + " TEXT UNIQUE NOT NULL, "
            + COL_EMAIL + " TEXT"
            + ");";
       
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        String sql = "DROP TABLE IF EXISTS " + TABLE;

        db.execSQL(sql);
        onCreate(db);
    }
}


É preciso tomar muito cuidado com o uso da versão. Ela deve começar com o valor 1 e sempre que ocorrer qualquer mudança na estrutura do banco de dados, ela deve ser alterada sempre acrescentando um valor a mais. Ou seja, inicia-se com 1, caso seja acrescentado um atributo novo como telefone, ou um atributo do banco tenha tido seu nome alterado, então, a próxima versão deve ter o valor 2 e depois 3, 4 e assim por diante. Para o programador isso não fará muita diferença, mas, para o cliente que comprou o seu aplicativo fará muita diferença, pois uma mudança de versão da maneira incorreta poderá fazer com que o cliente perca todos os dados que ele já possuía. Portanto, cuidado!

Temos a nossa tabela de usuário e agora precisamos inserir e recuperar usuários do banco de dados. Para inserir, não podemos usar o método execSQL para executar o código SQL diretamente, pois isso é muito perigoso. Caso fique utilizando execução direta de SQL, pode ser que alguém se aproveite disso para fazer uma injeção de SQL (SQL Injection), causando danos a sua base da dados. Por isso, devemos inserir usuários por meio de um objeto ContentValues que funciona como um HashMap. Após colocar os dados de um usuário em um ContentValues, basta inseri-lo passando-o como parâmetro de um getWritableDatabase().insert(). Veja o exemplo a seguir:

    public void inserir(Usuario usuario) {
        ContentValues values = new ContentValues();

        values.put(COL_NOME, usuario.getNome());
        values.put(COL_EMAIL, usuario.getEmail());

        getWritableDatabase().insert(TABELA, null, values);
    }


Inserimos o usuário e agora temos que recuperá-lo. Temos que posicionar um cursor para o local em que gostaríamos de recuperar os dados, então você usa um SELECT para pegar os dados que você quer, depois use o método moveToNext() para movimentar o cursor que irá se posicionar na próxima linha da tabela. Então, é só fazer um loop que irá percorrer todas as linhas da tabela. Vá colocando todos os dados em uma lista e, então, retorne-a. Veja o exemplo a seguir:

    public List<Usuario> getLista() {
        List<Usuario> lista = new ArrayList<>();

        String sql = "SELECT * FROM " + TABELA + ";";
        Cursor c = getReadableDatabase().rawQuery(sql, null);

        Usuario usuario = null;

        while(c.moveToNext()) {
            usuario = new Usuario();

            usuario.setId(c.getString(c.getColumnIndex(COL_ID)));
            usuario.setNome(c.getString(c.getColumnIndex(COL_NOME)));
            usuario.setEmail(c.getString(c.getColumnIndex(COL_EMAIL)));

            lista.add(usuario);
        }

        return lista;
    }


Além de inserir um usuário, é preciso deletar algum quando necessário. Por isso, também precisamos criar um método deletar que receba um usuário como parâmetro e o exclua do banco por meio da checagem de seu id. Vamos utilizar o método getWritableDatabase().delete para evitarmos uma sql injection. Ele receberá como parâmetro um array de strings. Neste caso, iremos passar o id do usuário para informar qual usuário é que deve ser excluído. Um exemplo de código pode ser visto a seguir:

    public void deletar(Usuario usuario) {
        String[] argumentos = {usuario.getId().toString()};
        getWritableDatabase().delete(TABELA, COL_ID + "=?", argumentos);
    }


Já inserimos, recuperamos e excluímos usuários do banco. Agora precisamos alterar ou dar um update no conteúdo de um determinado usuário que se encontra no banco. Para isso, precisamos novamente receber um usuário como parâmetro de um método alterar, que conterá todas as novas informações a serem alteradas em um usuário já inserido. A seguir segue um exemplo de código:

    public void alterar(Usuario usuario) {
        ContentValues values = new ContentValues();
        values.put(COL_NOME, usuario.getNome());
        values.put(COL_EMAIL, usuario.getEmail());

        String[] argumentos = {usuario.getId().toString()};
        getWritableDatabase().update(TABELA, values, COL_ID + "=?", argumentos);
    }


Pronto, agora a nossa classe já está bem funcional e pode ser utilizada em uma activity:

UsuarioDAO dao = new UsuarioDAO(MinhaActivity.this);
List<Usuario> usuarios = dao.getLista();


Suponha agora que você tenha que alterar a estrutura de banco de dados, aumentando mais um campo, que neste exemplo será o endereço do usuário. Para isso, precisamos adicionar o novo campo no onCreate para aqueles que irão instalar o aplicativo pela primeira vez e precisamos apenas atualizar a nova estrutura do banco no celular daqueles que já possuem o aplicativo instalado. No segundo caso, é preciso modificar o método onUpgrade(). Além disso, é imprescindível trocar a versão, que atualmente está 1, para o valor 2. Então, o onCreate ficará assim:

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE " + TABLE + " ("
            + COL_ID + " INTEGER PRIMARY KEY, "
            + COL_NOME + " TEXT UNIQUE NOT NULL, "
            + COL_EMAIL + " TEXT, "
            + COL_ENDERECO + " TEXT"
            + ");";
       
        db.execSQL(sql);
    }


Enquanto que o onUpgrade não apenas mais derrubará a tabela e a criará novamente. Ele agora será utilizado para alterar a tabela antiga, acrescentando mais uma coluna:

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        String sql = "ALTER TABLE " + TABLE + " ADD COLUMN " + COL_ENDERECO + " TEXT;";

        db.execSQL(sql);
    }


Lembre-se também de adicionar o novo campo nos métodos inserir, getLista e alterar.

E se, por acaso, decidirmos alterar novamente o banco de dados? O banco irá para a versão 3, mas o que acontece com aqueles que instalaram o aplicativo e estão ainda com versões anteriores? Além de atulizar o método onCreate e alterar o inserir, getLista e alterar, é preciso tomar cuidado com o método onUpgrade. Ele ficará responsável para progressivamente realizar todas as atualizações desde a que está instalada no celular até chegar na atualização atual.

Suponha então que vamos adicionar o telefone do usuário. O onUpgrade ficaria assim:

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        String sql;

        switch(oldVersion) {
            case 1:
                sql = "ALTER TABLE " + TABLE + " ADD COLUMN " + COL_ENDERECO + " TEXT;";
                db.execSQL(sql);
            case 2:
                sql = "ALTER TABLE " + TABLE + " ADD COLUMN " + COL_TELEFONE + " TEXT;";
                db.execSQL(sql);
        }
    }


Assim, se a versão antiga instalada for a 2, será feita a atualização do banco executando o que está no case 2. Se a versão antiga instalada for a 1, será feita a atualização do banco executando o que está tanto no case 1 quanto no case 2. Repare que eu não usei o break!

O código completo de nosso UsuarioDAO pode ser visto logo a seguir:

public class UsuarioDAO extends SQLiteOpenHelper {
    private static final String DATABASE = "appExemplo";
    private static final String TABELA = "Usuarios";
    private static final int VERSAO = 3;
    private static final String COL_ID = "id";
    private static final String COL_NOME = "nome";
    private static final String COL_EMAIL = "email";
    private static final String COL_ENDERECO = "endereco";
    private static final String COL_ENDERECO = "endereco";

    public UsuarioDAO(Context context) {
        super(context, DATABASE, null, VERSAO);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE " + TABLE + " ("
            + COL_ID + " INTEGER PRIMARY KEY, "
            + COL_NOME + " TEXT UNIQUE NOT NULL, "
            + COL_EMAIL + " TEXT, "
            + COL_ENDERECO + " TEXT, "
            + COL_TELEFONE + " TEXT"
            + ");";
      
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        String sql;

        switch(oldVersion) {
            case 1:
                sql = "ALTER TABLE " + TABLE + " ADD COLUMN " + COL_ENDERECO + " TEXT;";
                db.execSQL(sql);
            case 2:
                sql = "ALTER TABLE " + TABLE + " ADD COLUMN " + COL_TELEFONE + " TEXT;";
                db.execSQL(sql);
        }
    }

    public void inserir(Usuario usuario) {
        ContentValues values = new ContentValues();

        values.put(COL_NOME, usuario.getNome());
        values.put(COL_EMAIL, usuario.getEmail());
        values.put(COL_ENDERECO, usuario.getEndereco());
        values.put(COL_TELEFONE, usuario.getTelefone());

        getWritableDatabase().insert(TABELA, null, values);
    }

    public List<Usuario> getLista() {
        List<Usuario> lista = new ArrayList<>();

        String sql = "SELECT * FROM " + TABELA + ";";
        Cursor c = getReadableDatabase().rawQuery(sql, null);

        Usuario usuario = null;

        while(c.moveToNext()) {
            usuario = new Usuario();

            usuario.setId(c.getString(c.getColumnIndex(COL_ID)));
            usuario.setNome(c.getString(c.getColumnIndex(COL_NOME)));
            usuario.setEmail(c.getString(c.getColumnIndex(COL_EMAIL)));
            usuario.setEndereco(c.getString(c.getColumnIndex(COL_ENDERECO)));
            usuario.setTelefone(c.getString(c.getColumnIndex(COL_TELEFONE)));

            lista.add(usuario);
        }

        return lista;
    }

    public void deletar(Usuario usuario) {
        String[] argumentos = {usuario.getId().toString()};
        getWritableDatabase().delete(TABELA, COL_ID + "=?", argumentos);
    }

    public void alterar(Usuario usuario) {
        ContentValues values = new ContentValues();
        values.put(COL_NOME, usuario.getNome());
        values.put(COL_EMAIL, usuario.getEmail());
        alues.put(COL_TELEFONE, usuario.getTelefone());

        String[] argumentos = {usuario.getId().toString()};
        getWritableDatabase().update(TABELA, values, COL_ID + "=?", argumentos);
    }
}


That's all! ^^

Nenhum comentário:

Postar um comentário