Mostrando entradas con la etiqueta android. Mostrar todas las entradas
Mostrando entradas con la etiqueta android. Mostrar todas las entradas

jueves, 7 de julio de 2011

Parsear JSON utilizando Gson

Voy a explicar cómo parsear objetos JSON utilizando la librería open source Gson (http://code.google.com/p/google-gson/) utilizando como ejemplo los trending topics de Twitter.
La ventaja de utilizar Gson es que la generación de objetos Java es inmediata, no más toString() Basta definir el modelo que representará la respuesta JSON, pasárselo al parseador de Gson y él se encargará de hacer todo el trabajo. Esto también funciona en sentido inverso: serializar objetos nunca fue tan fácil.
La consulta de los trending topics mundiales de Twitter tiene esta pinta:
[
    {
        "created_at":"2011-07-06T15:10:14Z",
        "as_of":"2011-07-06T15:22:20Z",
        "locations":[
            {
                "name":"Worldwide",
                "woeid":1
            }
        ],
        "trends":[
            {
                "url":"http:\/\/search.twitter.com\/search?q=%23nationalkissingday",
                "events":null,
                "name":"#nationalkissingday",
                "promoted_content":null,
                "query":"%23nationalkissingday"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=%23goseeksomehelp",
                "events":null,
                "name":"#goseeksomehelp",
                "promoted_content":null,
                "query":"%23goseeksomehelp"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=%23elmejorinvento",
                "events":null,
                "name":"#elmejorinvento",
                "promoted_content":null,
                "query":"%23elmejorinvento"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=BTWBall",
                "events":null,
                "name":"BTWBall",
                "promoted_content":null,
                "query":"BTWBall"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=%22Brasil%203x2%20Cuba%22",
                "events":null,
                "name":"Brasil 3x2 Cuba",
                "promoted_content":null,
                "query":"%22Brasil%203x2%20Cuba%22"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=JoeJonasIsPERFECT",
                "events":null,
                "name":"JoeJonasIsPERFECT",
                "promoted_content":null,
                "query":"JoeJonasIsPERFECT"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=%22Abbey%20Dawn%22",
                "events":null,
                "name":"Abbey Dawn",
                "promoted_content":null,
                "query":"%22Abbey%20Dawn%22"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=%22Charlie%20Adam%22",
                "events":null,
                "name":"Charlie Adam",
                "promoted_content":null,
                "query":"%22Charlie%20Adam%22"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=%22Noel%20Gallagher%22",
                "events":null,
                "name":"Noel Gallagher",
                "promoted_content":null,
                "query":"%22Noel%20Gallagher%22"
            },
            {
                "url":"http:\/\/search.twitter.com\/search?q=Boonen",
                "events":null,
                "name":"Boonen",
                "promoted_content":null,
                "query":"Boonen"
            }
        ]
    }
]
Como se puede ver lo que estamos recibiendo es: - Array de un solo elemento con: - Atributos informando de la fecha de generación. - Array de localizaciones con un solo elemento. (no lo utilizaremos) - Array de 'trends' con 10 elementos.
A su vez, cada trend tiene varios atributos. Para este ejemplo nos quedaremos solo con URL y Name.
Lo siguiente es crear el modelo que representará esta estructura de datos. Para ello se necesitan dos clases muy simples. La primera es TwitterTrends que modela un elemento tipo 'trend'
package es.lgvalle.trendsparser;
import com.google.gson.annotations.SerializedName;

public class TwitterTrend {
 @SerializedName("name")
 private String name;

 @SerializedName("url")
 private String url;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public String getUrl() {
  return url;
 }

 public void setUrl(String url) {
  this.url = url;
 }
}
La segunda es TwitterTrends que contiene una lista de TwitterTrend junto con la fecha de creación
package es.lgvalle.trendsparser;
import java.util.List;
import com.google.gson.annotations.SerializedName;

public class TwitterTrends {

 @SerializedName("as_of")
    private String as_of;
 
 @SerializedName("created_at")
    private String created_at;
 
 @SerializedName("trends")
    private List trends;
 
    public String getCreated_at() {
  return created_at;
 }
 public void setCreated_at(String created_at) {
  this.created_at = created_at;
 }
 public String getAs_of() {
        return as_of;
    }
    public void setAs_of(String asOf) {
        as_of = asOf;
    }
    public List getTrends() {
        return trends;
    }
    public void setTrends(List trends) {
        this.trends = trends;
    }
}
En este ejemplo voy a generar un objeto TwitterTrends que mostrará sus 'trends' en un FlipperView. Al hacer click sobre un elemento navegaremos a su url asociada.
El código que hace esto quedaría así:
Type objType = new TypeToken>() {}.getType();
ArrayList trendsList = (ArrayList) JSONClient.getJson(TRENDS_URL, objType);

// El api de Twitter devuelve un array con un único elemento.
TwitterTrends objs = (TwitterTrends) trendsList.get(0);

for (final TwitterTrend tr : objs.getTrends()) {
 TextView trend = (TextView) getLayoutInflater().inflate(R.layout.trend, null);
 trend.setText(tr.getName());
 trend.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
   startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(tr.getUrl())));
   }
 });
 mFlipper.addView(trend);
}
mFlipper.startFlipping();
Como se puede ver en primer lugar hay que crear un TypeToken especificando el tipo de objeto que modela el JSON que vamos a recibir.
Este TypeToken es propio de la librería Gson y sirve para lidiar con la pérdida de tipo al crear objetos genéricos en Java. Básicamente el problema es que en Java, cuando creamos un List y pedimos el tipo obtenemos 'List', la información sobre String se pierde. Para más información: http://download.oracle.com/javase/tutorial/java/generics/erasure.html
El Type Erasure es útil para muchas cosas pero un gran problema para nuestro ejemplo. A Gson necesitamos especificarle exactamente el objeto que vamos a construir y ahí es donde entra TypeToken.
Finalmente la clase JSONClient.java quedaría así:
package es.lgvalle.trendsparser;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Type;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import com.google.gson.Gson;

public class JSONClient {
 public static Object getJson(String url, Type objType) {
  Gson gson = new Gson();
  Reader r = new InputStreamReader(getStream(url));
  Object fromJson = gson.fromJson(r, objType);
  return fromJson;
 }

 private static InputStream getStream(String url) {
  HttpGet request = new HttpGet(url);
  InputStream is = null;
  try {
   HttpResponse response = new DefaultHttpClient().execute(request);
   final int statusCode = response.getStatusLine().getStatusCode();
   if (statusCode != HttpStatus.SC_OK) {
    Log.v("Error " + statusCode + " for URL " + url);
   } else {
    is = response.getEntity().getContent();
   }
   
  } catch (IOException e) {
   request.abort();
   Log.v("Error for URL " + url);
  }
  Log.v("Got stream from "+url);
  return is;
 }
}
Done.
El código completo de este ejemplo está disponible aquí.

jueves, 9 de junio de 2011

Custom scrollview con scroll lock dinámico

Con esta sencilla clase podemos tener un scrollview que deshabilite el scroll dinámicamente en función de un flag

public class MenuScrollView extends ScrollView {


/**

* Flag para controlar el bloqueo del scroll

*/

boolean lock = false;


public boolean isLocked() {

return lock;

}


public void setLock(boolean lock) {

this.lock = lock;

}


public MenuScrollView(Context context, AttributeSet attrs, int i) {

super(context, attrs, i);

this.setVerticalScrollBarEnabled(false);

this.setHorizontalScrollBarEnabled(false);

}


public MenuScrollView(Context context, AttributeSet attrs) {

super(context, attrs);

this.setVerticalScrollBarEnabled(false);

this.setHorizontalScrollBarEnabled(false);

}


public MenuScrollView(Context context) {

super(context);

this.setVerticalScrollBarEnabled(false);

this.setHorizontalScrollBarEnabled(false);

}


/**

* Return true -> no scroll

*/

public boolean onTouchEvent(MotionEvent ev) {

if (lock) {

return true;

} else {

return super.onTouchEvent(ev);

}


}


/**

* Return false -> no scroll

*/

public boolean onInterceptTouchEvent(MotionEvent ev) {

if (lock) {

return false;

} else {

return super.onInterceptTouchEvent(ev);

}

}

}



martes, 15 de febrero de 2011

Android: write EASY on internal storage

Recently I found that not all sdcards filesystem root paths starts with /sdcard. So I was getting an Exception trying to write files in a location that doesn't exist. Epic fail in my code.

Filesystem root path independent code

String content = "Hola mundo";       
FileOutputStream fos = openFileOutput(FILE_NAME, Context.MODE_PRIVATE);
fos.write(content.getBytes());
fos.close();

This will create a file on your application private space, so no other applications can access to it.

Great thing is that you don't need to know anything about paths, permissions, inputstreams, ... Just let Android handle it for you.

viernes, 11 de febrero de 2011

Android: my custom log class

I use a custom log class on all my Android projects to easy my life.

Advantages
  • Possibility to enable/disable log by configuration. Useful to keep your releases clean.
  • Class call trace. Know which class is displaying which log message with no extra text.

How it's done?

Really easy. Just create a new class:
class Log {
public final static String LOGTAG = "Phone2Chrome";
static final boolean LOGV = true;

static void v(String logMe) {
 if (LOGV) {
  try {
   throw new Exception("go go logger");
  } catch (Exception e) {
   String className = e.getStackTrace()[1].getClassName();
   className = className.substring(className.lastIndexOf(".")+1, className.length());
   android.util.Log.v(LOGTAG, className + "." + e.getStackTrace()[1].getMethodName() + "() --- " + logMe);
  }
 }
}

Import it in your Android classes and make this replacement:
void myFunction() {
- Log.d(TAG, "MyActivity - myFunction: hello logcat");
+ Log.v("hello logcat");
}

What you are going to see in logcat is something like this:

V/Phone2Chrome(16119): MyActivity.myFuntion() --- hello logcat.

No more dirty log messages anymore!

domingo, 5 de diciembre de 2010

Align bottom in LinearLayout

A quick trick to align bottom anything in your android linear layout:

  • Create a linear layout vertical view
  • Add this style to your styles.xml file (layout_weight is the key here):

<style> name="Blank">

<item name="android:layout_width">fill_parent item>

<item name="android:layout_weight">1 item>

<item name="android:layout_height">0sp item>

<item name="android:background">@null item>

style>

  • Finally add this generic view to your linear layout just above the element or elements you want to align bottom:

<View android:id="@+id/blankline1"

style="@style/Blank" />