Recentemente escrevi um post falando sobre como criar uma tela de listagem utilizando o RecyclerView. Mencionei os seus principais benefícios como animações, gestos, gerenciadores de layout, atualização parcial da lista, etc. Para exemplificar esses conceitos, foi criado um adapter para uma lista de objetos. Mas e se quisermos utilizar um Cursor vindo do seu Content Provider?
Já mostrei aqui no blog como utilizar o conjunto ContentProvider + CursorLoader + CursorAdapter. Onde foi utilizado uma ListView para exibir na tela as informações vindas do SQLite. Mas esse conjunto pode ser utilizado com a RecyclerView? Vamos analizar cada um deles...
- O ContentProvider é uma abstração para um mecanismo de persistência (que normalmente é um SQLite). Como não tem nada a ver com UI, então esse componente funcionará com a RecyclerView.
- O CursorLoader nos ajuda a fazer a requisição em background e receber notificações quando alguma operação é realizada no provider. Uma vez que é independente da UI também é compatível.
- Já o CursorAdapter não pode ser utilizado com a RecyclerView. Pois ele herda de BaseAdapter e a RecyclerView espera uma instância de RecyclerView.Adapter.
Adicionando dependências
Antes de começarmos a implementação vamos adicionar no build.gradle algumas dependências que utilizaremos no nosso exemplo.apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
applicationId "ngvl.android.exrecyclerview"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.+'
compile 'com.android.support:cardview-v7:23.+'
compile 'com.android.support:recyclerview-v7:23.+'
compile 'com.android.support:support-v4:23.+'
compile 'com.android.support:design:23.+'
}
Classes básicas
Vamos adicionar uma interface que possui informações sobre o nosso banco de dados e do ContentProvider.public interface MensagemContract extends BaseColumns {
String AUTHORITY = "ngvl.android.exrecycler";
Uri BASE_URI = Uri.parse("content://"+ AUTHORITY);
Uri URI_MENSAGENS = Uri.withAppendedPath(BASE_URI, "msgs");
String TABELA_MENSAGEM = "Mensagem";
String TITULO = "titulo";
String DESCRICAO = "descricao";
}
Nossa interface possui uma constante que representa a authority do nosso provider. A authority nada mais é do que o identificador único do nosso provider e faz parte do endereço que utilizaremos para acessá-lo. O endereço completo é representado pela constante URI_MENSAGENS. Em seguida, definimos constantes para o nome da tabela e seus dois campos.Agora crie a classe DbHelper que será responsável por criar a estrutura do banco de dados da aplicação.
public class DbHelper extends SQLiteOpenHelper {
public DbHelper(Context context) {
super(context, "dbNotas", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE "+ MensagemContract.TABELA_MENSAGEM +" ("+
MensagemContract._ID +" INTEGER NOT NULL PRIMARY KEY, " +
MensagemContract.TITULO +" TEXT NOT NULL, " +
MensagemContract.DESCRICAO +" TEXT NOT NULL)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
Quando utilizarmos a classe DbHelper dentro da nossa aplicação, internamente é verificado se banco de dados existe. Caso ele não exista, o método onCreate() será invocado e nele criamos a tabela que armazenará a lista de mensagens.Content Provider
Adicione agora o ContentProvider da aplicação que se chamará MensagemProvider.public class MensagemProvider extends ContentProvider {
public static final int MENSAGENS = 1;
public static final int MENSAGENS_POR_ID = 2;
UriMatcher mUriMatcher;
DbHelper mDbHelper;
@Override
public boolean onCreate() {
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mUriMatcher.addURI(
MensagemContract.AUTHORITY, "msgs", MENSAGENS);
mUriMatcher.addURI(
MensagemContract.AUTHORITY, "msgs/#", MENSAGENS_POR_ID);
mDbHelper = new DbHelper(getContext());
return true;
}
@Override
public String getType(Uri uri) {
throw new UnsupportedOperationException("Não implementada.");
}
@Override
public Uri insert(Uri uri, ContentValues values) {
if (mUriMatcher.match(uri) == MENSAGENS){
SQLiteDatabase db = mDbHelper.getWritableDatabase();
long id = db.insert(
MensagemContract.TABELA_MENSAGEM, null, values);
Uri insertUri = Uri.withAppendedPath(
MensagemContract.BASE_URI, String.valueOf(id));
db.close();
notifyChanges(uri);
return insertUri;
} else {
throw new UnsupportedOperationException("Não suportada");
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
if (mUriMatcher.match(uri) == MENSAGENS_POR_ID){
SQLiteDatabase db = mDbHelper.getWritableDatabase();
int linhasAfetadas = db.delete(MensagemContract.TABELA_MENSAGEM,
MensagemContract._ID +" = ?",
new String[]{ uri.getLastPathSegment() });
db.close();
notifyChanges(uri);
return linhasAfetadas;
} else {
throw new UnsupportedOperationException(
"Uri inválida para exclusão.");
}
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
if (mUriMatcher.match(uri) == MENSAGENS_POR_ID){
SQLiteDatabase db = mDbHelper.getWritableDatabase();
int linhasAfetadas = db.update(MensagemContract.TABELA_MENSAGEM,
values,
MensagemContract._ID +" = ?",
new String[]{ uri.getLastPathSegment() });
db.close();
notifyChanges(uri);
return linhasAfetadas;
} else {
throw new UnsupportedOperationException(
"Uri inválida para atualização.");
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
if (mUriMatcher.match(uri) == MENSAGENS){
SQLiteDatabase db = mDbHelper.getWritableDatabase();
Cursor cursor = db.query(MensagemContract.TABELA_MENSAGEM,
projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
} else if (mUriMatcher.match(uri) == MENSAGENS_POR_ID) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
Cursor cursor = db.query(MensagemContract.TABELA_MENSAGEM,
projection,
MensagemContract._ID +" = ?",
new String[]{ uri.getLastPathSegment() },
null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
} else {
throw new UnsupportedOperationException("Not yet implemented");
}
}
private void notifyChanges(Uri uri){
if (getContext() != null
&& getContext().getContentResolver() != null){
getContext().getContentResolver().notifyChange(uri, null);
}
}
}
A primeira coisa que devemos ter em mente é que podemos inserir, alterar, excluir e obter registros em qualquer lugar. Aqui estamos utilizando um banco de dados SQLite, mas nada impede de utilizarmos outras fontes.Vamos entender um pouquinho do nosso provider:
- Se acessarmos o provider utilizando qualquer endereço (representado por um Uri) que comece com "content://ngvl.android.exrecycler" ele será chamado. Para restringir esse acesso para apenas alguns endereços específicos utilizamos a classe UriMatcher. Com ela, limitamos o acesso ao provider a apenas dois tipos de endereços:
- content://ngvl.android.exrecycler/msgs que será utilizado quando não quisermos acessar nenhuma mensagem específica. Utilizaremos esse endereço para inserir uma mensagem e recuperar a lista de mensagens cadastradas. Identificamos esse tipo de endereço como MENSAGENS com o valor 1.
- content://ngvl.android.exrecycler/msgs/ID_DA_MENSAGEM será utilizado quando quisermos realizar uma operação em um registro específico. Por exemplo, quando formos excluir ou alterar um registro. Nesse caso, definimos que esse tipo de endereço é MENSAGENS_POR_ID e tem o valor 2.
- Para cada operação que realizaremos no provider, checamos o tipo da Uri. Se ela não for de nenhum dos dois tipos especificados, levantamos uma exceção.
- No método insert(), estamos utilizando nossa classe DbHelper para obter a instância do banco e fazer a inclusão no banco de dados. O detalhe importante aqui é que após inserir o registro, estamos invocando o método notifyChange().
Ele é crucial, pois avisará que o provider representado pela Uri recebida como parâmetro sofreu modificações. No nosso exemplo, quando isso acontecer, a lista de mensagens que implementaremos mais adiante será atualizada automaticamente. - Os métodos delete e update seguem a mesma lógica do insert. Eles excluem e atualizam um determinado registro e avisam que isso aconteceu invocando o método notifyChange().
- A outra parte da mágica é feita no método query(). Quando realizamos uma busca no banco de dados, um objeto cursor é retornado e nele, atribuímos uma Uri. Quando essa Uri sofre alguma modificação, esse cursor é notificado e atualizado com as novas informações. E quem está notificando esse cursor? Nosso método notifyChange() ;)
Outra curiosidade no método query() é que podemos obter uma mensagem utilizando a Uri content://ngvl.android.exrecycler/msgs/ID_DA_MENSAGEM.
<application ...>
<provider
android:name=".MensagemProvider"
android:authorities="ngvl.android.exrecycler"
android:enabled="true"
android:exported="true">
</provider>
Criadas as classes básicas e o mecanismo de persistência no banco, vamos começar a implementação da interface gráfica do nosso exemplo que implementaremos no próximo post.4br4ç05,
nglauber
Nenhum comentário:
Postar um comentário