Tutorial Android: Content Provider – Principios Parte 2

On 31 de março de 2014 by Bernardo Reis

Introdução ao Content Provider

No tutorial anterior, Tutorial Android: Content Provider – Entenda O Content Provider – Parte 1, aprendemos os conceitos básicos acerca de um Content Provider que, de forma geral, serve para armazenar dados de forma compartilhada entre aplicativos em aparelhos Android.

Considere um cenário em que precisamos fazer uma aplicação que manipule dados de eventos que aconteceram ou acontecerão em determinada situação, como um festival ou simplesmente eventos gerais de uma cidade.

Você pretende, porém, criar mais aplicativos que sejam compatíveis com o primeiro ou formar parcerias com outros desenvolvedores para ser capaz de compartilhar seus conteúdos entre si.

Para isso é necessária a implementação de Content Providers, o que disponibilizará os dados para serem compartilhados entre as diferentes aplicações. Assim, um evento criado em seu aplicativo poderá ser utilizado no aplicativo de seu parceiro e vice-versa.

Desenho

Implementação do Content provider

Implementaremos aqui, um Content Provider para uma classe de eventos. Vejamos a classe:

[code language=”java”]

public class Event {

private int id;
private String name;
private int user_id;
private int type;
private String description;
private String date;
private String site;
private String imageUrl;
private int place_id;
private int board_id;
private String created_at;
private String updated_at;

public Event(int id, String name, int user_id, int type,
String description, String date, String site, String imageUrl,
int place_id, int board_id, String created_at, String updated_at) {
this.id = id;
this.name = name;
this.user_id = user_id;
this.type = type;
this.description = description;
this.date = date;
this.site = site;
this.imageUrl = imageUrl;
this.place_id = place_id;
this.board_id = board_id;
this.created_at = created_at;
this.updated_at = updated_at;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getUser_id() {
return user_id;
}

public void setUser_id(int user_id) {
this.user_id = user_id;
}

public int getType() {
return type;
}

public void setType(int type) {
this.type = type;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public String getDate() {
return date;
}

public void setDate(String date) {
this.date = date;
}

public String getSite() {
return site;
}

public void setSite(String site) {
this.site = site;
}

public String getImageUrl() {
return imageUrl;
}

public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}

public int getPlace_id() {
return place_id;
}

public void setPlace_id(int place_id) {
this.place_id = place_id;
}

public int getBoard_id() {
return board_id;
}

public void setBoard_id(int board_id) {
this.board_id = board_id;
}

public String getCreated_at() {
return created_at;
}

public void setCreated_at(String created_at) {
this.created_at = created_at;
}

public String getUpdated_at() {
return updated_at;
}

public void setUpdated_at(String updated_at) {
this.updated_at = updated_at;
}
}
[/code]

Veremos agora o código do Content Provider implementado para o armazenamento de Eventos.

A classe que criaremos deve extender a classe ContentProvider, disponível no pacote ‘android.content’. Você notará alguns metódos que devem ser implementados para o funcionamento da classe, mas antes de realmente implementá-los, vamos primeiro criar algumas constantes para deixar o código bem limpo e fácil de se dar manutenção.

[code language=”java”]
public class EventProvider extends ContentProvider {

// Pacote do Content Provider, necessário para a criação do URI correspondente a este provider.
public static String PATH = "imobilis.tutorial.provider";

// Classe que o Content Provider irá manipular.
// Por convenção, utilizamos o mesmo nome para o Content Provider em si.
public static String PROVIDER_NAME = "Event";

// No caso do banco de dados do UGuide, escolhemos sempre manter
// alguns atributos constantes para quaisquer tabelas que criassemos,
// por isso criamos os seguintes atributos fixos:
public static final String _ID = "_id";
public static final String CREATED_AT = "created_at";
public static final String UPDATED_AT = "updated_at";
public static final String STATUS = "status";

// Atributos próprios dessa classe em si.
public static final String NAME = "name";
public static final String USER_ID = "user_id";
public static final String BOARD_ID = "board_id";
public static final String DESCRIPTION = "description";
public static final String TYPE = "event_type";
public static final String IMAGE_URL = "image_url";
public static final String SITE = "site";
public static final String START = "start";
public static final String PLACE_ID = "place_id";

// Atributos de pesquisa, para saber se as requisições serão
// feitas para apenas um item ou para múltiplos itens.
private static final int SINGLE_ID = 1;
private static final int MULTIPLE_ID = 2;

// Iremos criar um banco de dados que irá armazenar
// apenas uma tabela de eventos

// Nome do Banco de dados que irá conter a tabela.
protected static String DATABASE_NAME = "events";

// Nome da tabela no banco de dados
protected static String DATABASE_TABLE = "events";

// Criando o caminho do Content Provider utilizando as constantes criadas acima.
public static Uri CONTENT_URI = Uri.parse("content://" + PATH + "." + PROVIDER_NAME + "/" + DATABASE_TABLE);

// Versão da tabela.
// Utilizada para controle de quando há alguma
// modificação estrutural no banco de dados ou na tabela.
// Quando essas modificações acontecerem, será necessário
// modificar esta variável para que o provider seja reconstruído.
// ATENÇÃO: NA IMPLEMENTAÇÃO ATUAL ISSO IRÁ
// ACARRETAR NA PERDA DOS DADOS EXISTENTES
protected static int DATABASE_VERSION = 2;

// Criando a String que será utilizada apra criar a tabela de
// acordo com os atributos definidos anteriormente.
protected static String DATABASE_CREATE = "create table " + DATABASE_TABLE
+ " (" + _ID + " integer, " +
/* Inicio dos atributos próprios */
NAME + " text not null, " + USER_ID + " integer not null, "
+ BOARD_ID + " integer not null, " + DESCRIPTION
+ " text not null, " + TYPE + " integer not null, " + IMAGE_URL
+ " text not null, " + SITE + " text not null, " + PLACE_ID
+ " integer not null, " + START + " text not null, " +
/* Atributos fixos */
STATUS + " text not null, " + CREATED_AT + " text not null, "
+ UPDATED_AT + " text not null);";

// Objeto responsável pro prover as funções de Banco de Dados necessárias.
private static SQLiteDatabase database;

// O UriMatcher é responsável pela resolução e comparação das URIs utilizadas.
private static final UriMatcher uriMatcher;

// Configurando o UriMatcher para fazer as comparações e os retornos corretos.
// O primeiro argomento do método ‘addUri’ é uma String com o formato da Uri a
// ser comparada, enquanto o segundo argumento é o retorno que a função deve
// dar ao ter aquele URI reconhecido.
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PATH + "." + PROVIDER_NAME, DATABASE_TABLE,
MULTIPLE_ID);
uriMatcher.addURI(PATH + "." + PROVIDER_NAME, DATABASE_TABLE + "/#",
SINGLE_ID);
}

// Criamos aqui uma classe chamada DatabaseHelper, que serve para
// manipular a abertura e manutenção do banco de dados em si.
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);

}

//Efetuamos aqui a atualização da versão do banco de dados.
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w("Content provider database",
"Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");

db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE);
onCreate(db);

}
}
[/code]

Vamos agora aos métodos do Content Provider. Implementaremos o método ‘delete’, responsável por manipular internamente o banco de dados para executar uma remoção de dados.

[code language=”java”]

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;

// Aqui pedimos para o UriMatcher verificar a URI recebida
switch (uriMatcher.match(uri)) {
// Caso o usuário esteja tentando deletar múltiplos ids,
// mandamos o banco de dados deletar os dados da tabela de
// acordo com a requisição do usuário
case MULTIPLE_ID:
count = database.delete(DATABASE_TABLE, selection, selectionArgs);
break;
// Caso o usuário queira deletar apenas um id, mandamos o
// bando de dados deletar o id correspondente e repassamos a
// String de Selection enviada pelo usuário
case SINGLE_ID:
String id = uri.getPathSegments().get(1);
count = database.delete(DATABASE_TABLE, _ID
+ " = "
+ id
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection
+ ‘)’ : ""), selectionArgs);

}

return count;
}

[/code]

O método ‘getType’ deve retornar um MIMEType, cuja documentação pode ser encontrada em: .
Ele basicamente consiste de uma String que irá informar o tipo de dado que está sendo armazenado.

Utilizamos aqui a mesma estratégia do código anterior para múltiplos ids ou id único.

[code language=”java”]
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
// Pegar todos items
case MULTIPLE_ID:
// Estilo do MIMEType para várias linhas da tabela
return "vnd.android.cursor.dir/vnd." + PATH + "." + DATABASE_TABLE
+ " ";
// Pegar um item particular
case SINGLE_ID:
// Estilo do MIMEType para apenas uma linha da tabela
return "vnd.android.cursor.item/vnd." + PATH + "." + DATABASE_TABLE
+ " ";
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}

[/code]

O próximo é o método ‘insert’, no qual precisamos apenas inserir os dados recebidos do usuário através do objeto ContentValues e notificar o ContentResolver que houve tal modificação, para que ele possa atualizar quaisquer aplicações que estejam utilizando os dados de nosso Content Provider.

[code language=”java”]

@Override
public Uri insert(Uri uri, ContentValues values) {
// Adicionar novo item
long rowID = database.insert(DATABASE_TABLE, "", values);

// Se adicionado com sucesso
if (rowID >= 0) {
// Recuperando a URI do dado que acabou de ser adicionado.
Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);
// Notificando a mudança.
getContext().getContentResolver().notifyChange(_uri, null);
return _uri;
}
throw new SQLException("Failed to insert row into " + uri);
}

[/code]

Método chamado durante a criação do ContentProvider, onde instanciamos nossa classe DatabaseHelper para que possamos recuperar a tabela do nosso banco de dados, retornando sucesso ou falha;

[code language=”java”]
@Override
public boolean onCreate() {
Context context = getContext();
DatabaseHelper dbHelper = new DatabaseHelper(context);
database = dbHelper.getWritableDatabase();
return (database == null) ? false : true;
}

[/code]

O método ‘query’ é responsável por fazer buscas em nosso banco de dados. Logo, ele deve receber argumentos para sua busca, baseados em requisições SQL e enviar a requisição ao banco.
Neste método, devemos também configurar uma URI para receber notificações de atualizações que possam ocorrer no banco de dados.

[code language=”java”]
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {

// Criamos aqui um auxiliar para a construção de um query em SQLite
// e definimos em qual tabela buscar.
SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
sqlBuilder.setTables(DATABASE_TABLE);

// Especificamos aqui se devemos fazer a busca em apenas um item.
if (uriMatcher.match(uri) == SINGLE_ID)
sqlBuilder.appendWhere(_ID + " = " + uri.getPathSegments().get(1));

// Verificamos se temos alguma ordenação pedida pelo usuário.
// Caso não tenha, simplesmente ordenaremos pelo id.
if (sortOrder == null || sortOrder == "")
sortOrder = _ID;

// Aqui é onde realmente fazemos a busca, que nos retorna um
// Cursor com os valores encontrados.
Cursor c = sqlBuilder.query(database, projection, selection,
selectionArgs, null, null, sortOrder);

// Registra a URI no cursor para que seja vigiada caso hajam mudanças nos dados,
// ou seja, caso um método ‘update’ tenha sido chamada para os dados presentes.
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}

[/code]

Método ‘update’ utilizado para atualizar dados em nosso banco de dados.
Utilizamos a mesma lógica do método ‘delete’, para identificar se a modificação acontecerá em apenas um id ou em múltiplos ids.
O método deve receber os valores através de um objeto da classe ContentValues.

[code language=”java”]

@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int count = 0;
switch (uriMatcher.match(uri)) {
case MULTIPLE_ID:
count = database.update(DATABASE_TABLE, values, selection,
selectionArgs);
break;
case SINGLE_ID:
count = database.update(
DATABASE_TABLE,
values,
_ID
+ " = "
+ uri.getPathSegments().get(1)
+ (!TextUtils.isEmpty(selection) ? " AND ("
+ selection + ‘)’ : ""), selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}

// Lembra do ‘setNotificationUri’ do método ‘query’?
// Estamos agora notificando que houve mudanças nessa URI.
getContext().getContentResolver().notifyChange(uri, null);
return count;
}

[/code]

Agora todos que os métodos obrigatórios já foram implementados, criamos também métodos auxiliares para prover fácil acesso aos dados do Content Provider.
Nesse método, simplesmente verificamos a existencia de um determido ID no banco de dados.
OBS.: TODOS OS CURSORES DEVEM SER FECHADOS NO PRÓPRIO MÉTODO PARA EVITAR LEAK DE MEMÓRIA.

[code language=”java”]
public static boolean exists(int id, ContentResolver contentResolver) {

// Construindo a projeção e a seleção para a query
String[] projection = {_ID};
String selection = _ID + " = " + id;

// Executando a query
Cursor cursor = contentResolver.query(CONTENT_URI, projection, selection, null, null);

// Caso exista algum dado, o método ‘moveToFirst’ consegue executar sua função,
// logo retorna true. Fechamos então o cursor e retornamos o valor correto.
if(cursor.moveToFirst()) {
cursor.close();
return true;
}
cursor.close();
return false;
}

[/code]

Criamos um método para receber um Evento e adicioná-lo ao Content Provider

[code language=”java”]
public static void add(Event event, ContentResolver contentResolver) {
// Devemos instanciar um objeto da classe ContentValues para passar os
// valores que desejamos ao ContentProvider
ContentValues contentValues = new ContentValues();

// Adicionamos, então, os valores ao ContentValues na forma de chave-valor.
// Valores fixos
contentValues.put(_ID, event.getId());
contentValues.put(CREATED_AT, event.getCreated_at().toString());
contentValues.put(UPDATED_AT, event.getUpdated_at().toString());
// Valores da classe
contentValues.put(NAME, event.getName());
contentValues.put(USER_ID, event.getUser_id());
contentValues.put(BOARD_ID, event.getBoard());
contentValues.put(DESCRIPTION, event.getDescription());
contentValues.put(TYPE, event.getType());
contentValues.put(START, event.getDate().toString());
contentValues.put(SITE, event.getSite());
contentValues.put(IMAGE_URL, event.getImageUrl());
contentValues.put(PLACE_ID, event.getPlace());

// Caso o id do evento seja 0, significa que ele acabou de ser criado e
// está sendo inserido pela primeira vez na tabela. Então, inserimos o
// status NEW (Presente em outra classe, mas é apenas um Enum para controlar
// se determinado dado está sendo criado, destruído ou editado). Por fim, inserimos
// o dado através do método do ContentResolver ‘insert’, passando o ContentValues criado
// e a URI do ContentProvider.
int id = event.getId();
if(id == 0) {
contentValues.put(STATUS, Status.NEW.toString());
contentResolver.insert(CONTENT_URI, contentValues);
} else {
// Se não for um evento novo com ID = 0, usamos o Status.UPDATE
contentValues.put(STATUS, Status.UPDATED.toString());
// Por fim, verificamos ainda se o evento com determinado ID já existe
// no banco. Caso exista, utilizamos o método ‘update’ passando o id
// do evento a ser modificado.
if(exists(id, contentResolver))
contentResolver.update(CONTENT_URI, contentValues, _ID + " = " + id, null);
else // Caso não exista, simplesmente o inserimos na tabela.
contentResolver.insert(CONTENT_URI, contentValues);
}

}

[/code]

Criamos aqui um método ‘get’ para recuperar objetos Event do ContentProvider.

[code language=”java”]
public static ArrayList get(String selection,
ContentResolver contentResolver) {

// Criamos a lsita que conterá os dados recuperados
ArrayList eventList = new ArrayList();

// Fazemos a pesquisa com a seleção recebida do usuário.
// Como estamos tratando de eventos, adotamos o padrão de organizá-los
// pela data em que acontecerão.
Cursor cursor = contentResolver.query(CONTENT_URI, null, selection, null, START + " ASC");

// Caso o cursor contenha algum dado, o método ‘moveToFirst’ retornará ‘true’ enquanto ainda
// move seu iterador para a primeira posição.
if (cursor.moveToFirst()) {
do {
// Criamos o evento recuperando os dados do cursor utilizando o método ‘getColumnIndex’,
// que recebe uma chave correspondente ao dado que deve ser retornado como argumento dos métodos
// de recuperação de dados (‘getInt’, ‘getString’, entre outros disponíveis na classe Cursor.
Event event = new Event(cursor.getInt(cursor.getColumnIndex(_ID)),
cursor.getString(cursor.getColumnIndex(NAME)),
cursor.getInt(cursor.getColumnIndex(USER_ID)),
cursor.getInt(cursor.getColumnIndex(TYPE)),
cursor.getString(cursor.getColumnIndex(DESCRIPTION)),
cursor.getString(cursor.getColumnIndex(START)),
cursor.getString(cursor.getColumnIndex(SITE)),
cursor.getString(cursor.getColumnIndex(IMAGE_URL)),
cursor.getInt(cursor.getColumnIndex(BOARD_ID)),
cursor.getInt(cursor.getColumnIndex(PLACE_ID)),
cursor.getString(cursor.getColumnIndex(CREATED_AT)),
cursor.getString(cursor.getColumnIndex(UPDATED_AT)));
// Adicionamos o evento na lista
eventList.add(event);

// E movemos o iterador do cursor para a próxima posição, caso seja possível
} while (cursor.moveToNext());
}
return eventList;
}

[/code]

Só para ficar mais legível e cômodo, criamos também um método ‘getAll’.

[code language=”java”]
public static ArrayList getAll(ContentResolver contentResolver) {
return get(null, contentResolver);
}

[/code]

E também sobrecarregamos o método ‘get’ para funcionar ao receber apenas um ID para a busca

[code language=”java”]
public static Event get(int id, ContentResolver contentResolver) {
String selection = _ID + " = " + id;
return get(selection, contentResolver);
}
}

[/code]

Lembrando que, assim como uma Activity, é necessário declarar todos os Content Provider implementados no arquivo AndroidManifest.xml. No nosso caso, a declaração acontece da seguinte forma:

[code language=”xml” collapse=”false”]
<provider
android:name="imobilis.tutorial.provider.EventProvider"
android:authorities="imobilis.tutorial.provider.Event" />
[/code]

Conclusão

Agora que temos um Content Provider para eventos, podemos acessá-los de qualquer aplicativo que conheça seu caminho, ou seja, sua URI. Graças a isso, você pode integrar dados entre suas aplicações e de seus parceiros.

4 Responses to “Tutorial Android: Content Provider – Principios Parte 2”

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *