Para finalizar os tutoriais de introdução a android, era impensável não se falar de Base de Dados. O tutorial irá usar como base o tema de clubes de futebol que temos vindo a utilizar. Para isso, iremos adicionar 3 clubes inicialmente na base de dados e, depois iremos demonstrar como é que se carregam os dados, como adicionar e/ou remover um clube.
Gradle file
O primeiro grande passo para o tutorial, uma vez que vamos usar sqlcipher é o leitor dirigir-se ao ficheiro gradle do módulo e, nas dependencias acrescentar a seguinte:
dependencies { (...) compile 'net.zetetic:android-database-sqlcipher:3.3.1-1@aar' }
Isto irá permitir o utilizador utilizar o sqlcipher, fazendo o import do mesmo.
Classe da Base Dados
O próximo passo será criar uma classe que nos irá servir de apoio para todo o processo. Para realizar isso, o utilizador deverá acrescentar a classe da seguinte forma e com o nome de classe a ser “BaseDados”:
Esta classe irá dar-nos todo o apoio necessário. Irá conter o nome da base de dados, o nome das tabelas e seus respectivos campos. Também irá neste caso criá-la caso ainda não exista e, preenche-la com dados que iremos inserir manualmente. A classe deverá ter o seguinte aspecto:
import android.content.ContentValues; import android.content.Context; // Use import android.database.sqlite.SQLiteDatabase; // import android.database.sqlite.SQLiteOpenHelper; for normal support, with no encryption import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteOpenHelper; public class BaseDados extends SQLiteOpenHelper { private static final String PASS_HARDCODED = "hard-coded password - Nao fazer isto em ambientes de producao"; private static final String NOME_BASEDADOS = "clubes.db"; private static final int SCHEMA = 1; //We could put those on a contract class container... static final String NOME = "nome"; static final String SIGLA = "sigla"; static final String TABELA = "clubes"; //SCHEMA Version is 1 public BaseDados(Context context) { super(context, NOME_BASEDADOS, null, SCHEMA); SQLiteDatabase.loadLibs(context); } //Called on db creation, thanks to getReadableDatabase or getWriteableDatabase @Override public void onCreate(SQLiteDatabase db) { //Table creation !!! try { db.beginTransaction(); db.execSQL("CREATE TABLE clubes (nome TEXT, sigla TEXT);"); ContentValues cv = new ContentValues(); cv.put(NOME, "Futebol Clube do Porto"); cv.put(SIGLA, "FCP"); db.insert(TABELA, NOME, cv); cv.put(NOME, "Sport Lisboa e Benfica"); cv.put(SIGLA, "SLB"); db.insert(TABELA, NOME, cv); cv.put(NOME, "Sporting Clube de Portugal"); cv.put(SIGLA, "SCP"); db.insert(TABELA, NOME, cv); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { throw new RuntimeException("Erro"); } SQLiteDatabase getReadableDatabase() { return (super.getReadableDatabase(PASS_HARDCODED)); } SQLiteDatabase getWritableDatabase() { return (super.getWritableDatabase(PASS_HARDCODED)); } }
Layouts
O layout da activity_main irá ser bastante simples, irá ter um spinner para mostrar dados e dois botões, um para adicionar e outro para remover os clubes. Para uma primeira fase, os botões estarão inactivos. O layout deverá ter o seguinte aspecto:
<Spinner android:id="@+id/spinner" android:layout_width="fill_parent" android:layout_height="wrap_content" android:spinnerMode="dropdown"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Adicionar Clube" android:id="@+id/btnAdd" android:layout_below="@+id/spinner" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_marginTop="45dp" android:onClick="AdicionarClube"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Remover Clube" android:id="@+id/delete" android:layout_below="@+id/btnAdd" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_marginTop="45dp" android:onClick="RemoverClube"/>
O próximo passo deverá ser criar um ficheiro .xml com o nome de row. Este ficheiro irá indicar a estrutura do tipo de dados que nós queremos mostrar. Neste caso, será mostrar o nome e a sigla do clube, logo dois “TextView” serão necessários. O código será o seguinte:
<TextView android:id="@+id/Nome" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" /> <TextView android:id="@+id/Sigla" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" />
MainActivity
Para finalizar a fase inicial de bases de dados, o código java da MainActivity deverá ter o aspecto indicado em baixo. Importa clarificar o seguinte:
- Cursor current -> cursor com os dados actual
- BaseDados bd -> Base de dados criada
- AsyncTask task -> uma tarefa que irá correr “de parte” para inserir/eliminar dados. Esta é importante, sempre que queremos fazer uma ação à Base de Dados deveremos usar AsyncTasks para não haver conflitos com a thread principal da aplicação
- Spinner meuSpinner -> spinner de dados. Se o leitor não sabe o que é um spinner, poderá ver aqui.
- Classe BaseTask -> será a classe que irá correr as AsyncTask para operar sobre a base de dados e, a mesma terá uma função de Load sobre os dados que já contem na base de dados.
public class MainActivity extends AppCompatActivity { private Cursor current = null; private BaseDados bd = null; private AsyncTask task = null; Spinner meuSpinner = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.row, current, new String[]{ BaseDados.NOME, BaseDados.SIGLA}, new int[]{R.id.Nome, R.id.Sigla}, 0); meuSpinner = (Spinner) findViewById(R.id.spinner); meuSpinner.setAdapter(adapter); if (current == null) { bd = new BaseDados(this); task = new LoadCursorTask().execute(); } } abstract private class BaseTask<T> extends AsyncTask<T, Void, Cursor> { @Override public void onPostExecute(Cursor result) { ((CursorAdapter) meuSpinner.getAdapter()).changeCursor(result); current = result; task = null; } //Check http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#query(java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String) //We are also retrieving the ROWID, apart from the title and value columns //Return results ordered by title column protected Cursor doQuery() { Cursor result = bd .getReadableDatabase() .query(BaseDados.TABELA, new String[]{"ROWID AS _id", BaseDados.NOME, BaseDados.SIGLA}, null, null, null, null, BaseDados.NOME); result.getCount(); return (result); } } //Wrapper classes for insertion and data loading private class LoadCursorTask extends BaseTask<Void> { @Override protected Cursor doInBackground(Void... params) { return (doQuery()); } } }
O leitor pode agora correr a aplicação e verificar que o spinner criado se encontra preenchido com o nome dos 3 clubes que adicionámos no content values.
Remover Clubes
Para criar o remover, o utilizador deverá acrescentar código na classe BaseTask para criar uma tarefa de remoção.
Para isso, logo a seguir ao método LoadCursorTask, deverá acrescentar o seguinte:
private class DeleteTask extends BaseTask<Cursor> { @Override protected Cursor doInBackground(Cursor... values) { String rowid=values[0].getString(values[0].getColumnIndex("_id")); bd.getWritableDatabase().delete(BaseDados.TABELA, "ROWID" + "=" + rowid, null); return(doQuery()); } }
Esta função deverá depois ser chamada quando o botão de remoção é utilizado. Para isso o leitor deverá criar um método para o botão de remover e codificá-lo de forma a que consiga ir buscar o item do spinner e eliminá-lo da seguinte forma:
public void RemoverClube(View view) { final Cursor cr = (Cursor) meuSpinner.getSelectedItem(); task=new DeleteTask().execute(cr); }
Adicionar Clubes
Neste ponto, iremos ser mais “preguiçosos” e adicionar novamente manualmente. O leitor poderá criar edit text para ir buscar o texto e adicionar vários clubes.
Da mesma forma que criámos uma task para o delete, teremos de criar outra para a inserção, da seguinte forma:
private class InsertTask extends BaseTask<ContentValues> { @Override protected Cursor doInBackground(ContentValues... values) { bd.getWritableDatabase().insert(BaseDados.TABELA, BaseDados.NOME, values[0]); return(doQuery()); } }
Para dicionar, terá de criar um Content Values para inserir 2 valores (nome e sigla dos clubes) e enviá-lo para a InsertTask da seguinte forma (isto no evento do botão adicionar):
public void AdicionarClube(View view) { ContentValues values=new ContentValues(2); values.put(BaseDados.NOME, "Sporting Clube de Braga"); values.put(BaseDados.SIGLA, "SCB"); task=new InsertTask().execute(values); }
O leitor poderá correr a aplicação e, terá uma base de dados operacional para adicionar e remover clubes de futebol.