Archivo de la categoría: Android

Limitar posibles valores en una función a través de @Anotaciones.

Limitar los posibles valores que puede recibir una función a través de @Anotaciones, nos ayudará a determinar valores en constantes sin la necesidad de utilizar valores de tipo ENUM y prevenir posibles errores (un valor que no corresponda a la función) en tiempo de compilación.

Agregamos la dependencia a nuestro proyecto

compile 'com.android.support:support-annotations:24.2.1'

Definimos nuestros posibles valores en constantes

public static final int YES = 1;
public static final int NO = -1;

Definimos nuestra @anotación con las constantes antes mencionadas

@IntDef({YES, NO})
@Retention(RetentionPolicy.SOURCE)
public @interface OptionValues {}

Podemos especificar un valor por defecto

@OptionValues int currentOption = NO;

Creamos nuestro accesador y mutador

public void setCurrentOption(@OptionValues int currentOption) {
    this.currentOption = currentOption;
}
@OptionValues public int getCurrentOption() {
    return currentOption;
}

Y así ya podemos asignar un valor

setCurrentOption(YES);

Aquí todo el código del ejemplo

public class MainActivity extends Activity {
	public static final int YES = 1;
    public static final int NO = -1;

	@IntDef({YES, NO})
    @Retention(RetentionPolicy.SOURCE)
    public @interface OptionValues {}

	@OptionValues int currentOption = NO;

	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setCurrentOption(YES);
	}

	public void setCurrentOption(@OptionValues int currentOption) {
        this.currentOption = currentOption;
    }
 
    @OptionValues
    public int getCurrentOption() {
        return currentOption;
    }
}

Vista para nuestras listas vacías en Android

Durante mi vida como desarrollador Android, he visto las formas más ingeniosas de mostrar vistas cuando una lista (ListView o RecyclerView) están vacías; desde frames ocultos en el layout, listas que empujan otros view con match_parent, entre otros.
Por suerte para estos males y otros, desde la versión del API 1, existe la función setEmptyView que recibe como parámetro un objeto View, que será mostrada cuando la el listView no contenga elementos.

View emptyView = getLayoutInflater().inflate(R.layout.empty_view, null);
ListView listView = (ListView)findViewById(R.id.listView);
listView.setEmptyView(emptyView);

Ahora respecto a los RecyclerView, en la documentación oficial no encontré una forma tan simple para poder resolverlo, por lo que hay que volver a las viejas andanzas y utilizar los famosos setVisibility en un frame que cubra toda la pantalla.
FrameLayout emptyView = (FrameLayout)findViewById(R.layout.empty_view);
MyRecyclerAdapter listAdapter = new MyRecyclerAdapter(listItems);
listAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
  @Override
  public void onChanged() {
    super.onChanged();
    if (listAdapter.getItemCount() == 0) {
      emptyView.setVisibility(View.VISIBLE);
    } else {
      emptyView.setVisibility(View.GONE);
    }
  }
});

Si conocen una forma mejor de poder mostrar una vista en un RecyclerView vacío, por favor compártanla para mejorar el tip.
Gracias!

Verificar si tenemos conexión a Internet en Android

Para verificar si estamos conectados a Internet, lo primero que tenemos que hacer es agregar el permiso ACCESS_NETWORK_STATE a nuestro archivo AndroidManifest.xml para así consultar el estado de nuestra conexión.

<uses-permission 
	android:name="android.permission.ACCESS_NETWORK_STATE" />

Luego creamos una función que a través del contexto en el cuál estemos trabajando verifique en el servicio CONNECTIVITY_SERVICE si estamos conectados o conectándonos. Esta función es estática para que pueda ser accesada desde cualquier parte sin necesidad te pertenecer a una instancia y debería estar ubicada en una clase Singleton o similar.

static public boolean isNetworkingAvailable(Context c){
	ConnectivityManager cm = 
		(ConnectivityManager)c.getSystemService(Context.CONNECTIVITY_SERVICE);

	NetworkInfo activeNetwork = cm.getActiveNetworkInfo();

	return activeNetwork != null && 
		activeNetwork.isConnectedOrConnecting();
}

Eventualmente si estamos conectados a una red que no tiene salida a Internet, podemos capturar el evento al momento de realizar algún request. Pero siempre deberíamos consultar primero con este servicio.

EditText con sugerencias de autocompletado en Android

AutoCompleteTextView

En este post crearemos un elemento EditText, que al momento que vayamos escribiendo palabras sobre este, nos vaya sugiriendo coincidencias obtenidas de un arreglo de palabras definidos por nosotros mismos anteriormente, como pueden ver a continuación.
Lo primero que tenemos que hacer es agregar el elemento EditText en la vista de nuestra Activity, que en este caso se llama AutoCompleteTextView.

<AutoCompleteTextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/autocomplete_region" android:layout_width="fill_parent" android:layout_height="wrap_content" />

Luego debemos crear la lista de las sugerencias que ofreceremos al usuario, esto debemos hacerlo en el archivo res/values/strings.xml. Recuerden que las etiquetas <resources></resources> son la raíz del archivo, por lo que si tienen más strings definidos deben ir también dentro de estas etiquetas.

<resources>
  <string-array name="region_array">
    <item>Región Metropolitana</item>
    <item>Región de Aríca y Parinacota</item>
    <item>Región de Tarapacá</item>
    <item>Región de Antofagasta</item>
    <item>Región de Atacama</item>
    <item>Región de Coquimbo</item>
    <item>Región de Valparaíso</item>
    <item>Región de O\'Higgins</item>
    <item>Región de Maule</item>
    <item>Región del Biobío</item>
    <item>Región de la Araucanía</item>
    <item>Región de los Ríos</item>
    <item>Región de los Lagos</item>
    <item>Región de Aysén</item>
    <item>Región de Magallanes y Antártica</item>
  </string-array>
</resources>

A continuación, dentro de la función onCreate, partimos referenciando el elemento AutoCompleteTextView que creamos anteriormente en la vista. Luego obtenemos las regiones que creamos en el archivos de string y creamos un adaptador ArrayAdapter que viene por defecto en Android. Cabe señalar que el elemento android.R.layout.simple_list_item_1 es también una vista que viene con el mismo android. Nosotros también podríamos crear nuestro propio adaptador extendiendo de ArrayAdapter (o de cualquier similar) y crear nuestra propia vista para los elementos que se desplieguen. Con lo que finalmente le pasamos el adaptador al elemento en la vista.

// Referencia al elemento en la vista
AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.autocomplete_region);

// Arreglo con las regiones
String[] regions = getResources().getStringArray(R.array.region_array);

// Le pasamos las regiones al adaptador
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, regions);

// finalmente le asignamos el adaptador a nuestro elemento
textView.setAdapter(adapter);

Recuerden que pueden consultar la documentación completa de AutoCompleteTextView ya que dispone de muchas funcionalidades para que podemos utilizar.

Espero que les haya servido y saludos!.

Cómo referenciar nuestras vistas y recursos con Butter Knife en Android

buttlerknife

Butter Knife es una excelente librería que nos ayudará a ahorrar código al momento de referenciar elementos de nuestras vistas, recursos del proyecto o hasta referencias eventos que interactúan con el usuario, utilizando connotaciones para cada una de estas.
Para comenzar, compilamos la librería en nuestro archivo gradle con las siguientes dependencias

compile 'com.jakewharton:butterknife:8.0.1'

Normalmente referenciamos elementos de nuestras vistas de la siguiente forma

class ExampleActivity extends Activity {
  TextView textViewName;
  TextView textViewCity;

  @Override 
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    textViewName = (TextView)findViewById(R.id.textViewName);
    textViewCity = (TextView)findViewById(R.id.textViewCity);
  }
}

Con la librería Butter Knife podríamos hacerlo así

class ExampleActivity extends Activity {
  @BindView(R.id.textViewName) TextView title;
  @BindView(R.id.textViewCity) TextView subtitle;

  @Override 
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
  }
}

Si estamos dentro de un fragment pasamos la vista a la instancia de ButterKnife

View view = inflater.inflate(R.layout.fragment, container, false);
ButterKnife.bind(this, view);

Es importante liberar la memoria utilizada, una vez que destruyamos el fragment.

// ...
private Unbinder unbinder;
// ...

@Override 
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  View view = inflater.inflate(R.layout.fragment, container, false);
  unbinder = ButterKnife.bind(this, view);
  return view;
}
@Override 
public void onDestroyView() {
  super.onDestroyView();
  unbinder.unbind();
}

Y a su vez también disponemos de otras connotaciones para para referenciar los recursos de nuestro proyecto, utilizando @BindBool, @BindColor, @BindDimen, @BindDrawable, @BindInt, @BindString

@BindString(R.string.title) String title;
@BindDrawable(R.drawable.graphic) Drawable graphic;
@BindColor(R.color.red) int red;
@BindDimen(R.dimen.left_margin) Float left_margin;

Podemos asignar eventos a los elementos de la vista

@OnClick(R.id.buttonSubmit) void submit() {
  // Aquí nuestro código...
}

Pasar la vista como parámetro

@OnClick(R.id.buttonSubmit) void submit(View view) {
  // Aquí nuestro código...
}

O pasar la posición si corresponde

@OnItemClick(R.id.list_of_things) void onItemClick(int position) {
  // Aquí nuestro código...
}

Tenemos disponible los siguientes eventos para asignar: @OnLongClick, @OnPageChange, @OnTextChanged, @OnTouch, @OnItemLongClick y @OnCheckedChanged. Incluso asignar el evento a varios elementos de una sola vez

@OnClick(R.id.buttonOne, R.id.buttonTwo, R.id.buttonThree)
public void submit(View view) {
  // Aquí nuestro código...
}

Podemos hacer listas de vistas y aplicar propiedades

@BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name })
List<EditText> nameViews;

ButterKnife.apply(nameViews, View.ALPHA, 0.0f);
ButterKnife.apply(nameViews, View.VISIBLE, false);
ButterKnife.apply(nameViews, DISABLE, false);

Finalmente podemos especificar que un elemento de la vista puede ser nulo al momento de referenciarlo o opcional al momento de asignar un evento.

@Nullable @BindView(R.id.might_not_be_there) TextView mightNotBeThere;

@Optional @OnClick(R.id.maybe_missing) void onMaybeMissingClicked() {
  // Nuestro código...
}

Espero que el post haya sido de ayuda y no duden en comentar sus dudas o sugerencias. Saludos!

Conectarse a socket.io de NodeJS con Android

logo android

Lo primero es agregar la dependencia de la librería a nuestro archivo grade en nuestro proyecto en Android Studio.
compile 'com.github.nkzawa:socket.io-client:0.4.0'

Así en la actividad que queramos utilizar el socket creamos el objeto Socket.
Socket socket;

y algunas constantes que utilizaremos en nuestro código.

final static String URL_SOCKET = "http://localhost";
final static String MY_EVENT = "my event";
final static String PARAM_NAME = "name";
final static String PARAM_IMAGE = "image";

Instanciamos nuestra conexión al servidor pasando como parámetro la url de este mismo.

try{
  /* Instance object socket */
  socket = IO.socket(URL_SOCKET);
}catch (URISyntaxException e){
  e.printStackTrace();
}

Podemos instanciar el objecto con opciones predeterminadas para que se reconecte el socket.

IO.Options opts = new IO.Options();
opts.forceNew = true;
opts.reconnection = false;
try{
  /* Instance object socket */
  socket = IO.socket(URL_SOCKET, opts);
}catch (URISyntaxException e){
  e.printStackTrace();
}

O para utilizar conexiones SSL por defecto o como parámetro al instanciar el objeto

/* Default SSLContext for all sockets */
IO.setDefaultSSLContext(mySSLContext);

/* Set as an option */
opts = new IO.Options();
opts.sslContext = mySSLContext;
try{
  /* Instance object socket */
  socket = IO.socket(URL_SOCKET, opts);
}catch (URISyntaxException e){
  e.printStackTrace();
}

Luego creamos los eventos principales de nuestro socket: EVENT_CONNECT y EVENT_DISCONNECT, estos son los que recibirán las emisiones desde el servidor.

socket.on(Socket.EVENT_CONNECT, new Emitter.Listener(){
  @Override
  public void call(Object... args) {
    /* Our code */
  }
}).on(Socket.EVENT_DISCONNECT, new Emitter.Listener(){
  @Override
  public void call(Object... args) {
    /* Our code */      
  }
});

Y crear nuestros propios eventos listener

socket.on(MY_EVENT, new Emitter.Listener(){
  @Override
  public void call(Object... args) {
    /* Our code */      
  }
});

Con esto ya podemos abrir la conexión
socket.connect();

Además disponemos de otras funciones como para verificar el estado de la conexión.
socket.connected()

Para desconectar nuestro socket.
socket.disconnect()

Si lo que queremos es emitir eventos por nuestro socket utilizamos, enviando cadenas de texto o también datos binarios.

JSONObject obj = new JSONObject();
obj.put(PARAM_NAME, "Pablo");
obj.put(PARAM_IMAGE, new byte[42]);

/* Emit event */
socket.emit(MY_EVENT, obj)

También podemos capturar el callback que retorna el servidor

socket.emit(MY_EVENT, obj, new Ack() {
  @Override
  public void call(Object... args) {
    /* Our code */
  }
});

Y eso es todo, Saludos!

Cambiar el tema de un fragment en Android

logo androidEstoy programando mucho en Android, pero no he subido nada al respecto, por lo que iré compartiendo pequeños códigos para hacer cosas especificas, que también claro, servirán para anotaciones personales mias.
Los fragment estan inmersos en las actividades que los contienen, por lo que no podemos cambiarlos directamente en el archivo AndroidManifest.xml, así que heredan los estilos que las actividades tengan definidas, y si estas últimas no tienen ningún tema definido heredan el que tenga la aplicación. Pero podemos cambiar esto utilizando las siguientes 3 lineas dentro de la función onCreateView de nuestro fragment.
Primero traemos el tema que queremos aplicar al fragment creando un objeto de contexto. Deben reemplazar MyAppTheme por su tema.

final Context themeWrapper = 
	new ContextThemeWrapper(getActivity(), R.style.MyAppTheme);

Luego definimos un LayoutInflater al cuál le aplicamos nuestro contexto anterior.
LayoutInflater cloneInflate = 
	inflater.cloneInContext(themeWrapper);

Y finalmente inflamos el layout de nuestro fragment utilizando el layout inflate definido anteriormente. Recuerden que deben reemplazar fragment_layout por el que corresponda con su fragment, y que será el que finalmente retornaremos en la función.
View view = 
	cloneInflate.inflate(R.layout.fragment_layout, container, false);

Eso es todo! Saludos!

Ya está disponible la versión beta de Jaampr

jaampr_logo

Jaampr es un proyecto de red social para Android (por ahora) en el cual llevo un tiempo trabajando. Consiste en una red social de actividades georeferenciada donde las personas pueden compartir lo que hacen y otras pueden unirse o interactuar con ellas. Ahora está en un proceso de beta así que sería de gran ayuda si mandan su feedback o reportar errores. Gracias!

Google Cloud Messaging con NodeJS

google-cloud-messaging

Google Cloud Messaging una de las funcionalidades más lindas que he utilizando en Android, sin tener la necesidad de tener que conectarnos cada vez al servidor, podemos mandar información a través de push a los dispositivos que tengan instalada nuestra aplicación para luego ser tratada, perfecto para notificaciones, ya que ocurre al instante. Ese post no está dedicado a explicar como funciona gcm, si lo desean pueden consultar la documentación oficial de Google para más detalles Google Developer
Entonces lo primero es crear nuestra librería con la cuál nos comunicamos en los servidores de Google, la llamaremos gcm.js

[javascript]
var request = require(‘request’);
var GCM = function(api_key) {
this._api_key = api_key;
}
GCM.prototype.send = function(msg, callback) {
request.post({
uri: ‘https://android.googleapis.com/gcm/send’,
json: msg,
headers: {
Authorization: ‘key=’ + this._api_key
}
}, function(err, response, body) {
callback(err, body);
})
}
module.exports = GCM;
[/javascript]

Recuerden que para como estamos importando ‘request’ debemos instalarla en nuestro proyecto con:

sudo npm install request

Paso seguido, creamos la función que determinará a que usuario notificaremos. Importamos el gcm.js que acabamos de crear y reemplazamos las XxxXXXxXxxXxXxXxXXxXxXxXxXxXxXxXxXx por nuestro api key (recuerden que deben dar los respectivos permisos en Google Console), para este ejemplo estoy creando un modulo llamado ‘notify’ que, apoyandome con ExpressJS, recibe a través de una consulta POST el parámetro user_id, luego en un schema llamado ‘User’ busco en mi base de datos un usuario con ese id y obtengo su gcm_id, que había guardado previamente desde el dispositivo del usuario, para poder rutear el mensaje que deseo enviar.

[javascript]
var GCM = require(‘./gcm.js’);
var gcm = new GCM(‘XxxXXXxXxxXxXxXxXXxXxXxXxXxXxXxXxXx’);

exports.notify = function(req, res){
var user_id = req.body.user_id;
User.findOne({_id: user_id}, function(err, user){
if(!err && user){
var msg = {
registration_ids: [user.gcm_id],
collapse_key: "your_collapse_key",
time_to_live: 180,
data: {
message: ‘Este es mi mensaje!’
}
};
gcm.send(msg, function(err, response){
console.log(response);
res.send(response.success);
});
}else{
res.send(‘false’);
}
});
}
[/javascript]

Y listo! Una implementación bastante sencilla de Google Cloud Message en NodeJS.
Saludos!

Genymotion con Google Apps

genymotion

Hace un tiempo que estoy utilizando la versión community (free) de Genymotion y una de las limitantes con las que me encontré es que no pude usar los servicios de Google, como los mapas, mensajería gcm, etc. He aquí la solución!

Primero bajamos este archivo:
ARM Translation Installer v1.1 (Mirror)
y sin descomprimirlo lo arrastramos sobre nuestra ventana de VM de Genymotion iniciada (si arrastrar sobre la ventana!).

Luego bajando la Google Apps según la versión de Android que estemos utilizando
Google Apps for Android 4.4 (Mirror)(Download from CM11 Links)
Google Apps for Android 4.3 (Mirrors)
Google Apps for Android 4.2
Google Apps for Android 4.1
y también arrastramos sobre la ventana de VM Genymotion iniciada.
Ahora reinicias y ya podrás acceder a Google Play y a sus servicios!
Saludos!