martes, 15 de diciembre de 2015

Hablemos de Fragmentos I

Esta entrada no tiene por objeto ser un tutorial mas de los que arrancan de cero diciéndote que "un fragmento puede ser entendido como una subactividad, cuyo ciclo de vida depende de... bla, bla, bla".

La idea es que ya conoces lo que son los fragmentos, que has hecho alguna app usandolos, pero que tratando de dominarlos sientes que sabes menos que al principio.
A mi me paso tener que convertir una aplicacion basada en Tabs y Activities a una aplicacion basada en un Side Menu con Fragmentos. La conversion en si es bastante directa, lo que mas tienes que tener en cuenta es que ahora tienes un metodo onCreateView que es el responsable de hacer lo que en una Activity normalmente hace el metodo onCreate. Luego adaptar los metodos de callback (onStart, onResume, etc) a sus homologos para fragmentos y poco mas.
No es big deal, como se dice en ingles. Los problemas arrancan cuando ya tienes todo eso solucionado, pero tienes que hacer funcionar la navegacion.
Desde mi perspectiva los dos problemas principales que vas a encontrar y que te puede a llevar a darte la cabeza contra el monitor repetidamente son la navegacion y su derivado, el mantener estado en los fragmentos mientras ocurre la navegacion.

Navegacion entre fragmentos (sin tener en cuenta el estado de las vistas)

Por ejemplo supongamos que tenes una app con dos actividades y dentro de una de las actividades llamamos a tres fragmentos (A, B y C).

Es decir que iniciamos con la Activity 1 y pasamos a la Activity 2 que comienza mostrando el fragmento A, luego el B y luego el C:



Si el usuario viendo el Fragmento C le diera al boton Back, esperaria ver el Fragmento B, pero nones. Veria la IU de la Activity 1. Y no, esto no es lo esperado.
La clave para la navegacion con Fragmentos pasa por un objeto que se llama BackStack. Suele ser mas incomprendido  que una película de Fellini, pero es una de las claves de la felicidad en el mundo Fragment. La fuente de la incomprehension es que la gente normalmente piensa en el como en un Stack donde los objetos fragmentos se apilan. Y no se trata de apilar Fragmentos sino de apilar transacciones.

Suponte que quieres agregar esos tres fragmentos:


        FragmentA fa = new FragmentA();

        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

        fragmentTransaction.add(R.id.fragments, fa, "A");

        fragmentTransaction.addToBackStack("addA");

        fragmentTransaction.commit();

        .....



        FragmentB fb = new FragmentB();

        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

        fragmentTransaction.add(R.id.fragments, fb, "B");

        fragmentTransaction.addToBackStack("addB");

        fragmentTransaction.commit();

        ......



        FragmentC fc = new FragmentC();

        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

        fragmentTransaction.add(R.id.fragments, fc, "C");

        fragmentTransaction.addToBackStack("addC");

        fragmentTransaction.commit();



Si el container (R.id.fragments) fuera un FrameLayout (luego veremos que con un LinearLayout no verias lo mismo) verias aparecer la sequencia
A -> B -> C
La clave aqui es si usas o no addToBackStack. Si usas, tus transacciones pasaran a la pila y por ende seran reversibles. Es decir que al ir hacia atras el usuario, podra recorrer el camino inverso, navegando hacia el origen del Stack, re-ejecutando las transacciones que va encontrando en su camino descendente.
El BackStack es una pila de transacciones no una pila de Fragmentos.



Total que para navegar hacia atras entre fragmentos lo que necesitamos es interceptar el boton Back y navegar hacia atras por el BackStack.

La forma de ir hacia atras en el backstack es utilizando el metodo popBackStack() y conectandolo con la accion del boton Back. Es decir que popBackStack() se comportara como el boton Back, en el sentido de que navega hacia atras por la pila. Hacia la anterior transaccion inferior siguiente si es que no lleva parametro y hacia una transaccion determinada si se le pasa como parametro el ID de la transaccion.

Para ver todo esto funcionando te dejo este Proyecto BackNav en Github. Es una app que consta de dos actividades y tres fragmentos que se van agregando en la segunda actividad. Al tratar de navegar hacia atras la aplicacion solo se movera entre Activities. Si descomentas el bloque de codigo onBackPressed() podras navegar hacia atras entre los fragmentos.

 @Override
    public void onBackPressed() {
        // your code.
        int t = mFragmentManager.getBackStackEntryCount();
        if(t > 1){
            FragmentManager.BackStackEntry bse = mFragmentManager.getBackStackEntryAt(t-1);
            Log.i("MAIN2","Estaba en: "+bse.getName());
            mFragmentManager.popBackStack();//retrocede en la pila
        }else{
            super.onBackPressed();//como si apretaran el back entre actividades
        }
    }
    


En onBackPressed() tenemos dos metodos importantes;

getBackStackEntryCount(); que devuelve la cantidad de entradas que tenemos en la pila.
getBackStackEntryAt(x); que devuelve la entrada de la posición x

Con estos dos metodos comprobamos si tenemos entradas en la pila y, si tenemos, retrocedemos con popBackStack(), si ya no tenemos mas ejecutamos super.onBackPressed() que causa el mismo efecto que el boton Back entre actividades, llevandonos a la Actividad 1.

Entendiendo mejor el BackStack

En el Proyecto BackStack Experiments puedes bajarte un ejemplo de codigo que te permitira ver el estado del backstack a medida que ejecutas transacciones con tus fragmentos.

Se trata de una Activity con dos fragmentos y varios metodos para operar con ellos:

public class MainActivity extends Activity implements FragmentManager.OnBackStackChangedListener, FragmentA.OnFragmentInteractionListener, FragmentB.OnFragmentInteractionListener{



    FragmentManager mFragmentManager;

    TextView mBackStack;



    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        setContentView(R.layout.activity_main);

        mBackStack = (TextView)findViewById(R.id.backstack);

        mFragmentManager = getFragmentManager();

        mFragmentManager.addOnBackStackChangedListener(this);



    }



    public void addA(View v){

        FragmentA fa = new FragmentA();

        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

        fragmentTransaction.add(R.id.fragments, fa, "A");

        fragmentTransaction.addToBackStack("addA");

        fragmentTransaction.commit();

    }

    public void addB(View v){

        FragmentB fb = new FragmentB();

        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

        fragmentTransaction.add(R.id.fragments, fb, "B");

        fragmentTransaction.addToBackStack("addB");

        fragmentTransaction.commit();

    }

    public void removeA(View v) {

        FragmentA fa = (FragmentA) mFragmentManager.findFragmentByTag("A");

        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

        if (fa != null) {

            fragmentTransaction.remove(fa);

            fragmentTransaction.addToBackStack("removeA");

            fragmentTransaction.commit();

       } else {

            Toast.makeText(this, "Fragment A not added", Toast.LENGTH_LONG).show();

        }

    }

    public void removeB(View v) {

        FragmentB fb = (FragmentB) mFragmentManager.findFragmentByTag("B");

        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

        if (fb != null) {

            fragmentTransaction.remove(fb);

            fragmentTransaction.addToBackStack("removeB");

            fragmentTransaction.commit();

        } else {

            Toast.makeText(this, "Fragment B not added", Toast.LENGTH_LONG).show();

        }

    }

    public void replaceA(View v){

        FragmentA fa = new FragmentA();

        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

        fragmentTransaction.replace(R.id.fragments, fa, "A");

        fragmentTransaction.addToBackStack("replaceWithA");

        fragmentTransaction.commit();

    }

    public void replaceB(View v){

        FragmentB fb = new FragmentB();

        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

        fragmentTransaction.replace(R.id.fragments, fb, "B");

        fragmentTransaction.addToBackStack("replaceWithB");

        fragmentTransaction.commit();

    }

    public void attachB(View v){

        FragmentB fb = (FragmentB) mFragmentManager.findFragmentByTag("B");

        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

        if(fb != null){

            fragmentTransaction.attach(fb);

            fragmentTransaction.addToBackStack("attachB");

            fragmentTransaction.commit();

        }else{

            Toast.makeText(this, "Can't attach Fragment B because does not exist", Toast.LENGTH_LONG).show();

        }



    }

    public void detachB(View v){

        FragmentB fb = (FragmentB) mFragmentManager.findFragmentByTag("B");

        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

        if(fb != null){

            fragmentTransaction.detach(fb);

            fragmentTransaction.addToBackStack("detachB");

            fragmentTransaction.commit();

        }else{

            Toast.makeText(this, "Can' detach Fragment B because does not exist", Toast.LENGTH_LONG).show();

        }

    }

    public void pop_add_B(View v){

        mFragmentManager.popBackStack("addB", 0);

    }



    public void back(View v){

        mFragmentManager.popBackStack();

    }





    @Override

    public boolean onCreateOptionsMenu(Menu menu) {

        // Inflate the menu; this adds items to the action bar if it is present.

        getMenuInflater().inflate(R.menu.menu_main, menu);

        return true;

    }



    @Override

    public boolean onOptionsItemSelected(MenuItem item) {

        // Handle action bar item clicks here. The action bar will

        // automatically handle clicks on the Home/Up button, so long

        // as you specify a parent activity in AndroidManifest.xml.

        int id = item.getItemId();



        //noinspection SimplifiableIfStatement

        if (id == R.id.action_settings) {

            return true;

        }



        return super.onOptionsItemSelected(item);

    }



    @Override

    public void onBackStackChanged() {

        mBackStack.setText(mBackStack.getText()+"\n");

        mBackStack.setText("BackStack Status:\n");

        mBackStack.setText("-----------------\n");

        int count = mFragmentManager.getBackStackEntryCount();

        for(int i=count-1; i>= 0 ; i--){

            FragmentManager.BackStackEntry entry = mFragmentManager.getBackStackEntryAt(i);

            mBackStack.setText(mBackStack.getText()+" "+entry.getName()+" \n");

        }

    }



    @Override

    public void onFragmentInteraction(Uri uri) {

        Log.i("MainActivity", uri.toString());

    }

}

Es recomendable que compruebes la diferencia entre utilizar como host view de tus fragmentos un FrameLayout (que los apila en el eje Z) y un LinearLayout que los instala en un mismo plano de manera que el primero tiene prioridad sobre el segundo. Y si usa;

 android:layout_width="match_parent"
 android:layout_height="match_parent"
,

directamente no veras el segundo y te parecera que nada ha pasado al agregar el segundo fragmento.

En la proxima entrada vemos como mantener estado en las vistas. Espero que te sirva. Tus comentarios seran bienvenidos.



domingo, 26 de julio de 2015

MAiLog: Libreria para recibir logs por correo, desde aplicaciones ejecutando en dispositivos


Introduccion

Después de buscar un rato y no encontrar nada que me convenza decidi crear una libreria que permite recibir un email con los contenidos de Logcat y System.err desde una aplicación que este corriendo en un dispositivo fisico.
Esta basada en javax.mail  y no en Intents (que llamen a aplicaciones locales de correo, como Gmail) de manera que envie los correos automaticamente, sin intervencion del usuario.
Para no resultar demasiado verbosa filtra del Log los tags marcados como "MAiLog" que es el tag por defecto y los del System.err.
Proximamente voy a publicarla en Github como proyecto open source para que quien quiera pueda modificarla y agregarle nuevas posibilidades. Ya que la he implementado solo con posibilidades minimas de configuracion.

Descargar la libreria

Haz click aqui para descargarte la libreria:  mailoglibrary-v1.0

To get an english version of this blog entry, please click here


Configuracion

Para utilizar MAiLog en tu proyecto solo tienes que seguir los tres siguientes pasos:


1) Colocar en /assets un fichero log_config.properties

El fichero de contener la configuracion de las cuentas de correo involucradas (solo utiliza cuentas de Gmail). El nombre log_config.properties es obligatorio:















El contenido de log_config.properties es el que sigue:

sender_email=xxxxxxx@gmail.com
sender_email_password=yyyyyyyyy
receiver_email=zzzzzzzz@gmail.com
email_subject=This is Subject
log_on=true

Los nombres de las propiedades son obligatorios tambien

2) Agregar el AAR en /libs









3) Agregar la dependencia en Gradle:












El bloque repositories tiene la finalidad de permitir usar un aar local


Uso

En la Activity se debe configurar la instancia y su inicialización como sigue

LogSingleton.getInstance().init(Context context);

Ejemplo:














Para crear una entrada de Log se utiliza la clase MAiLog, que es un wrapper de la clase Log de Android. Esta clase ofrece los tipicos metodos .e, .i, .w, .d, .v en dos versiones con diferente firma para entradas; con envio de mail y sin envio de mail.
Para registrar una entrada de Log sin enviar un email debe utilizar

MAiLog.e(String mensaje)

Ejemplo:



Si deseas capturar un Log y ademas enviar todo por mail (por ejemplo cuando ocurre una excepción) debes utilizar en tu código (normalmente dentro del catch):

MAiLog.e(String mensaje, Exception excepcion)

Ejemplo:


Donde e es el objeto Exception que devuelven el catch.
El mail recibido luce así:

















Espero vuestros comentarios!

domingo, 12 de abril de 2015

Patron para conectar con un backend Restful

Es tipico de las aplicaciones de empresa la conectividad con alguno o normalmente varios servicios web. Normalmente el estandar para servicios actualmente es el Rest.  Lo que sigue es una manera de organizar el codigo para que nuestro intercambio con el backend este ordenado. No se si corresponde llamarlo patron aunque de hecho yo lo utilizo como una estructura fija en mi codigo.


En todo intercambio de datos con el backend podemos encontrar codigo que obedece a la conexiones propiamente dichas (POST, GET, headers, etc.) y codigo que permite wrapear el intercambio (JSON o XML) como objetos java.

Objetos de dominio para intercambio JSON

Un ejemplo de codigo que permite wrapear JSON en objetos java lo tenemos a continuacion:

 public class User extends DataObject implements Serializable  {  
      private static final long serialVersionUID = 1L;  
      @Expose  
      public String firstName;  
      @Expose  
      public String lastName;  
      @Expose  
      public String email;  
      @Expose  
      public String phone;  
      public User(){  
      }  
 }  

En este caso tenemos un objeto User que podriamos estar enviando al backend luego del proceso de registracion.
Tambien podriamos manejar grupos de objetos. A continuacion tenemos un objeto de tipo Contact que luego recibiremos como una array de objetos JSON:

   public class Contact extends DataObject implements Serializable {   
      private static final long serialVersionUID = 1L;   
        @Expose   
        public String contactName;   
        @Expose   
        public String phoneNumber;   
  }   

Y para recibir un conjunto de objetos Contact usariamos una clase como:

 public class Contacts extends DataObject {  
   public Contacts(){  
   }  
   @Expose  
   public int totalCount;  
   @Expose  
   public int pageCount;  
   @Expose  
   public int pageSize;  
   @Expose  
   public int pageIndex;  
   @Expose  
   public List<Contact> results = new ArrayList<Contact>();  
 }  

La anotacion @Expose permite incluir o excluir un campo de los procesos de serializacion y deserializacion que proporciona la clase Gson. Esta entrada no pretende ser una guia exahustiva y ni siquiera introductoria sobre GSON.  Pero si deseamos incluir un campo en ambos procesos de serializacion y deserializacion utilizaremos la anotacion @Expose. Esta anotacion admite los siguientes parametros:


@Expose (serialize = true/false, deserialize = true/false)


Que permite anular alguno o ambos procesos para un campo dado. Obviamente una anotacion de tipo @Expose (serialize = false, deserialize = false) es equivalente a no anotar con @Expose un campo. Esto es el resultado sera el mismo (el campo sera ignorado).


Para deserializar un objeto JSON conteniendo datos del usuario instanciariamos el objeto Gson y luego su metodo fromJson:


 Gson gson = new Gson();  
 User user = gson.fromJson((String) result, User.class);  

Resulta conveniente para mantener nuestro codigo de conexion generico hacer extender estos objetos de una superclase con algun metodo helper.

 public class DataObject {  
      public DataObject() {  
      }   
      public List<Integer> extractArrayIntegers(String json) throws JSONException {  
           JSONArray array = new JSONArray(json);  
           List<Integer> extractedInts = new ArrayList<Integer>(array.length());  
           for (int i = 0; i < array.length(); i++) {  
                extractedInts.add(array.getInt(i));  
           }  
           return extractedInts;  
      }  
      public List<String> extractArrayStrings(String json) throws JSONException {  
           JSONArray array = new JSONArray(json);  
           List<String> extractedStrings = new ArrayList<String>(array.length());  
           for (int i = 0; i < array.length(); i++) {  
                extractedStrings.add(array.getString(i));  
           }  
           return extractedStrings;  
      }  
 }  

Los metodos helper en este caso sirven para obtener listas de String e Integer para integrar a nuestro codigo donde lo necesitemos.


Clases cliente

Siempre que conectamos con servicios necesitamos que la clase que efectua la llamada reciba el resultado, sea este el objeto de datos buscado o la identificacion del problema que impidio hacernos con los datos.

Una manera efectiva de hacer esto es creando interfaces que las clases clientes deben implementar para recibir el resultado. Es decir, que crearemos interfaces que definiran metodos callback donde el objeto encargado de la conexion podra eventualmente volcar el resultado (Datos, Excepciones, etc.):


 public class Interfaces {  
   public abstract interface APIUserback {  
     public void apiDidFinish(Response response, User user);  
   }  
   public abstract interface APIContactsCallback {  
     public void apiDidFinish(Response response, Contacts contacts);  
   }  
 }  

   Segun vemos en el codigo los metodos a implementar en las clases clientes recibiran (en este caso) dos parametros, un objeto Response que contendra los detalles de la conexion y objetos de tipo User y de tipo Contacts que contendran los datos propios que se han ido a buscar o null en el caso de que haya habido problemas en la conexion.

Clase de conexion

Resulta util centralizar todas las conexiones al backend en una sola clase. Esta clase contendria metodos como los que siguen, que permiten conectar y obtener resultados de una manera generica. El siguiente metodo encapsula el resultado de la conexion en un objeto Response que permitira mantener informado al usuario o tomar opciones alternativas:

   private Response response(boolean success, String jsonResponse,  
                int statusCode, String error, String service, String location) {  
     Response response = new Response();  
     response.success = success;  
     response.statusCode = statusCode;  
     response.jsonResponse = jsonResponse;  
     response.errorCode = error;  
     response.location = location;  
     response.generateDisplayMessage(ctx);  
     response.logDetails();  
     return response;  
   }  
 }  

   El objeto Response podria ser algo parecido a:

 public class Response extends DataObject {  
      public boolean success;  
      public int statusCode;  
      public String errorCode;  
      public String displayMessage;  
      public String jsonResponse;  
      public String location;  
      public Response() {  
           super();  
      }  
      public void generateDisplayMessage(Context ctx) {  
           if(statusCode == 409){  
                displayMessage = ctx.getResources().getString(R.string.account_already_exists);  
           }  
      }  
 }  


Donde vemos que podemos agregar un metodo que genere un mensaje de alto nivel para mantener informado al cliente del problema que haya ocurrido.

Luego el metodo que finalmente hace la conexion podria verse como sigue:


      public Response execute(URL url, String method, String service,  
                DataObject dataObject, boolean getRedirectLocation,  
                boolean followRedirects) {  
           String responseString = "";  
           String responseMessage = "";  
           String location = "";  
           int statusCode = 0;  
           boolean success = false;  
           HttpEntity entity;  
           HttpResponse httpResp = null;  
           DefaultHttpClient httpClient;  
           HttpParams httpParameters = new BasicHttpParams();  
           HttpConnectionParams.setConnectionTimeout(httpParameters,  
                     Constants.TIMEOUT_CONNECTION);  
           HttpConnectionParams.setSoTimeout(httpParameters,  
                     Constants.TIMEOUT_CONNECTION);  
           httpClient = getNewHttpClient(url.getProtocol(), httpParameters);  
           if (!followRedirects) {  
                HttpParams params = httpClient.getParams();  
                HttpClientParams.setRedirecting(params, false);  
           }  
           try {  
                if (method.equals("GET")) {// GET  
                     HttpGet httpGet = new HttpGet(url.toURI());  
                     for (Map.Entry<String, String> entry : this.headers(service)  
                               .entrySet()) {  
                          httpGet.addHeader(entry.getKey(), entry.getValue());  
                     }  
                     httpResp = httpClient.execute(httpGet);  
                     entity = httpResp.getEntity();  
                     if (entity != null) {  
                          InputStream inputStream = entity.getContent();  
                          responseString = API.convertStreamToString(inputStream);  
                     }  
                     if (httpResp.getStatusLine().getStatusCode() < 400) {  
                          success = true;  
                     }  
                } else if ((method.equals("POST") && dataObject != null)) {// POST  
                     HttpPost httpPost = null;  
                     httpPost = new HttpPost(url.toURI());  
                     for (Map.Entry<String, String> entry : this.headers(service)  
                               .entrySet()) {  
                          RWLog.v("POST header---> " + entry.getKey() + ": "  
                                    + entry.getValue());  
                          httpPost.addHeader(entry.getKey(), entry.getValue());  
                     }  
                     entity = new StringEntity(dataObject.toJsonString(), "UTF-8");  
                     httpPost.setEntity(entity);  
                     httpResp = httpClient.execute(httpPost);  
                     InputStream inputStream = httpResp.getEntity().getContent();  
                     responseString = API.convertStreamToString(inputStream);  
                     if (httpResp.getStatusLine().getStatusCode() < 400) {  
                          success = true;  
                     }  
                } else if ((method.equals("DELETE"))) {  
                     HttpDelete httpDelete = new HttpDelete(url.toURI());  
                     for (Map.Entry<String, String> entry : this.headers(service)  
                               .entrySet()) {  
                          httpDelete.addHeader(entry.getKey(), entry.getValue());  
                     }  
                     httpResp = httpClient.execute(httpDelete);  
                     entity = httpResp.getEntity();  
                     if (entity != null) {  
                          InputStream inputStream = entity.getContent();  
                          responseString = API.convertStreamToString(inputStream);  
                     }  
                     if (httpResp.getStatusLine().getStatusCode() < 400) {  
                          success = true;  
                     }  
                }  
                responseMessage = httpResp.getStatusLine().getReasonPhrase();  
                statusCode = httpResp.getStatusLine().getStatusCode();  
                if (responseString.length() > 1) {  
                     JSONObject jsonError = new JSONObject(responseString);  
                     responseMessage = jsonError.getString("errorCode");  
                }  
                if (getRedirectLocation) {  
                     Header header = httpResp.getFirstHeader(("Location"));  
                     if (header != null) {  
                          location = header.getValue();  
                     }  
                }  
           } catch (NoHttpResponseException nhre) {  
                statusCode = OFFLINE_ERROR;  
                nhre.printStackTrace();  
           } catch (ConnectTimeoutException cte) {  
                statusCode = TIMEOUT_ERROR;  
                cte.printStackTrace();  
           } catch (IOException ioe) {  
                if (ioe.getMessage().equals("No authentication challenges found")) {  
                     statusCode = 401;  
                }  
                try {  
                     if (responseString.length() > 1) {  
                          // getErrorStream() does not exist in HttpClient, supposedly  
                          // the error stream will be be in the response body in case  
                          // of error, but this is something to check  
                          JSONObject jsonError = new JSONObject(responseString);  
                          responseMessage = jsonError.getString("errorCode");  
                     }  
                } catch (JSONException jse) {  
                     jse.printStackTrace();  
                }  
           } catch (JSONException jse) {  
                jse.printStackTrace();  
           } catch (URISyntaxException e) {  
                e.printStackTrace();  
           }  
           Response r = response(success, responseString, statusCode,  
                     responseMessage, service, location);  
           return r;  
      }  

Aqui se utiliza el framework de Apache HTTPClient, pero se puede implementar facilmente con HTTPUrlConnection si se lo desea.


Finalmente la llamada a este metodo podriamos colocarla en un metodo como el siguiente:

   public void contacts(final int pageSize, final int pageIndex) {  
     new Thread(new Runnable() {  
       @Override  
       public void run() {  
         String service = "contacts";  
         Map<String, String> parameters = new HashMap<String, String>();  
         parameters.put("pageSize", Integer.toString(pageSize));  
         parameters.put("pageIndex", Integer.toString(pageIndex));  
         Response response = execute(urlForService(service, parameters),  
             "GET", service, null, false, false);  
         try {  
           Contacts responseDataObject = new Contacts();  
           if (response.success) {  
             if (response.statusCode == 200) {  
               responseDataObject = new Gson().fromJson(  
                   response.jsonResponse, Contacts.class);  
             } else if (response.statusCode == 204) {  
               // just init to an empty list if empty  
               responseDataObject.results = new ArrayList<Contact>();  
             }  
           }  
           apiContactsCallback.apiDidFinish(response,  
               responseDataObject);  
         } catch (Exception e) {  
           e.printStackTrace();  
         }  
       }  
     }).start();  
   }  

Sintetizando, que podriamos crear una clase API que tuviera los metodos:

public void contacts(final int pageSize, final int pageIndex)


public Response execute(URL url, String method, String service,  
                DataObject dataObject, boolean getRedirectLocation,  
                boolean followRedirects)

y
private Response response(boolean success, String jsonResponse,  
                int statusCode, String error, String service, String location)


Y a la cual llamariamos desde nuestras Activity con:

     API api = new API(this);  
     api.setContactsCallback(new Interfaces.APIContactsCallback() {  
       @Override  
       public void apiDidFinish(final Response response, final Contacts contacts) {  
         contactsAdapter = new ContactsAdapter(MyContactsActivity.this, contacts);  
         runOnUiThread(new Runnable() {  
           @Override  
           public void run() {  
             if(response.statusCode == 403){  
                 ............. Y AQUI COMUNICARIAMOS AL USUARIO LO QUE TOQUE  


En nuestra clase API agregariamos entonces todos los metodos que requieran conectar con el backend y todas las clases clientes implementarian una interface de tipo callback que recibiria los resultados. De esta manera todo nuestro codigo de conexion con el backend quedaria integrado en una sola clase (API). Representando las Actividades con cajas, las clases con circulos y los metodos con puntos de enlace entre clases quedaria algo como:


Bueno, se ma hecho larga esta primer entrada. En la proxima si lo solicitan agrego un ejemplo en codigo para que se bajen. Saludos y espero vuestros comentarios para mejorar la entrada.


Soy desarrollador Android, profesional. A veces cuando me encuentro con algo que nunca hice antes, o cuando encuentro una mejor manera de hacer algo que ya hice antes, suelo reenviarme la solucion por correo con algunas pocas notas. Este blog es la version 2.1 de esa misma idea. Es tener un lugar donde dejarlas y donde ademas de a mi les pueda servir a otros.

Las notas son un mix entre cosas que descubri por mi mismo, cosas interesantes que encontre manteniendo codigo ajeno y cosas que saque del arbol de la ciencia del bien y del mal. Y no tienen una hilacion tematica entre ellas, mas que su pertenencia al mundo de la programacion Android. No forman parte de un curso ni tiene sentido leerlas en secuencia.
Pero espero que les sirvan, como me sirvieron a mi ;-)