domingo, 1 de julho de 2018

Rumo ao Certificado Android:LiveData e Observer Pattern

O que é?
LiveData é uma classe que observa e notifica mudanças de dados, respeitando ao mesmo tempo, o ciclo de vida de outros objetos, como Activities e Fragments.

Como usar:

Adicionando Dependências:
No Gradle, adicionar as seguintes dependências:

dependencies{

    implementation "android.arch.lifecycle:extensions:1:1:0"
    annotationProcessor "android.arch.lifecycle:compiler:1.1.0"

}

Adicionando em um DAO
Vamos pegar o DAO feito no post sobre Room e vamos fazer o seguinte: como nós precisamos saber se houve alguma mudança na lista de todos os nomes, então vamos adicionar, mudando o retorno da função com LiveData (linha 04). Obviamente, qualquer referência a essa função deve ser mudada também. Segue o código modificado:

01. @Dao
02. public interface NameDao{

03.   @Query("SELECT * FROM name ORDER BY priority")
04.   LiveData<List<NameEntry>> loadAllNames();

05.   @Insert
06.   void insertName(NameEntry nameEntry);

07.   @Update(onConflict=OnConflictStrategy.REPLACE)
08.   void updateName(NameEntry nameEntry);

09.   @Delete
10.   void deleteName(NameEntry nameEntry);

11.   @Query("SELECT * FROM name WHERE id=:id")
12.   NameEntry loadNameById(int id);
13. }

Adicionando um Observable
Agora, vamos adicionar um Observer ao LiveData para ser notificado. Supondo que temos o seguinte método (ver abaixo) em uma Activity para pegar a lista de nomes no banco de dados:



01. public class ActivityName extends Activity{

02.   private void getAllNames(){
03.     final LiveData<List<NameEntry>> names = bd.namesDAO().loadAllNames();
04.     names.observe(this, new Observer<List<NameEntry>>(){
          
05.        @Override
06.        public void onChange(@Nullable List<NameEntry> namesEntries){
07.           //Recebendo os dados atualizados do LiveData
08.        }

09.     });

10.  }

11.}

O método acima agora só precisa ser executado uma vez (geralmente no onCreate), e qualquer mudança que ocorrer no banco de dados agora, o método onChange (linha 05-08) do objeto Observer que criamos na linha 4.

quinta-feira, 28 de junho de 2018

Rumo ao Certificado Android: Multithread com Executors

O que é Executors?
É uma forma segura de lidar com threads no Android sem ter as inconveniências de usar o Runnable padrão do Java.

Como implementar?
Aqui usaremos o padrão Singleton, para termos apenas uma referência ao Executor (podemos ter mais de um se desejar, mas é bom manter apenas uma referência para cada executor). Criamos um novo executor implementando uma outra classe que herda Executor, mas também podemos usar a classe Executors para outras formas de instanciar, como usando o método newSingleThreadExecutor ou newFixedThreadPool.

01. public class AppExecutor{
02.    private static final Object LOCK = new Object();
03.    private static AppExecutors instance;
04.    private final Executor executor;


05.    private AppExecutor(Executor executor){
06.       this.executor = executor;
07.    }


08.    public static AppExecutors getInstance(){
09.       if(instance==null){
10.          synchronized(LOCK){
11.             instance = new AppExecutor(ThreadExecutor()));
12.          }
13.       }
14.       return instance;
15.    }

16.    public Executor getExecutor(){
17.       return executor;
18.    }

19.    private static class ThreadExecutor implements Executor{
20.       private Handler threadHandler = new Handler(Looper.getMainLooper());

21.       @Override
22.       public void execute(@NonNull Runnable command){
23.         threadHandler.post(command);
24.       }
25.    }
26. }


Rumo ao Certificado Android: Banco de Dados SQLite com Room

O que é Room?
Room é uma biblioteca do Android para mapeamento das tabelas de um banco SQLite para Objetos, em outras palavras um ORS (Object-relational mapping - Mapeamento objeto-relacional).

Ajustando dependências
Como qualquer outra biblioteca facultativa do Android, o Room deve ser importado no Gradle para poder ser usado. Para isso, vamos criar uma dependência:

dependencies{
    implementation "android.arch.persistence.room:runtime:1.0.0"
    annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
}


Criando uma Entity
Entities são objetos que representam uma tabela no banco de dados. 
No código abaixo, nas linhas 1 e 2, vemos que Room utiliza classes com a anotação @Entity para criar as tabela antes da declaração da classe. Caso o nome da classe é diferente do nome da tabela, podemos indicar o nome da tabela que queremos no parâmetro da anotação.

Nas linhas 3-9 temos os campos do objeto, que por padrão vão ser as colunas da nossa tabela. Para criar um campo que não deva ser mapeado, devemos usar a anotação @Ignore (linha 8). Para definirmos uma chave primaria, usamos a anotação @PrimaryKey com parâmetro autoGenerate para deixarmos o banco de dados gerar os valores ou nós gerarmos. Não esqueça de criar os Getters e Setters (linha 22-26).

Também vale lembrar que o Room não permite que nossa classe tenha dois construtores (linha 10-21). Isso porque o Room usará construtores para popular os dados. Caso você queira ter um segundo construtor, você pode usar a anotação @Ignore no construtor (ou construtores) que você não deseja que o Room utilize (linha 15-21).

01. @Entity(tableName="name")
02. public class NameEntry{

03.   @PrimaryKey(autoGenerate=true)
04.   private int id;
05.   private String name;
06.   private int priority;
07.   private Date update;
08.   @Ignore
09.   private int dontIncludeMe;

10.   public NameEntry(String name,int priority,Date update){
11.       this.name = name;
12.       this.priority = priority;
13.       this.update = update;
14.   }

15.   @Ignore;
16.   public NameEntry(int id, String name,int priority,Date update){
17.       this.id = id;
18.       this.name = name;
19.       this.priority = priority;
20.       this.update = update;
21.   }


22.  //Getters e Setters
23.  public int getId() {return id;}
24.  public void setId(int id){ this.id = id; }
25.  public String getName() {return name; }
26.  public void setName(String name) {this.name = name; }
23.  public int getPriority() {return priority;}
24.  public void setPriority(int priority){ this.priority = priority; }
25.  public int getUpdate() {return update;}
26.  public void setUpdate(int update){ this.updateupdate; }

27. }

Criando uma DAO
DAO (Data Access Object) é o objeto responsável por permitir o acesso aos dados no banco de dados. Cada Entity criada vai possuir um DAO. 

Para criar um DAO usando Room, você deve implementar uma interface com a anotação @Dao (linha 1-2).
Um método que retorna uma lista da Entity na qual o DAO implementa o acesso deve ser criada para consulta, juntamente com sua anotação @Query, contendo a consulta SQL para obter todas as Entities (linha 3-4).
Consulta com parâmetros podem ser feitas também como mostra a linha 11 e 12.
Também precisamos inserir as operações INSERT (linha 5-6), UPDATE (linha 07-08) e DELETE (linha 09-10), juntamente com suas respectivas anotações @Insert, @Update e @Delete.
Uma observação no @Update do código abaixo (linha 07) é que usamos o parâmetro onConflict para especificar uma estratégia de conflito, que no caso seria um REPLACE. Mas o que seria isso? Conflitos são gerados quando definimos restrições, como a não repetição de valores em uma coluna (UNIQUE), chaves primárias ou chaves estrangeiras. No caso do exemplo, usamos o REPLACE como parâmetro, o que significa que se houver um conflito, os registros conflitantes serão deletados e, neste caso como se trata de um Update, o registro a ser alterado será atualizado.



01. @Dao
02. public interface NameDao{

03.   @Query("SELECT * FROM name ORDER BY priority")
04.   List<NameEntry> loadAllNames();

05.   @Insert
06.   void insertName(NameEntry nameEntry);

07.   @Update(onConflict=OnConflictStrategy.REPLACE)
08.   void updateName(NameEntry nameEntry);

09.   @Delete
10.   void deleteName(NameEntry nameEntry);

11.   @Query("SELECT * FROM name WHERE id=:id")
12.   NameEntry loadNameById(int id);
13. }

Criando um Type Converters
SQLite possui limitações quantos aos tipos. Ele não possui nativamente uma estrutura de dados para datas, valores booleanos e qualquer outra classe que não seja Null, Integer, Real, Text ou Blob. Se mesmo assim, nós queremos utilizar uma classe específica para este dado, então precisamos criar um Type Converter. Vamos olhar por exemplo, que a nossa classe NameEntry possui um campo de data. Então como fazemos?

Criaremos dois métodos, neste caso, um vai receber um valor do tipo long (do banco de dados) para um Objeto Date (linhas 02-05). Enquanto o outro vai fazer o contrário, vai receber um objeto Date e converter para um long (linhas 06-09). Note que ambos métodos possuem uma anotação @TypeConverter (linhas 02 e 06).



01. public class DateConverter{

02.     @TypeConverter
03.     public static Date toDate(long timestamp){
04.         return timestamp==null?null:new Date(timestamp);
05.     }

06.     @TypeConverter
07.     public static long toTimeStamp(Date date){
08.        return date==null?null:date.getTime();
09.     }
10. }


Criando o banco de dados
Para criar um banco de dados, vamos criar uma classe abstrata que herdará a classe RoomDatabase (linha 03). Também usaremos a anotação @Database, que levará como parâmetros, uma lista de todas as classes com a notação @Entity, a versão do banco de dados e se é para exportar um schema do BD (linha 01). Se tiver Type Converters, então também vamos declará-los aqui (linha 02).

Aqui usaremos um Singleton para criar/instanciar o banco de dados: Se já não houver uma instância, criaremos usando um databaseBuilder, que utiliza de um contexto para ser criado. (linhas 07-16)

Por fim, criaremos um método abstrato para cada DAOs que tivermos criado (linha 17).  

01. @Database(entities={NameEntry.class}, version=1, exportSchema=false) 
02. @TypeConverters(DateConverter.class);
03. public abstract class AppDatabase extends RoomDatabase{

04.   private static final Object LOCK = new Object();
05.   private static final String DATABASE_NAME = "NameList";
06.   private static AppDatabase instance;

07.   public static AppDatabase getInstance(Context context){
08.       if(instance == null){
09.         synchronized(LOCK){
10.            instance = Room.databaseBuilder(context.getApplicationContext(),
11.                       AppDatabase.class,
12.                       AppDatabase.DATABASE_NAME).build();
13.         }
14.       }
15.       return instance;
16.   }
  
17.   public abstract NameDao nameDao();
18. }

Como usar tudo isso?
Vamos pegar um exemplo de método para inserir um novo dado:

01.  public void insert(String name){
02.      NameEntry newName = new NameEntry(name, 1, new Date());
03.      AppDatabase.getInstance(this).taskDao().insertName(newName);
04.  } 

Basicamente, pegamos uma instancia do banco de dados, pegamos o DAO da nossa tabela e invocamos o método responsável por inserir. Simples assim.

Notas Importantes
- Nunca execute queries em threads principais de uma atividade. Apesar de poder desativar a proteção, por padrão, o mesmo não permitirá que utilize em threads principais. 



sexta-feira, 22 de junho de 2018

Rumo ao Certificado Android: Content Provider

Já vimos bastante da Activity, então hoje vamos ver mais um dos quatros classes bases do Android: os Content Providers. O objetivo de um Content Provider é criar uma interface para acessar dados, geralmente de um banco de dados, principalmente quando isso significa permitir que esses dados seja acessados por diversos aplicativos, inclusive dados como Agenda, alarme e outros nativos do Android. Para quem é da programação WEB, pode pensar que o Content Provider é um aplicativo REST que serve como uma interface aos dados.

Permissão
Para utilizar um Content Provider, é necessário registrar no manifesto a permissão para acessar o determinado Content Provider. Cada provider que app possuir deve ser registrado manualmente com a devida permissão de leitura/gravação.

Quando você quer dar permissão para somente leitura:
<uses-permission android:name="com.yourdomain.classedoprovider.TERMS_READ" />

Content Resolver
Content Resolver é a classe que fará a interface com o Content Provider. Qualquer classe que herdar o Context (ou ter acesso ao um objeto Context), pode usar o método getContentResolver para ter uma interface com os Provider

ContentResolver resolver = getContentResolver();

Ações
Com o Resolver, nós podemos agora realizar Queries e ações no banco de dados (limitados pela permissão). Existem quatro ações que podemos realizar:


  • Ler dados com o método query;
  • Adicionar dados com o método insert;
  • Atualizar dados com o método update;
  • Deletar dados com o método delete;


URI para Content Provider
A primeira ação é a da consulta, usando o resolver, temos o seguinte comando:

Cursor cursor = resolver.query(AppContract.CONTENT_URI,null,null,null,null);

Sendo que AppContract.CONTENT_URI é uma URI que apontará o Content Provider. Geralmente, o URI para Content Provider tem a seguinte sintaxe:

content://com.yourdomain.classedoprovider/termos

sendo:
  • content: Prefixo indicando que é um Content Provider
  • com.yourdomain.classedoprovider: é o que o Android chama de Autoridade de Conteúdo (Content Authority). É literalmente, a classe que controla os dados;
  • termos: Aqui é onde especificamos os dados que queremos acessar. Pode ter vários níveis, como tabela/id, e outras formas. Basicamente, o que vier aqui já é domínio do desenvolvedor, não mais do sistema.
Contrato
Uma das questões levantadas geralmente é o seguinte: como que eu vou saber de cabeça essa URI, ou suas composições. Se você observar o código da seção anterior, verá um AppContract que tem como uma constante a URI. 
Para padronizar essas informações, o Android recomenda criar uma classe Contract, que vai justamente manter essas informações de forma organizada e facilitar o desenvolvimento. A criação do Contrato é opcional em geral, mas para o Certificado, temos que seguir os padrões, e para a minha opnião, essa é um padrão que funciona tão bem que utilizo até fora do desenvolvimento Android.

Para acessar dados dos aplicativos próprio do Android, como Calendário, você pode encontrar na documentação os seus respectivos contratos (CalendarContract no caso)

Acessar o Content Provider
Content Provider, assim como qualquer recurso externo, precisa de tempo para ser carregado/executado. Então, assim, como vimos no caso de requisição Web, requisições ao Content provider devem utilizar um Loader ou pelo menos um AsyncTask (Exceto as que retornam um cursor, para esse, existe o CursorLoader).

Query
Agora vamos estuda o método Query mais detalhadamente:

Cursor cursor = resolver.query(uri,projection,selectionArg,sort);

Agora vamos estuda o método Query mais detalhadamente, observando seus parâmetros:
  • uri: é o URI para o Content Provider;
  • projection: contém filtro para selecionar quais colunas de uma base de dados você deseja obter. Neste parâmetro, quando usamos null, seria como ter dado um "SELECT *" no SQL;
  • selectionArg: é o filtro para selecionar os dados. No SQL, seria o equivalente ao WHERE;
  • sort: inidica se deve vir ordenado. É equivalente ao ORDER BY do SQL;
Cursor
É através do Cursor que podemos obter os dados do Content Provider. Um exemplo de código para conseguir todos os dados que retornaram pelo cursor:

while(cursor.moveToNext()){
   String str = cursor.getString("nomeCol");
   int inte = cursor.getInt(2);
}

Pontos importantes:
  • O cursor sempre inicia na posição anterior ao primeiro dado, por isso podemos usar o moveToNext já de cara sem perder dados.
  • Use o método get<tipo> de acordo com o tipo de dado;
  • Você pode usar o get<tipo> com o nome da coluna ou com o índice;
Cursor Loader
CursorLoader é uma classe derivada do AsyncTaskLoader, mas específico para as consultas com Cursor. Para criar um Cursor Loader, tenha em mente que o retorno dele é sempre um Cursor.
Em onCreateLoader, você deve retornar um novo  CursorLoader

return new CursorLoader(context,
                       forecastQueryUri,
                       MAIN_FORECAST_PROJECTION,
                       selection,
                       null,
                       sortOrder);

Pode reparar que a única diferença para o Query, é o contexto que deve ser passado por primeiro.

quinta-feira, 21 de junho de 2018

Rumo ao Certificado Android: Dados Persistentes - Shared Preferences

Hoje vou começar uma sub-série sobre persistência de dados no Android. Persistência de dados é a capacidade do aplicativo manter os dados mesmo depois do aplicativo ser finalizado. O Android possui 5 formas de lidar com persistência de dados, cada uma atendendo a uma necessidade específica.

A primeira já vimos, que é o Instance State, usado para persistir o estado de uma Activity quando o mesmo está preste a ser destruído pelo sistema sem que o usuário deseje isso. Como o usuário pode querer retomar a Activity, o ideal é que a mesma mantenha da mesma forma que o usuário deixou. Se o usuário fechar o aplicativo, então os dados do Instance State são apagados.

O segundo é o SharedPreferences, onde utiliza um arquivo e num sistema de chave/valor, permite armazenar strings. Os dados armazenados pela SharedPreferences são guardados até a desinstalação do aplicativo.

Para dados mais complexos e estruturados, o Android possui suporte nativo ao banco de dados SQLite, podendo criar tabelas e relacionamentos.

Para arquivos em geral, tanto gerado pelo aplicativo quanto para os download, podemos utilizar o Internal/External Storage. Interno salvará o arquivo na memória interna, enquanto que o Externo salvará em um cartão de memória. Aliás, esse foi o meu primeiro registro sobre programação Android, então vou ter que dar uma revisada neste ponto.

Por último, podemos salvar os dados na nuvem, Google Drive, Firebase, etc...


Como utilizar o Shared Preferences
Como dito antes, Shared Preferences possui uma estrutura de Chave/Valor, por isso, ele é ótimo para armazenar informações com uma semântica simples, por exemplo:

Nome = "João"
CorCamisa = "Verde"
CorBermuda = "Azul"
CorFundo = "Vermelho"

Acredito que deu para entender.

1. PreferenceFragment
Então, a primeira coisa que precisamos fazer é criar um PreferenceFragment, que é uma interface para nós modificar um Shared Preference. Não é exatamente necessário criar um, mas como é comum utilizar uma tela de configurações, então o PreferenceFragment já auxilia nesta tarefa.

Então, para nós criar, primeiro temos que criar uma depêndencia no arquivo do Gradle:

dependencies{
    compile 'com.android.support:preference-v7:25.0.0'
}

Em seguida, criaremos uma classe que herda da classe PreferenceFragment ou PreferenceFragmentCompat. Também implemente as classes que o Android Studio irá pedir para implementar. A classe ficará com esse template:

public class NomeDaClasse extends PreferenceFragmentCompat{
  
  @Override
  public void onCreatePreferences(Bundle savedInstanceState, String rootKey){
     
  }
}

2. Criando o Arquivo de Preferências
Com o botão direito do mouse, vá em "New > Directory" e crie um diretório chamado xml (isso se ela já não existir). Então, clique na pasta com botão direito, vá em "New > XML Resource File". Dê um nome. O arquivo XML de preferência sempre terá como nó raiz a tag <PreferenceScreen>.
Dentro da mesma, você vai determinar os elementos e como será a interface, como:

  • CheckBoxPreference: Configura a opção como Checkbox;
  • ListPreference: configura a opção com uma lista para selecionar
  • EditTextPreference: configura em um campo de texto

Exemplo de como pode ser o XML:
<?xml version="1.0" encoding="UTF-8" ?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
  <CheckBoxPreference
    android:defaultValue="true"
    android:key="show_bass"
    android:summaryOff="Hidden"
    android:summaryOn="Shown"
    android:title="Show Bass" />
  <ListPreference
    android:defaultValue="Valor default"
    android:entries="@array/opcoes"
    android:key="list_array"
    android:title="Array List" />
</PreferenceScreen>

3. Implementar onCreatePreferences
Aqui vamos implementar o método onCreatePreferences com base no arquivo XML

@Override
  public void onCreatePreferences(Bundle savedInstanceState, String rootKey){
     addPreferencesFromResource(R.xml.nome_xml);
  }

4. Adicionar o Fragmento na Activity
Aqui vamos usar inserir o fragmento na Activity (não entrarei agora na questão fragmento, mas veremos futuramente). No arquivo de layout da Activity, colocaremos como base, a tag fragment. Ficará assim:

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/activity_settings"
   android:name="android.example.com.visualizerpreferences.NomeDaClasse"
 android:layout_width="match_parent"
 android:layout_height="match_parent" />


Lendo um valor de Preferences
Para ler uma Preferences, você pode requisitar para PreferencesManager um acesso para as Preferences. Para isso, em algum lugar que a Activity esteja em execução, podemos invocar o um método para obeter a preferencias e usá-los para obter um valor, de forma muito semelhante a um Bundle, exceto que ele pede um segundo parâmetro que seria um valor padrão:

public void NomeActivity extends Activity{
   [...]

   public void pref(){
      SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
      int chave1 = sharedPref.getInt("chave1", 0);        
   }
}

Escrevendo um valor nas Preferences (Sem usar PreferenceFragment)
De forma bastante similar ao código anterior, mas usaremos um Editor para poder inserir um valor:


   public void prefEdit(){
      SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
      SharedPreferences.Editor editor =  sharedPref.edit();        
      editor.putInt("chave1",1);
      editor.apply();
   }


Atualizando a Activity sobre mudanças na Shared Preferences: onSharedPreferenceChangeListener
Um dos casos do SharedPreferences é que muitas vezes as opções que você alterou deve fazer efeito imediatamente, entretanto, o SharedPreferences é invisível para a Activity. Para isso precisamos implementar um Listener para ser notificado de alguma mudança.

1. Na Activity, implementar a interface onSharedPreferenceChangeListener

public void NomeActivity extends Activity implements SharedPreferences.OnSharedPreferenceChangeListener{
   
  @Override
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key){
    //Implementar aqui o que vai mudar, geralmente baseado
    //na key
  }  

}

2. Registrar o Listener

public void NomeActivity extends Activity implements SharedPreferences.OnSharedPreferenceChangeListener{
    public void registerListener(){
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        sharedPref.registerOnSharedPreferenceChangeListener(this);

    }   

}

3. Remover o Listener quando a Activity for destruído:

public void NomeActivity extends Activity implements SharedPreferences.OnSharedPreferenceChangeListener{
    
   @Override
   protected void onDestroy(){
     super.onDestroy();
     PreferenceManager.getDefaultSharedPreferences(this)
         .unregisterOnSharedPreferenceChangeListener(this);
   }
}


Resumo Rápido de outras funcionalidades:


  • Em Preferences de campo de texto, você pode usar o listener PreferenceChangeListener para validar o texto e impedir que um dado inválido seja gravado;

quarta-feira, 20 de junho de 2018

Rumo ao Certificado Android: Loaders

No post anterior, vimos que o Sistema Android pode destruir e reconstruir atividades conforme necessário, como na rotação do aplicativo. Entretanto, isso pode ser complicado quando usamos um AsyncTask. Se a activity estiver controlando uma AsyncTask diretamente, um processo de destruição e reconstrução fará com que a AsyncTask responda a Activity destruída, mas não a nova instancia reconstruída, logo, não terá efeito. Ou pior ainda! Irá criar uma nova thread fazendo a mesma coisa, só que para a Activity nova.

Para resolver esse problema, podemos utilizar o Loader e o LoaderManager para gerenciá-los.

Vamos entender como usá-los:

1. Crie uma classe para implementar LoadManager.LoaderCallbacks<T>, onde o T é o resultado do AsyncTask;

public class Classe implements LoaderManager.LoaderCallbacks<String>{
}

O Android Studio vai pedir para importar a classe e criar os métodos da implementação, só fazer o que foi pedido.

2. Criar uma constante para servir como identificador. É recomendado que cada recurso que utilize Loader tenha um ID único.

public class Classe implements LoaderManager.LoaderCallbacks<String>{
private static final int RESOURCE_1 = 10;

}

3. Implementar o método onCreateLoader. Este método possui dois parâmetros, o ID que nós usamos e um Bundle com os parâmetros para carregar. Aqui vamos criar um novo AsyncTaskLoader e implementar seus métodos. O código ficará assim:

public class Classe implements LoaderManager.LoaderCallbacks<String>{
private static final int RESOURCE_1 = 10;

@Override
public Loader<String> onCreateLoader(int id, final Bundle bundle){
return new AsyncTaskLoader<String>(this){

@Override
protected void onStartLoading(){
super.onStartLoading();
if(bundle==null){
return;
}
//Aqui você pode fazer um feedback de loading, por
//exemplo, exibir uma mensagem indicando o inicio
}

@Override
public String loadInBackground(){
//Aqui é a rotina de carregamento, semelhante
//ao AsyncTask visto anteriormente
}
}
}


}

4. Implementar o método onLoadFinished. Esse método vai ser chamado quando a tarefa for concluída. O código fica o seguinte:

public class Classe implements LoaderManager.LoaderCallbacks<String>{
  [...]

@Override
public void onLoadFinished(Loader<String> loader, String data){
//Aqui faz o tratamento do retorno dos dados, seja correto
//erros
}


}

5. Chamar o LoaderManager. Cada Activity tem seu próprio LoadManager e podemos conseguir invocando o método getLoaderManager. Note que dependendo se você estiver usando fragmentos, ou appCompat, pode haver variações no nome, como getSupportLoaderManager. Uma discussão sobre isso pode ser visto aqui. Então, nós tentamos inicializá-lo ou recuperá-lo, dependendo se o mesmo já foi executado antes.

public class Classe implements LoaderManager.LoaderCallbacks<String>{
   private static final int RESOURCE_1 = 10;

   public void startLoad(Activity act, Bundle bundle){
      LoaderManager loaderManager = act.getLoaderManager();
      Loader<String> loader = loaderManager.getLoader(RESOURCE_1);
if(loader == null){
loaderManager.initLoader(RESOURCE_1, bundle, act);
}else{
loaderManager.restartLoader(RESOURCE_1, bundle, act);
}
   }
}

Passo Bônus: Guardar o resultado em cache. Para isso, podemos criar uma propriedade que vai armazenar o retorno do Loader. É uma boa prática se você sabe que o resultado não vai ser diferente para uma mesma consulta, ou algo semelhante. Então, no loadInBackground você pode verificar se o valor que está no cache já é o que deseja e retornar ele, ao invés de recarregar de novamente.

Experiência Própria: Quando usar Loaders ou AsyncTask?
A resposta é bem simples, se é uma Activity, use sempre Loader, caso contrário, use AsyncTask. Se você está seguindo a série "Rumo ao Certificado Android" na ordem de publicação, até agora só trabalhamos com Activity, mas existem outras situações, que no meu caso foi a implementação de um Widget, que não havia como obter um loader, e assim, precisei usar um AsyncTask.