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