martes, agosto 29, 2017

Cómo pasar parámetros a un JobService

En mi post anterior expliqué como crear un JobSerivce, este job service recibe parámetros; Para pasar parámetros al JobService usamos la clase PersistableBundle de la siguiente manera.

private void requestAlertsToDelete(int[] alerts) {

   Alerts alertsRequest = Alerts.getAlertsRequest(
         SessionManager.getAuth(getActivity()),
         new int[]{},
         alerts);

   PersistableBundle bundle = new PersistableBundle();

   bundle.putString(Constants.INSTANCE.getREQUEST_PARAMS(), alertsRequest.toJSON());

   JobScheduler jobScheduler = (JobScheduler) getActivity()
                         .getSystemService(Context.JOB_SCHEDULER_SERVICE);

   JobInfo jobInfo = new JobInfo.Builder(0, new ComponentName(getActivity(), AlertJobService.class))
         .setExtras(bundle)
         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
         .build();

   if (jobScheduler != null) {
      jobScheduler.schedule(jobInfo);
   }


Esta clase acepta tipos básicos, enteros, cadenas de caracteres y algunos otros, pero no Parcelables, por lo tanto lo que hice fue crear en mi clase Alerts un método llamado toJSON() que devuelve la instancia de esa clase a JSON.

Para esto uso la librería Gson, por si acaso les dejo el código también.

public String toJSON() {
   return gs.toJson(this, Alerts.class);
}

la variable gs está definida de forma global de la siguiente manera.
public  final Gson gs = new GsonBuilder().serializeNulls()
      .disableHtmlEscaping()
      .create();

Saludos

Reemplazando Intent Services por Job Services


Android Oreo sería el mejor de todas las versiones de Android hasta ahora. La verdad la verdad, no lo comparto. Las notificaciones de aplicaciones en segundo plano son molestas e inútiles, los mensajes en apps de mensajes como WhatsApp llegan con retraso, y así por el estilo sin embargo, este post no es para hablar del sistema operativo como tal. Con los cambios hechos los Googlers te sugieren usar JobScheduler para ejecutar tareas en segundo plano,  lo resalto por eso, para ejecutar así en background, si la app esta en foreground seguimos usando intent services o los mecanismos de siempre.

JobScheduler  fue introducido en Lollipop (API 21) pero ahora es cuando me he dado la tarea de aprender sobre JS al ver los ANR en Android Oreo de mi app. La primera estrellada, JobService trabaja en el Main Thread (UI Thread) no leí esa parte y pensaba que funcionaba como un IntentService luego de meter código de networking en onStartJob me apareció el stack trace de networking en el UI thread, por lo que hay que sacar código de ejecución larga y pesada a su propio thread.

La solución (según los ejemplos de Googlers) es usar un AsyncTask , algo así como esto

@Overridepublic boolean onStartJob(final JobParameters params) {
    mDownloadArtworkTask = new DownloadArtworkTask(this) {
        @Override        protected void onPostExecute(Boolean success) {
            super.onPostExecute(success);
            jobFinished(params, !success);
        }
    };
    mDownloadArtworkTask.execute();
    return true;
}

pero lamentablemente esto tiene una inspección de código  bien fea que dice.

Static Field Leaks:  A static field will leak contexts.  Non-static inner classes have an implicit reference to their outer class. If that outer class is for example a Fragment or Activity, then this reference means that the long-running handler/loader/task will hold a reference to the activity which prevents it from getting garbage collected.  Similarly, direct field references to activities and fragments from these longer running instances can cause leaks.  ViewModel classes should never point to Views or non-application Contexts.

En español , podría ocurrir memory leaks al tener clases internas no estáticas, ya que guardan una referencia implícita del contexto de la clase externa. Si ocurren o no, no lo sé. Supongo que no, pero particularmente no me gusta tener Warnings en mi código así que decidí buscar otra forma de hacerlo.

Leyendo un poco mas, te hablan también de usar Handlers y Threads estos hace tiempo que no los usaba y había olvidado como se implementaban;  Luego de terminar mi implementación pues funciono muy bien, y no habían warnings molestos por ningún lado. Así que aquí es a donde vamos.

JobService tiene tres métodos principales:  onStartJob, onStopJob y el onCreate de siempre.

public class AlertJobService extends JobService {

   private Handler mJobServiceHandler;

   private final int TASK_COMPLETE = 0;
@Override
   public void onCreate() {
      super.onCreate();
   }


   @Override   
   public boolean onStartJob(JobParameters jobParameters) {
      return true;
   }


   @Override   public boolean onStopJob(JobParameters jobParameters) {
      return false;
   }
}

En onCreate puedes iniciar lo que quieras entre éstas y una bien importante es el handler, el cual se encargar de manejar los mensajes que reciba dese otros Threads.

Definimos una variable global de tipo Handler -yo la llame mJobServiceHandler - y la iniciamos en onCreate. Estoy asignando a este handle el main looper o en otras palabras el UI Thread.

@Overridepublic void onCreate() {

   super.onCreate();

   mJobServiceHandler = new Handler(Looper.getMainLooper()) {

      @Override
      public void handleMessage(Message msg) {
         switch (msg.what) {

            default:
               super.handleMessage(msg);
         }
      }
   };
}

Creamos el Thread para networking quedando algo así como esto.

/** * Network thread with background priority */

class NetworkThread extends Thread {

   JobParameters jobParameters;

   String url;

   String requestParameters;

   /*** Network handler constructor    
     ** @param jobParameters Job parameters of this Job service    
     * @param url           Service url    
     * @param requestParams Request parameters    
     */  
   NetworkThread(JobParameters jobParameters, String url, String requestParams) {
      this.url = url;
      this.jobParameters = jobParameters;
      this.requestParameters = requestParams;
   }

   @Override
   public void run() {

      Response response = null;

      android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

      OkHttpClient client = new OkHttpClient();

      RequestBody requestBody = RequestBody.create(JSON, requestParameters);

      Request request = new Request.Builder()
            .url(url)
            .post(requestBody)
            .build();
      try {
         response = client.newCall(request).execute();
      } catch (IOException e) {
         e.printStackTrace();
      }

      Message message = new Message();

      message.what = TASK_COMPLETE;

      Bundle bundle = new Bundle();

      bundle.putParcelable(Constants.JOB_PARAMETERS, jobParameters);

      if (response != null) {
         bundle.putString(Constants.RESPONSE, response.body().string());
      }

      message.setData(bundle);

      mJobServiceHandler.sendMessage(message);

   }
}

Llamamos el thread en onStartJob. En mi caso, estoy llamando a un servicio que me retorna una lista de alertas. Si te das cuenta, estoy creando el url y los parámetros que necesito en onStartJob para poder usar los métodos como getString(), getResources() etc. Estos tienen un contexto definido y puedo hacerlo, luego se los paso al thread desde el constructor.


@Override

public boolean onStartJob(JobParameters jobParameters) {

   if (BuildConfig.DEBUG) {
      Log.d(TAG, "onStartJob: Alert Job Service");
   }

   String url = getString(R.string.base_server_url);

   String requestParams = jobParameters.getExtras().getString(Constants.REQUEST_PARAMS, "");

   if (requestParams.isEmpty()) {
      throw new IllegalArgumentException(getResources().getString(R.string.throw_add_parameters_to_request));

   } else {

      NetworkThread networkThread = new NetworkThread(jobParameters, url, requestParams);

      new Thread(networkThread).start();
   }

   return true;
}


onStartJob retorna true para que el framework lo mantenga vivo, es decir, retornamos true si llamamos a hacer algo en otro thread y tenemos que esperar la respuesta como en el caso de mi NetworkThread.

Una vez empezado el thread, este llama al servicio remoto hace lo que tiene que hacer y regresa, al final llama a mJobServiceHandler.sendMessage(message); Ahí defino mi mensaje el cual va a ser recibido por handleMessage().  A continuación el código completo de handleMessage

@Override

public void onCreate() {

   super.onCreate();

   mJobServiceHandler = new Handler(Looper.getMainLooper()) {

      @Override
public void handleMessage(Message msg) { switch (msg.what) { case TASK_COMPLETE: if (msg.getData().containsKey(Constants.INSTANCE.getRESPONSE())) { Intent broadcast = new Intent(); Type alertListType = new TypeToken<ArrayList<Alert>>() { }.getType(); AlertsResponse response = gs.fromJson(msg.getData().getString(Constants.INSTANCE.getRESPONSE()), alertListType); broadcast.putExtra(Constants.INSTANCE.getRESPONSE(), response); LocalBroadcastManager.getInstance(AlertJobService.this).sendBroadcast(broadcast); } JobParameters parameters = msg.getData().getParcelable("jobParameters"); jobFinished(parameters, true); break; default: super.handleMessage(msg); } } }; }


Handle message tiene un switch case en msg.what esto lo definí dentro del Network thread para indicar que ya se terminó el trabajo pendiente, una vez recibido envío mi broadcast o local broadcast con el resultado, y listo.

martes, agosto 22, 2017

Límites de ejecución en segundo plano de Android Oreo

viernes, julio 21, 2017

Android 2017

Saludos de nuevo. 

Ya hace un tiempo que no escribía, he tenido bastante trabajo y como siempre he estado leyendo y aprendiendo muchas cosas sobre Android. 

Pedro Varela Google I/O 2017
Sundar Pichai Google I/O 2017

Este año tuve la oportunidad de ser seleccionado y asistir a Google IO 2017, fue una experiencia única, estar rodeado por tanta gente con tus mismos intereses, conocer lo nuevo de Android antes que muchas otras personas es fascinante, ver a todos los Googlers hablar con ellos y hacerles preguntas no tiene precio. 

En fin,  a lo que vengo con este post - y no es notica nueva - Kotlin es un lenguaje de programación oficial para Android, por lo que de ahora en adelante estaré publicando tutoriales escritos en kotlin, así como una introducción al mismo.







viernes, abril 14, 2017

Verificar Google Play Services

Para que nuestra aplicación pueda ejecutarse si necesitamos Google Play Services, tenemos que hacer la verificación previa.

Esto lo podemos lograr fácilmente agregando las siguientes líneas de código en nuestra actividad de lanzamiento o Splash Screen si es que la tenemos sino, en la primera actividad de nuestra aplicación.

Creamos el método checkPlayServices() con  el código

errorDialog es una variable global de la actividad.

Dialog errorDialog;

private boolean checkPlayServices() {

    GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();

    int resultCode = googleApiAvailability.isGooglePlayServicesAvailable(this);

    if (resultCode != ConnectionResult.SUCCESS) {
       if (googleApiAvailability.isUserResolvableError(resultCode)) {

       if (errorDialog == null) {
           errorDialog = googleApiAvailability.getErrorDialog(this, resultCode, 2404);
           errorDialog.setCancelable(false);
       }

       if (!errorDialog.isShowing())
           errorDialog.show();
        }
     }

    return resultCode == ConnectionResult.SUCCESS;
}


Y luego, en el método onResume(), hacemos la llamada al método checkPlayServices()

@Override
protected void onResume() {
    super.onResume();
    if (checkPlayServices()) {
        startApp();
    }
}


¿Por qué en onResume() y no en onCreate()? Sencillamente porque en onCreate() la llamada se va a ejecutar una sola vez; onResume() llamará checkPlayServices() hasta que actualicemos Google Play Service o cerremos la aplicación, evitando así que iniciemos nuestra app sin la versión más reciente de estos servicios de Google.

miércoles, febrero 01, 2017

Life of a Certification Student

editText.getText().toString(); #buildbetterapps

Saludos a todos.

En la mayoría de tutoriales , la misma documentación de Android te enseñan a obtener el String de un  EditText de la siguiente manera.

String phoneNumber = mTextInputEditTextPhoneNumber.getText().toString();

Perfecto, eso no tiene ningún error.  El punto está en que en ocasiones se vuelve muy fastidioso y si tenemos muchos EditText on TextInputEditText como en mi caso obtenerlos todos buuuuuff cansa. 

Recordando DataBinding en Apache Flex me dije: ok, si podemos agregar un TextChangeListener para que asignar la variable con getText().toString(), luego hice esto.

mTextInputEditTextPhoneNumber.addTextChangedListener(new TextWatcher() {
   @Override   public void beforeTextChanged(CharSequence s, int start, int count, int after) {
      
   }
   
   @Override   public void onTextChanged(CharSequence s, int start, int before, int count) {
      
   }
   
   @Override   public void afterTextChanged(Editable s) {
      phoneNumber = s.toString();
   }
});


El resultado es que mi variable phoneNumber siempre va a estar actualizada cada vez que el texto cambie en el TextInputEditTextPhoneNumber.

Dirás, es más código, pero no! Ese código lo agrega Android Studio automáticamente la única línea que tenemos que escribir es phoneNumber = s.toString(); dentro del afterTextChanged.

Espero lo consideren para sus proyectos.

Y sigan creando mejores apps! - #buildbetterapps  - 

lunes, enero 02, 2017

Solicitar permisos en Android en tiempo de ejecución más fácilmente

Feliz año a todos.

Hace tiempo que quería compartir con ustedes un forma más sencilla para solicitar permisos en tiempo de ejecución y con menos código o al menos, para no repetir tanto los métodos de solicitud de permisos en todos lados.

En este enlace pueden descargar el fragmento que hace la magia.

Extiende tu fragment de RequestPermissionFragment, implementa los métodos en tu Fragmento, lo que tienes que hacer en tu Fragmento concreto es solicitar el(los) permiso(s) y luego  verificar el permiso que quieras con ActivityCompat.checkSelfPermission, algo así:

/** * Request fine location permission */private void requestFineLocationPermission() 
{
   requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_CODE_ACCESS_FINE_LOCATION);
}


/** * Set settings of google map UI */private void setGoogleMapUISettings() {
   
   if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
      return;   
}
Si la aplicación no tiene permiso va a retornar y no hará nada. Pero los métodos extendidos de  se ejecutarán y podrás mostrar tus diálogos.

/** * Handled when all permissions requested are granted * * @param requestCode Request code */
@Overridepublic void onPermissionsGranted(int requestCode) {
   //cool nothing happens}

/** * Gets whether you should show UI with rationale for requesting a permission. * You should do this only if you do not have the permission and the context in which * the permission is requested does not clearly communicate to the user what would be the * benefit from granting this permission. */
@Overridepublic void onShouldShowRequestPermissionsRationale() {
   showRationaleLocationDialog();}

/** * Handled when all permissions requested are denied * * @param requestCode Request code */

@Override
public void onPermissionsDenied(int requestCode) {
   makeSnackBarSettings(coordinatorLayout, R.string.permission_location);
}

Espero les sea de utilidad.

Happy coding.