Attention, this answer is indicated for programmers who have at least a basic knowledge of:
- Activity
- Fragment
- Lists and Adapters
- Custom Lists and Custom Adapters
Loaders
Introduced in Android 3.0, loaders make it easier to load data asynchronously in Activitys and Fragments. Loaders has the following features:
- They are available in each Activity and Fragment.
- They provide data loading asynchronously .
- They monitor the source of your data and deliver new results when content is modified.
- They automatically reconnect to the last slider cursor when they are being recreated after a configuration change. Therefore, they do not need to re-query your data.
Summary of Loader API
These are the multiple classes and interfaces that are involved in using loaders in an application. They are summarized in this table:
NOTE: This table was mounted from the table that is on the official website of the Android documentation on Download. I took their table, translated it, mounted it in a text editor, took a print and then edited the image. References to the sites are at the end of the post.
References for classes / interfaces:
The classes and interfaces in the table above are the essential components that you will use to implement a Loader in your application. You won’t need all of them for each Loader you create, but you’ll always need a reference to the Loadermanager to initialize a Loader and an implementation of a Loader class like Cursorloader.
Using Loaders in your Application
An application that uses Loaders will typically include the following:
NOTE: PAY ATTENTION TO ITEM 3 BELOW, THE PART IN BOLD.
- An Activity or Fragment.
- An instance of Loadermanager.
- A Cursorloader to upload data supported by a Contentprovider. Alternatively, you can implement your own subclass of loader or Asynctaskloader to load data from some other source.
- An implementation of Loadermanager.LoaderCallbacks. This is where you create new Loaders and manage your references to existing Loaders.
We saw a bit about Loader and the classes that are involved in its implementation. Did you notice item 3 above? I asked to repair. In the documentation, where the above text was taken from, it says to use a Cursorloader to upload data from a Contentprovider, but I don’t want that.
I will teach how to load data directly from an Sqlite database without creating a Contentprovider. We will do this from a publicly available library. However, remember, we could implement our own Custom Loader if we did not want to use the library. But stay calm, I’ll still teach you how to implement a Custom Loader.
cwac-loaderex library
Regardless of how you create your database, the library needs to use a reference to Sqliteopenhelper, so have a method where it is possible to return such an object.
With the library imported into your project and assuming you already have a reference to Sqliteopenhelper, let’s use as an example a Listfragment that wants to display a list of manufacturers that is stored in the database and wants to use a Simplecursoradapter to link this data to the list.
Class Fabricantelistfragment
This class presents only the methods and commands needed for the implementation, ignoring the rest.
/**
* Classe que exibe uma lista de fabricantes que estao cadastradas no banco de dados.
* O usuario pode cadastrar, editar e deletar um fabricante. Esses fabricantes sao utilizados somente para selecionar o fabricante do instrumento.
*
* @author Lucas Santos
* Date: 28/03/2014
* Time: 09:11:20
*/
public class FabricanteListFragment extends ListFragment implements LoaderCallbacks<Cursor> {
/** O ID unico do Loader. ID's dos Loaders sao especificos para a Activity ou Fragment em que eles residem. */
private static final int LOADER_ID = 1;
/** Referencia a interface callback no qual iremos interagir com o LoaderManager. */
private LoaderCallbacks<Cursor> loaderCallbacks;
/** O adapter que vincula nossos dados para o ListFragment. */
private SimpleCursorAdapter mAdapter;
/** Um Loader para um cursor que carrega dados diretamente de um banco de dados ao invés de utilizar Content Provider. */
private SQLiteCursorLoader loader;
@Override
public void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
String[] projection = { Fabricante.COLUNA_NOME.texto };
int[] to = { android.R.id.text1 };
/*
* Inicializa o adapter. O cursor do adapter e null. Nos iremos passar o cursor somente quando tiverem seu carregamento finalizado pela primeira vez.
* Exemplo: quando LoaderManager entrega os dados para onLoadFinished. Passango a flag 0 como ultimo argumento do adapter, isto previne que o adapter
* registre um ContentObserver para o Cursor (CursorLoader ira se encarregar disto para nos).
*/
mAdapter = new SimpleCursorAdapter( getActivity().getApplicationContext(), R.layout.list_fragment_item_drawer, null, projection, to, 0 );
// Associa o adapter, agora vazio, com o ListView
setListAdapter( mAdapter );
/*
* A Activity ou Fragment (que implementa a interfce LoaderCallbacks<D>) e o objeto callback do qual iremos interagir com o LoaderManager.
* O LoaderManager usa este objeto para instanciar um Loader e para notificar o cliente quando os dados estao disponiveis/indisponiveis.
*/
loaderCallbacks = this;
// Indica que este Fragmento gostaria de participar no preenchimento do menu de opcoes recebendo uma chamada para onCreateOptionsMenu e metodos relacionados.
setHasOptionsMenu( true );
}
@Override
public void onActivityCreated( Bundle savedInstanceState ) {
super.onActivityCreated( savedInstanceState );
/*
* Inicializa o Loader com o id "LOADER_ID" e callback 'loaderCallbacks'. Se o Loader nao existe, um e criado. Caso contrario, o Loader ja existente e reutilizado.
* O chamada ao initLoader assegura que o loader está inicializado e ativo. São duas as respostas a essa chamada:
* Se o loader especificado já existe, o último loader criado será reutilizado.
* Se o loader especificado não existe, o initLoader inicia o método onCreateLoader dentro de LoaderManager.LoaderCallbacks.
* Aqui é onde você implementa código para instanciar e retornar um novo loader.
* Em ambos os casos, o LoaderManager ira gerenciar o Loader sobre o ciclo de vida da Activity/Fragment, ira receber qualquer novos Loaders uma vez que estiverem completados,
* e ira relatar estes novos dados de volta para o objeto 'loaderCallbacks'.
*/
getLoaderManager().initLoader( LOADER_ID, null, loaderCallbacks );
}
/*
* Instancia e retorna um novo Loader para um dado ID.
* É um método de fábrica que simplesmente retorna um novo Loader. O LoaderManager vai chamar esse método quando ele for o Loader pela primeira vez.
*/
@Override
public Loader<Cursor> onCreateLoader( int id, Bundle args ) {
// AQUI eu obtenho referência para um objeto `SQLiteOpenHelper` através da minha classe de banco de dados.
DatabaseHelper databaseHelper = DatabaseHelper.getInstance( getActivity().getApplicationContext() );
// Criamos o Loader através da biblioteca sem a menor dificuldade, apenas passando o `context`, objeto `SQLiteOpenHelper`, uma `Query` e os argumentos null.
// `Fabricante.GET_ALL_FABRICANTES.texto` é uma `Query` em forma de String que fará a consulta ao banco de dados. Como eu não utilizo a clausula WHERE, então o quarto parâmetro é null.
loader = new SQLiteCursorLoader( getActivity().getApplicationContext(), databaseHelper, Fabricante.GET_ALL_FABRICANTES.texto, null );
return loader;
}
/*
* Chamado quando um loader criado préviamente tem seu carregamento finalizado.
* Este método é geralmente onde o cliente irá atualizar a interface do usuário do aplicativo com os dados carregados.
* O cliente pode (e deve) assumir que novos dados serão retornados a este método cada vez que novos dados são disponibilizados.
* Lembre-se que este é o trabalho do Loader, monitorar a fonte de dados e realizar os carregamentos assíncronos reais.
* O LoaderManager receberá esses carregamentos uma vez que tenham concluído, em seguida, passar o resultado para o método onLoadFinished do objeto callback
* para o cliente (ou seja, a Activity/Fragment) para usar.
*/
@Override
public void onLoadFinished( Loader<Cursor> loader, Cursor data ) {
this.loader = (SQLiteCursorLoader) loader;
// O carregamento assincrono esta completo e os dados agora estao disponiveis para uso. Somente agora podemos associar o cursor consultado com o Adapter.
mAdapter.swapCursor( data );
// O listview agora exibe os dados consultados.
}
/*
* Chamado quando um loader criado préviamente está sendo resetado, fazendo com que seus dados fiquem indisponíveis.
* É chamado quando os dados do Loader está prestes a ser resetado. Este método dá a você a oportunidade para remover
* quaisquer referências a dados antigos que podem não estar mais disponíveis.
*/
@Override
public void onLoaderReset( Loader<Cursor> loader ) {
// Por algume razao, os dados dos Loader's estao agora indisponiveis. Remova qualquer referencia para os dados antigos substituindo ele com um Cursor null.
mAdapter.swapCursor( null );
}
}
As the class is ALL commented, I will not comment on the class here, scan the code.
Using this library is really very easy. Now after you have used and implemented, test how the Loader automatically loads when a change is detected. Try putting a method that deletes a manufacturer from the list or add, but do it without leaving the Fragment from the list. But how? For example, put to when press an item from the list, pop up a Dialog asking if you want to delete, you confirm saying yes and then perform the deletion in the database. The screen will automatically update.
Take a look at the library documentation, it provides methods for your Loader created from Sqlitecursorloader to insert and delete from the database without having to create an Asynctask for it. The creation of Asynctask is encapsulated within the method provided by the library. For example, in the list above if you wanted to delete a record in the database using your Loader just do this:
// Usando este metodo com um SQLiteCursorLoader ele automaticamente e executado em uma AsyncTask, ou seja, fora da Thread Main.
loader.delete( Fabricante.NOME_TABELA.texto, Fabricante.COLUNA_ID.texto + " = " + idItemSelecionado, null );
Simple no? :). I’ll be explaining in another answer how to implement a Custom Loader to load data from a class and return it to display in a Custom List that uses a Custom Adapter.
References:
Note: language name/frameworks/technologies (Android, Sqlite) should not be highlighted as a code; I imagine that the table is a scan of some book, so it is important to quote the source.
– brasofilo
@brasofilo sorry, I did not know that it should not be highlighted as code. I’ve always done this to highlight different from bold. But I will change this habit and put highlights in bold only. As for the table I have already put the source, which was the site google. I took their table, translated it into a libre office document, took a print, edited the image and put it here.
– Lucas Santos
No problem, were only editorial remarks (usually, the origin of an image is cited just below it, but if it is its production, all blue). Excellent response by the way :)
– brasofilo
Thank you. I will edit the question after anyway, removing the highlights.
– Lucas Santos
Congratulations on your answers. They were very helpful to me! Thank you very much!
– Andrei Coelho