segunda-feira, 22 de outubro de 2018

Eclipse+Tomcat: Problemas ao iniciar TomCat

Estava eu tentando resolver um problema do cliente, quando eu vi a necessidade de reiniciar o Eclipse do cliente. Fiz isso, e aí surgiu o problema: a tarefa já estava no 100% para iniciar, mas nunca concluía. 

Procurando na Internet se achava a causa do problema e encontro a tal solução no StackOverflow. Desabilitei os breakpoints e voltou ao normal. Por algum motivo, ao publicar algo, no Eclipse pode ter alguma rotina marcada com breakpoint e isso acaba travando. Mas isso é apenas especulação.

terça-feira, 17 de julho de 2018

Rumo ao Certificado Android: Widgets

Widgets são mini-aplicativos que ficam na tela principal do Android. Eles geralmente facilitam alguma funcionalidade do aplicativo, tornando-o disponível sem precisar iniciar o aplicativo principal.

Para criar um Widget, temos os seguintes passos:

1. Criar um Widget Provider, uma classe que herdará AppWidgetProvider.

2. Criar um Widget Provider Info, um arquivo xml que contém os metadados do Widget.

3. Criar um arquivo de Layout para Widget.

4. Widgets são Broadcast Receiver, portanto é necessário registrá-los como tal no manifesto.

Android Studio facilita a criação dos Widgets, clicando com o botão direito na pasta "app", você pode ir em "New > Widget > AppWidget". Na tela que abre, configure as características do seu Widget, como nome da classe (Class Name), posicionamento (Placement), se permite redimensionar (Resizable), a largura mínima em células da altura e largura. Clique em "Finish" e o Android Studio criará o Widget Provider, Widget Provider Info, o layout e registrará o mesmo.

Ok, não adianta ter os arquivos criados e não saber usá-los, portanto, vamos verificar como usar:

Widget Provider Info
Este é o arquivo mais simples, contendo as configurações do Widget. Veja o código abaixo:

<appwidget-provider 
    xmlns:android="http//schemas.android.com/apk/res/android"
    android:initialLayout="@layout/layout_widget"
    android:minHeight="48dp"
    android:minWidth="48dp"
    android:previewImage="@drawable/launcher_icon"
    android:resizeModel="horizontal|vertical"
    android:updatePeriodMillis="1800000"
    android:widgetCategory="home_screen" />       

os parâmetros são:

  • initialLayout: layout inicial do Widget;
  • minHeight/minWidth: tamanho mínimo para altura/largura. Uma célula na main screen tem 48dp tanto para altura, quanto para largura;
  • previewImage: ícone do Widget na lista de widgets;
  • resizeModel: permite alterar o tamanho;
  • updatePeriodMillis: tempo em milisegundos para cada atualização. O tempo mínimo é de 30 minutos (1800000 milisegundos);


Widget Provider
É aqui que programaremos o nosso Widget. O WidgetProvider, como dita anteriormente, herda da classe AppWidgetProvider e precisamos sobrescrever alguns métodos. No código abaixo temos um exemplo:

public class NameWidgetProvider extends AppWidgetProvider{

  @Override
  public void onUpdate(Context c, AppWidgetManager awm, int[] appWidgetIds){
     /*
     Este método executa a cada atualização ou quando o widget é iniciado
     */
  }



}

Os principais métodos sobrescrito são:

  • onUpdate: executado a cada atualização ou inicialização de um Widget. Recebe três parâmetros:
    • Context: uma instância do contexto do aplicativo;
    • AppWidgetManager: uma instância do WidgetManager, que contém informações dos Widgets disponíveis e seus status;
    • int[] appWidgetIds: um vetor contendo as IDs das instância do Widget em questão. Como é possível ter mais de uma instância do mesmo Widget, isso permite criar um controle individual para os mesmos;
  • onDelete:

Já para obter um WidgetManager e AppWidgetIds, você pode usar os respectivos métodos

AppWidgetManager wManager = AppWidgetManager.getInstance(context);
int[] widgetsID = wManager.getAppWidgetIds(
    new ComponentName(context, NameWidgetProvider.class)
);

Isso é útil caso precise atualizar o Widget depois que uma Atividade/Serviço/qualquer coisa tenha alterado algo que posso ter modificado o Widget. Outro método útil do AppWidgetManager é o
getAppWidgetOptions, que passando um WidgetID, ele retornará um Bundle com as propriedades do Widget em questão

Bundle config = wManager.getAppWidgetOptions(widgetsID[0]);



RemoteViews
Se a Views são os componentes gráficos das Atividades, os RemoteViews são o mesmo para os Widget. Embora, são semelhante, os RemotesViews não possui todos os componentes das Views, como RecyclerView ou ConstraintLayout, mas possui as mais comuns para poder ser usadas.
Também de forma diferente das Views, nas quais nós devemos buscar o elemento com o método FindViewByID, RemoteViews permite nós acessar diretamente essas views, como setTextViewText.
O método abaixo mostra como pode ser a manipulação de um RemoteView

01. public class NameWidgetProvider extends AppWidgetProvider{

02.   static void letsUpdateInstance(Context c, AppWidgetManager awm, int widgetID){
03.      //Instanciar a RemoteView
04.      RemoteViews views = new RemoteView(c.getPackageName(), R.layout.app_widget_layout);
     
05.      //Setar um texto em um textField
06.      views.setTextViewText(R.id.appWidgetTextField, "algo");
     
07.      //Criando um evento onClick e colocando em um RemoteView
08.      Intent intent = new Intent(c,MainActivity.class);
09.      PendingIntent pendingIntent = PendingIntent.getActivity(c,0, intent, 0);
10.      view.setOnClickPendingIntent(R.id.randomImage, pendingIntent); 

11.      //Esta ação faz com que a modificação do widget fique efetiva
12.      awm.updateAppWidget(widgetID,views);
13.   }

14. }

Aqui temos bastante coisas interessantes: nas linhas 03 e 04 nós obtemos um RemoteViews, na linha 05 e 06 nós atualizamos o texto de um textView. Na linha 07 até 10, nós programamos uma View para quando ela for clicada, ela abra uma atividade (usando um PendingIntent). e na linha 12, nós atualizamos a instancia com a view modificada. Tenha em mente que não precisa necessariamente ser uma Activity a ser chamada, podendo ser um serviço ou um broadcast.

Notas Importantes:
Layout: A partir do API 14, o sistema já cria uma margem para os widgets, entretanto, para versões anteriores é necessário setar a margem manualmente.

domingo, 15 de julho de 2018

Rumo ao Certificado Android: Touch Selector

O que é?
Touch Selector é um arquivo XML que configura algumas propriedades visuais de acordo com o estado da view, especificadamente, quando um elemento é tocado.

Criando um Touch Selector
Crie um novo arquivo XML na pasta Drawable do Res. Na tela de configuração de "New Resource", certifique-se que o Resource Type é "Drawable" e que o Root Element é "selector".
Então podemos criar o novo selector, cujo código se assemelha com o seguinte:

<?xml version="1.0" encoding="utf-8" ?>
<selector 
   xmlns:android="http://schemas.android.com/apk/res/android">

   <item 
     android:drawable="@color/colorPrimaryLight"
     android:state_pressed="true" />  
   <item 
     android:drawable="@color/colorPrimaryLight"
     android:state_activated="true" />
   <item 
     android:drawable="@color/colorPrimaryLight"
     android:state_selected="true" />

   <item 
     android:drawable="@android:color/background_light" />

</selector>

No código acima, temos três estados em que uma view pode ter quanto aos toques e no final, um valor padrão para quando os mesmos não estão selecionados.

Aplicando Touch Selector
Em um arquivo de layout, encontre a view que receberá o selector e o que será alterado com o mesmo. Olhe no exemplo abaixo:

<LinearLayout
   android:background="@drawable/item_selector" />

Aqui temos um LinearLayout que mudará a cor do background de acordo com o que foi especificado no Selector.

Rumo ao Certificado Android: Styles e Themes

O que são?
São configuração de aparência aplicados em uma view (Style) ou Activity (Themes).

Criando uma Style
Para criar uma style é necessário criar um arquivo XML contendo as informações da style. O código abaixo é um exemplo:

01. <resources>
  
02.  <style name="AppTheme"
03.      parent="Theme.AppCompat.Light.DarkActionBar">

04.    <item name="colorPrimary">@color/colorPrimary</item>
05.    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
06.    <item name="colorAccent">@color/colorAccent</item>

07.  </style>

08.  <style name="ActivityLabelStyle">
   
09.    <item name="android:layout_width">match_parent</item>
10.    <item name="android:layout_height">90dp</item>
11.    <item name="android:gravity">center_vertical</item>
12.    <item name="android:padding">16dp</item>
13.    <item name="android:textColor">@android:color/white</item>
14.    <item name="android:textStyle">bold</item>
15.    <item name="android:textAppearance">?android:textApperanceMedium</item>
16.    <item name="android:layout_marginBottom">8dp</item>
17.    <item name="android:background">@color/colorPrimary</item>
18.  </style>

19. </resources>

Para criar um style, usamos a tag <style> e definimos as configurações com a tag item, onde o nome é o atributo a ser modificado e o valor é o próprio valor. No código acima, temos dois styles diferentes. A primeira, que na verdade é um theme, tem uma propriedade interessante: parent, onde ele está referenciando um outro style. Essa propriedade faz uma herança, ou seja, todo as configurações do style referenciado são setado igual ao do pai, e você pode sobrescreve-las conforme a necessidade.

Usando os Styles
Para usar um estilo em uma view, você pode utilizar o atributo style da view com o nome da style definido no arquivo de styles. Por exemplo:

<TextView
  android:id="@+id/nomeTextView"
  style="@style/ActivityLabelStyle" />

Aqui nós aplicamos a style ActivityLabelStyle em uma TextView.

Aplicando um theme
Para aplicar um tema ao aplicativo, você deve definir no manifesto, na tag "application" a propriedade "android:theme" igual ao style do tema. Semelhante ao seguinte:

<application
  android:theme="@style/AppTheme" />


Rumo ao Certificado Android: Data Binding com Layout

O que é?
Para evitar pesquisar no layout a view desejada, podemos definir um vínculo com a view e uma variável de uma classe. Este processo é denominado Data Binding.

Como utilizar?
Existem 5 passos para poder utilizar o Data Binding:

1. Habilitar o Data Binding no build.gradle, isso é feito dentro da seção android, como mostra o código abaixo:

android{
  dataBinding.enabled = true;
}

2. Adicionar <layout> como tag raíz da interface, Android vai criar automaticamente uma classe de Binding para os xml de layout que possuir esta tag como raíz, como mostra o exemplo abaixo:

<layout 
  xmlns:android="http//schemas.android.com/apk/res/android">

</layout>


3. Criar uma instância de Binding. Depois do passo 2, o Android deve criar as classes de binding baseadas no nome do arquivo XML dos layouts. Por exemplo, um arquivo xml com o nome "activity_main.xml" gerará uma classe chamada ActivityMainBinding. Onde você gerenciar os bindings, tenha certeza de instanciar esta classe para poder realizar os bindings.

4. Setar a view de conteúdo usando o DatabindingUtil, aqui nós vamos inflar o layout para o Data Binding. Usaremos o método estático setContentView da classe DataBindingUtil para isto. Ele leva dois parâmetros, a primeira a atividade e o segundo, a referência do layout a ser inflado. O código abaixo mostra um exemplo combinado do passo 3 e 4:

public class NameActivity extends Activity{
   ActivityNameBinding binding;

   @Override
   protected void onCreate(Bundle savedInstanceState){
     super.onCreate(savedInstanceState);

     binding = DataBindingUtil.setContentView(this, R.layout.activity_name);
   }

}

5. Vincular cada atributo da view com seus respectivos dados. A melhor maneira de fazer isso é criar uma classe modelo dos atributos que vão ser vinculados, e então, na atividade, usar cada atributo do objeto de bind com o atributo do modelo, como no exemplo abaixo:

public class NameActivity extends Activity{
   
   ActivityNameBinding binding;

   public void vincular(){
      Model m = loadModel(); //apenas abstrai que obtém o dado aqui

     binding.textViewString1.setText(m.string1);
     binding.textViewString2.setText(m.string2);
     binding.textViewString3.setText(m.string3);
   }

   public class Model{
      public string string1;
      public string string2;
      public string string3;
   }
}




sábado, 14 de julho de 2018

Rumo ao Certificado Android: Constraint Layout

O que é?
Para evitar que o aninhamento de Views exagerado, foi criado o Constraint Layout para melhor configurar os elementos do layout. Para utilizá-lo, primeiro deve-se instalar a biblioteca. Para isso, realize os seguintes passos:

1. Abre o SDK Manager (Tools > Android > SDK Manager);
2. Clique na aba SDK Tools;
3. Expanda o "Support Repository" e verifique o "ConstraintLayout for Android" e o "Solver for ConstraintLayout". Clique em "Show Package Details" e verifique a versão;
4. Clique em  OK;
5. Adicione o ConstraintLayout como dependência no arquivo build.gradle, como no exemplo abaixo (colocar a versão obtida no passo 3, por favor)

dependencies{
   compile 'com.android.support.constraint.constraint-layout:1.0.0'
}

6. Sincronize o projeto com o Gradle;

Usando no Layout
Para usar o ConstraintLayout, no XML do layout deve ter como raiz o ConstraintLayout, como no exemplo abaixo:

<android.support.constraint.ConstraintLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

<!--conteúdo aqui --> 

</android.support.constraint.ConstraintLayout>

As views dentro de um ConstraintLayout geralmente precisam de pelo menos duas restrições, uma vai determinar a posição horizontal e a outra a vertical, e sempre, em relação a uma outra view ou ao pai. As restrições podem ser atribuídos pelo atributo app:layout_constraint[tipo], como no exemplo abaixo:

<TextView
  app:layout_constraintTop_toTopOf="parent"
  app:layout_constraintLeft_toLeftOf="parent" />

No exemplo acima, temos duas restrições: o primeiro é que o topo da view deve estar junto com o topo da view pai. E a segunda restrição é o lado Esquerdo da view deve estar junto com o lado esquerdo do pai. Sem configuração de margens, o elemento ficará alinhado no canto superior esquerdo da view pai.  Existem vários outros tipos de restrição, mas basicamente ele irá alinhar um lado da view com um lado da outra view a ser referenciado, e assim as views vão se posicionando.


Rumo ao Certificado Android: Broadcast Receiver

O que é?
Broadcast Receiver permite que o aplicativo obtenha os intents enviados pelo sistema. Por exemplo, quando um fone de ouvido é plugado, o sistema envia um Intent para os aplicativos indicando que o fone foi plugado. Esse intent pode ser capturado se o aplicativo possuir um Broadcast Receiver e tratar essa ação. Para obter um Intent específico, utiliza-se de um Intent Filter, para filtrar os intents.
Broadcaster Receivers podem ser estáticos ou dinâmicos.

Classe do Broadcast Receiver
Para criar uma classe para tratar os broadcast, devemos herdar BroadcastReceiver e sobrescrever o método onReceive como o código abaixo:

public class NomeBroadcasterReceiver extends BroadcastReceiver{
  @Override
  public void onReceive(Context context, Intent intent){

  }
}

Broadcast Receiver Estáticos
São definidos no manifestos e são executados SEMPRE que o sistema mandar um broadcast com a intent com filtro desejado. Deve-se tomar muito cuidado, pois muitas vezes é desnecessário esse tipo de aplicação e diminuir o desempenho do sistema.

Registro no Manifesto
Como um dos quatro componentes do Android, Broadcast Receiver devem ser registrados no manifest. O código se assemelha abaixo (neste caso, quando uma foto foi tirada):

<receiver android:name=".NomeBroadcasterReceiver">
  <intent-filter>
    <action android:name="com.android.camera.NEW_PICTURE" />
    <action android:name="android.hardware.action.NEW_PICTURE" />
    <data android:mimeType="image/*" />
  </intent-filter>
</receiver>

Broadcast Receiver Dinâmicos
Os broadcast receiver dinâmicos são registrados em tempo de execução das atividades, geralmente nos métodos onResume e onPause. No método onResume, nós instanciamos um IntentFilter e o Broadcast Receiver e usamos o método registerReceiver para registrá-los.
Em onPause, nós removemos o registro com o unregisterReceiver para evitar disparos fora da execução do aplicativo.

public class NomeActivity extends Activity{
   
   NomeBroadcasterReceiver receiver;

   @Override
   protected void onResume(){
      super onResume();
      IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
      receiver = new NomeBroadcasterReceiver();
      registerReceiver(receiver, receiverFilter);
   }

   @Override
   protected void onPause(){
      super.onPause();
      unregisterReceiver(receiver);
   }
}

terça-feira, 10 de julho de 2018

Rumo ao Certificado Android: Programando execuções de serviço com JobScheduler

JobScheduler é uma classe para criar rotinas que serão executadas depois de um período de tempo.
Você pode obter um JobScheduler invocando o serviço de sistema Job Scheduler como mostra o código abaixo:

JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);

E você pode usar então a classe JobInfo para configurar o serviço que será executado:

JobInfo job = JobInfo.Builder(
    MY_BACKGROUND_JOB,
    new ComponentName(context, MyJobService.class)) 
//Aqui já criou o job, a partir daqui é configuração
    .setRequireNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
    .setRequiresCharging(true)
    .setBackoffCriteria(TWO_MINUTES, BACKOFF_POLICY_EXPONENTIAL)
    .setMinimumLatency*FIFTEEN_MINUTES)
    .build() //
js.schedule(job);

Esse é o método clássico, mas também podemos utilizar o Firebase Job Dispatcher para fazer o mesmo. Primeiro criamos uma instância do GooglePlayDriver e criamos um Dispatcher do Firebase:

Driver driver = new GooglePlayDriver(context);
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(driver);

Então configuramos um novo Job:

Job job = dispatcher.newJobBuilder()
    .setService(MyJobService.class) //Indicando a classe serviço
    .setTag("myJob") //Identificador do job. Tem que ser único
    .setRecurring(false) //se o job repete
    .setLifetime(Lifetime.UNTIL_NEXT_BOOT)
    .setTrigger(Trigger.executionWindow(0,900))
    .setReplaceCurrent(true)
    .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
    .setConstraints(
       Constraint.ON_UNMETERED_NETWORK,
       Constraint.DEVICE_CHARGING
    )
    .build();
dispatcher.schedule(job);

Mas porque usar o FirebaseJobDispatcher ao JobScheduler? Se você busca o máximo de retro-compatibilidade, o JobScheduler está disponível desde a API 21, enquanto o FirebaseJobDispatcher desde API 9.

E também um lembrete importante é que o dispositivo ou o Android Studio deve ter instalado o Google Play Services e adicionar a dependência no Gradle:

dependencies{
    compile 'com.firebase:firebase-jobdispatcher:0.5.0'
}

O serviço será uma classe que herda a classe JobService, na qual precisará sobrescrever dois métodos, onStartJob e onStopJob

import 'com.firebase.jobdispatcher.JobService'
public class myJob extends JobService{

   @Override
   public boolean onStartJob(JobParameters jParam){
     //Aqui será a lógica para quando o job iniciar, geralmente com 
     //um AsyncTask 
   }

   @Override
   public boolean onStopJob(JobParameters jParam){
     //Lógica para quando as condições de execução não é mais válida
     //e precisa de tratamento
   }

}

Não esquecer também de registrar o serviço.


segunda-feira, 9 de julho de 2018

Rumo ao Certificado Android: Services

Então chegamos ao nosso terceiro dos quatros componentes principais do Android: os serviços. Você deve criar um serviço para tarefas que precisam ser executados, mas que não interagem com o usuário diretamente, geralmente sendo executados em plano de fundo. Geralmente, uma atividade pode iniciar um serviço, e este serviço continuará rodando mesmo depois da atividade, ou até mesmo o aplicativo ser fechado.

Como iniciar um serviço
Existem 3 formas de executar um serviço:
1. Iniciar de forma explícita: a partir de um contexto, executar o método startService(). A atividade mandará o comando para executar o serviço, mas não espere retorno direto do mesmo com a atividade; Este será o foco do tutorial de hoje.

2. Programar a execução: Os serviços que são programados para ser executados são chamados de Job Services, e utiliza-se de um Job Scheduler para ser executado;

3. Atrelado com Atividade: semelhante ao primeiro, com a diferença que o mesmo pode se comunicar com a atividade que o iniciou;

A imagem abaixo mostra o ciclo de vida de um serviço para os casos 1 e 3:


Ok, mas como iniciamos os serviços?
Para o caso 1, nós utilizamos de um Intent, e de forma muito semelhante de como usamos para chamar outra atividade, nós usaremos um Intent para chamar um serviço. Observe o código abaixo:

Intent myIntent  = new Intent(this, MyIntentService.class);
startService(myIntent);

Supondo que o código acima esteja em um método de uma Activity e que exista uma classe chamada MyIntentService, é assim que iniciaremos um serviço.

Criando a classe Serviço
Um serviço iniciado por Intent irá herdar a classe IntentService. Nela devemos sobrescrever o método onHandleIntent e colocar nela a lógica a ser executado. Repare que o mesmo método recebe um Intent como parâmetro, que é o mesmo passado pelo método startService.

public class MyIntentService extends IntentService{

  @Override
  protected void onHandleIntent(Intent intent){
     //Aqui que você colocará a lógica do serviço
  }
}

Lembrando que assim como Activity e Content Provider, os Serviços devem ser registrado no manifesto do Android.



quinta-feira, 5 de julho de 2018

Rumo ao Certificado Android: Pending Intention e Notificações

O que é Pending Intention?

Pending Intention é uma Intention criada para dar para outros aplicativos, acesso a Activities, Services, etc do teu aplicativo, mesmo que o teu app não esteja em execução. Por exemplo, para uma notificação do Android abrir uma tela do seu aplicativo, uma Pending Intention pode ser criada e usada pelo serviço de notificação do Android.

Como criar uma Pending Intention?
Pending Intention usa uma Factory e vai depender do tipo de ação você quer, por exemplo, existe o getActivity, getService, getBroadcast, etc. Entretanto, todos eles terão 4 parâmetros:

  • Contexto: contexto da atividade que vai PendingIntention;
  • ID: um id para poder manipular o PendingIntention depois de criado. Garanta que cada PendingIntention tenha um id único, que vá permitir cancelar ou atualizar o mesmo no futuro;
  • Intent: o intent com a ação que deseja fazer;
  • Flag: flag indicando como deve tratar a Intention, por exemplo, FLAG_UPDATE_CURRENT irá tentar criar a PendingIntent, mas se a mesma já existir, atualizará os novos dados;
Exemplo do código:

PendingIntent.getService(context,
                         ID,
                         myIntent, 
                         FLAG_UPDATE_CURRENT);

Notificações
Para notificações, nós vamos criar uma classe específica para isso. Vamos primeiro criar um método para criar um PendingIntent para a nossa notificação. Nela, criamos uma intent que iniciará a atividade MainActivity, e então, usamos o getActivity para criar o PendingIntent.

01. public class NotificationUtils { 
02.   private static final int PENDING_INTENT_ID = 1;
03.   private static PendingIntent contentIntent(Context context) {

04.      Intent startActivityIntent = new Intent(context,                                                                           MainActivity.class);
05.      return PendingIntent.getActivity(context,
                                          PENDING_INTENT_ID,                       
                                          startActivityIntent,                     
                                          PendingIntent.FLAG_UPDATE_CURRENT);
06.   }

07. }


Para adicionar um ícone para a nossa notificação, adicione um ícone na pasta Drawable e vamos adicionar o método abaixo, onde pegamos o ícone no Resource e convertemos em bitmap.

01. private static Bitmap largeIcon(Context context) {            
02.     Resources res = context.getResources();    
03.     Bitmap largeIcon = BitmapFactory.decodeResource(res,  
                              R.drawable.ic_local_drink_black_24px);    
04.     return largeIcon;
05. }

Agora vamos criar a nossa notificação: começamos obtendo uma instância do NotificationManager usando o método getSystemService do contexto, passando como parâmetro, a constante NOTIFICATION_SERVICE (linha 04).

A partir do Android O (Oreo), nós devemos criar um canal de notificação. Por isso verificamos se o sistema do SDK é para Android O (linha 05) e se for, criamos um objeto NotificationChannel, com um ID do canal, o nome do canal, a prioridade (linha 06) e então mandamos para o notificationManager.(linha 07).

Então, vamos criar a notificação. Para isso, podemos usar um builder, que possui diversos métodos para configurar a aparência da notificação (linha 12). O principal dessa linha é o método setContentIntent, que recebe o PendingIntent que declaramos anteriormente e o setAutoCancel, para que o mesmo desapareça assim que for clicado.

O próximo trecho é a configuração de prioridade que existe a partir da versão Jelly Beam, mas antes do Oreo. Então fazemos esta verificação (linha 13) e se for verdadeira, setamos uma prioridade (linha 14).

Finalmente, na linha 16, usamos o notify para gerar a notificação.

01. private static final int NOTIFICATION_ID = 1138;
02. private static final String NOTIFICATION_CHANNEL_ID = "reminder_notification_channel";

03. public static void remindUserBecauseCharging(Context context) {
    
04.        NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
        
05.        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
06.            NotificationChannel mChannel = new NotificationChannel(
                    NOTIFICATION_CHANNEL_ID,
                    context.getString(R.string.main_notification_channel_name),
                    NotificationManager.IMPORTANCE_HIGH);
07.            notificationManager.createNotificationChannel(mChannel);
08.        }
        
12.        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context,NOTIFICATION_CHANNEL_ID)
                .setColor(ContextCompat.getColor(context, R.color.colorPrimary))
                .setSmallIcon(R.drawable.ic_drink_notification)
                .setLargeIcon(largeIcon(context))
 .setContentTitle(context.getString(R.string.charging_reminder_notification_title))
 .setContentText(context.getString(R.string.charging_reminder_notification_body))
                .setStyle(new NotificationCompat.BigTextStyle().bigText(                        context.getString(R.string.charging_reminder_notification_body)))
                .setDefaults(Notification.DEFAULT_VIBRATE)
                .setContentIntent(contentIntent(context))
                .setAutoCancel(true);

13.        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
                && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
14.            notificationBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
15.        }

16.         notificationManager.notify(NOTIFICATION_ID, tificationBuilder.build());
17.    }

Ações nas Notificações
As vezes, não é prático a notificação abrir o aplicativo enquanto a escolha do que fazer pode ser implementado na própria notificação. E é isso que vamos permitir. O  Android permite atribuir até 3 ações em uma notificação. O Código abaixo mostra como se cria uma ação:

private static Action drinkWaterAction(Context context) {
        Intent incrementWaterCountIntent = new Intent(context, WaterReminderIntentService.class);
        incrementWaterCountIntent.setAction(ReminderTasks.ACTION_INCREMENT_WATER_COUNT);
        PendingIntent incrementWaterPendingIntent = PendingIntent.getService(
                context,
                ACTION_DRINK_PENDING_INTENT_ID,
                incrementWaterCountIntent,
                PendingIntent.FLAG_CANCEL_CURRENT);
        Action drinkWaterAction = new Action(R.drawable.ic_local_drink_black_24px,
                "I did it!",
                incrementWaterPendingIntent);
        
return drinkWaterAction;
 }

Basicamente ele cria um Intent para chamar um serviço e configura os parâmetros para o mesmo. Então adiciona-o em um PendingIntent;. Depois instancia-se um objeto Action com um ícone, uma mensagem e o PendingIntent que vai ser executado quando selecionado. Para adicionar o mesmo, no NotificationBuilder, use o método addAction para adicionar o objeto Action.


Limpar as Notificações
Dependendo das ações no aplicativo, também pode ser conveniente que devemos limpar as notificações quando as mesmas já não são mais necessárias. Para isso, podemos implementar o seguinte método:

public static void clearAllNotifications(Context context) {
   NotificationManager notificationManager =             (NotificationManager) context.getSystemService (Context.NOTIFICATION_SERVICE);
   notificationManager.cancelAll();
}

o método cancelAll do notificationManager limpa todas as notificações..

terça-feira, 3 de julho de 2018

Rumo ao Certificado Android: ViewModel

O que é?
ViewModel é uma classe utilizada para manter os dados relacionados com os elementados gráficos, como se fosse um cache, para os mesmos persistirem depois de alguma mudança de configuração, como rotação de tela.

Como usar?
1. Criando uma Classe ViewModel
Crie uma classe que herdará a classe AndroidViewModel e coloque para implementar o construtor:

01. public class MainViewModel extends AndroidViewModel{
   
02.    public MainViewModel(@NonNull Application app){
03.       super(app);
04.    }
05. }


2. Crie o cache
Continuaremos utilizando o exemplo anterior com o Objeto LiveData e o DAO, então criaremos uma Lista de NameEntry que modificamos no tutorial de LiveData (linha 02) e um getter para esta variável (linha 06-09):

01. public class MainViewModel extends AndroidViewModel{
   
02.    private LiveData<List<NameEntry>> names;

03.    public MainViewModel(@NonNull Application app){
04.       super(app);
05.    }

06.    public LiveData<List<NameEntry>> getNames(){
07.       return names;
08.    }
09. }

3. Inicialize o cache
Obtenha o banco de dados e popule a variável cache no construtor (linha 05-06):

01. public class MainViewModel extends AndroidViewModel{
   
02.    private LiveData<List<NameEntry>> names;

03.    public MainViewModel(@NonNull Application app){
04.       super(app);
          
05.       AppDatabase db = AppDatabase.getInstance(
                                  this.getApplication());
06.       names = db.NameDao().getAllNames();
                             
07.    }

08.    public LiveData<List<NameEntry>> getNames(){
09.       return names;
10.    }
11. }

4. Obtendo os dados do ViewModel
Pegando o exemplo do tutorial LiveData, vamos usar a mesma atividade e métodos visto no adicionando Observer. Vamos fazer as seguintes mudanças, agora não vamos mais pegar o dados direto do Banco de Dados, mas do viewModel, portanto, vamos instânciar o ViewModel e pegar os dados de lá (Linhas 03-04):

01. public class ActivityName extends Activity{

02.   private void getAllNames(){
03.     MainViewModel vmodel = ViewModelProviders.of(this)
                                                 .get(MainViewModel.class);
04.     vmodel.getNames().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.}

Mas e se precisar de um Parâmetro
O caso visto até agora é o caso mais simples, obtendo dados só requisitando, sem parâmetros algum. Mas e se nós precisar de um parâmetro? Por exemplo, para a consulta de Nomes por id? Como fazemos? Bem, para relembrar, vamos pegar a segunda consulta do DAO em questão e colocar o LiveData nele (linha 12):

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.   LiveData<NameEntry> loadNameById(int id);
13. }

1. Criando uma nova ViewModel
Como vamos criar uma nova ViewModel para a consulta modificada. Vamos dar o nome de SingleNameViewModel. Como estaremos gerando os objetos dessa classe através de uma Factory, podemos herdar somente o ViewModel ao invés do AndroidViewModel (Linha 01). Nesta classe um membro privado que representa o dado obtido (Linha 02) e um getter para o mesmo (Linha 06-08). Também iniciaremos o mesmo em um construtor fazendo uma consulta ao banco de dados (Linha 03-05).

01. public class SingleNameViewModel extends ViewModel{
   
02.   private LiveData<NameEntry> name;

03.   public SingleNameViewModel(AppDatabase database, int taskID){
04.      name = database.nameDao().loadNameById(taskID);
05.   }

06.   public LiveData<NameEntry> getName(){
07.      return name;
08.   }
09. }

2. Criando uma Classe ViewModelFactory
Como neste caso precisamos passar um ID para o ViewModel, vamos utilizar uma ViewModelFactory para isso, dado que diversos IDs podem ser usados, cada um independente do outro. Para isso, crie uma nova classe que herdará a classe NewInstaceFactory de ViewModelProvider (Linha 01). Essa classe vai precisar de dois membros: uma instância do banco de dados (Linha 02) e o ID do nome que queremos observar (linha 03). E também criamos um construtor para iniciar os membros (Linha 04-07). Por fim, vamos sobrescrever o método Create para retornar um novo ViewModel que usará os membros inicializados (linha 08-10).

01. public class SingleNameViewModelFactory extends ViewModelProvider.NewInstanceFactory{

02.   private final AppDatabase db;
03.   private final int nameID;

04.   public SingleNameViewModelFactory(AppDatabase database, int nameID){
05.     this.db = database;
06.     this.nameID = nameID;
07.   }

08.   public <T extends ViewModel> T create(Class<T> modelClass){
09.     return (T) new SingleNameViewModel(db, nameID);
10.   }

11. }

3. Usando em uma atividade
Em algum método da atividade que vai usar, precisaremos primeiro instanciar a Factory (Linha 03). Então segue semelhante ao anterior, mas ao invés somente a atividade, passamos também a factory (Linha 04). E de resto é igual anteriormente para pegar os dados e sobrescrevendo o OnChange do Observer (linha 05-09).

01. public class ActivityName extends Activity{
  
02.   private void randomMethod(AppDatabase db, int id){
03.      SingleNameViewModelFactory factory =  new SingleNameViewModelFactory(db,id);
04.      final SingleNameViewModel viewModel = ViewModelProviders.of(this, factory).get(SingleNameViewModel.class);
05.      viewModel.getName().observe(this, new Observer<NameEntry>(){
06.         @Override
07.         public void onChanged(@Nullable NameEntry nameEntry){
           //Atualização do LiveData
08.         }
09.      });
10.   }
11. }