Menu Deslizante como o do app Facebook

Uma boa dica para seu aplicativo é usar um menu como o do Facebook, como mostra a imagem abaixo:

Para isso, a dica vai para usar uma biblioteca chamada SlidingMenu. Com ela é possível criar facilmente esse tipo de menu slider que desliza da esquerda para a direita. Para ver uma demo, os colaboradores disponibilizaram um app no Google Play, que pode ser conferido clicando aqui.

Usando GCM para Registro e Notificação

Se seu aplicativo vai receber notificaçoes, é inevitável que você use GCM. Há duas maneiras de fazer:
- Usando um GCM BroadCastReceiver e implementar o pedido de registro, como também um método chamado onReceive.
- Usando a biblioteca gcm.jar (localizada na pasta android-sdk/extras/google/gcm/gcm-client/dist ) e fazer seu próprio GCM Service para gerenciar tudo.

Pela experiência que tive, a primeira opçao foi a mais simples, mas em alguns celulares nao funcionavam (os Samsung costumam dar trabalho nisso). Por isso, vou ensinar como fazer com o segundo exemplo.

O primeiro passo é registrar seu aplicativo na página da Google API ( https://code.google.com/apis/console/?pli=1 ). Ao clicar em Create Project, você entrará em uma página do tipo: https://code.google.com/apis/console/#project:4815162342 ( nesse caso, 4815162342 seria seu SENDER_ID. Guarde esse valor que será importante!!).

Logo você terá que editar seu AndroidManifest.xml com:

<uses-permission android:name=”android.permission.INTERNET” />
<uses-permission android:name=”android.permission.GET_ACCOUNTS” />
<uses-permission android:name=”android.permission.WAKE_LOCK” />
<uses-permission android:name=”com.google.android.c2dm.permission.RECEIVE” />

<permission
android:name=”com.unkasoft.lowcost.permission.C2D_MESSAGE”
android:protectionLevel=”signature” />

<uses-permission android:name=”com.unkasoft.lowcost.permission.C2D_MESSAGE” />
<uses-permission android:name=”android.permission.VIBRATE” />

<!– GCM –>
<receiver android:name=”com.google.android.gcm.GCMBroadcastReceiver” android:permission=”com.google.android.c2dm.permission.SEND”>
<intent-filter>
<action android:name=”com.google.android.c2dm.intent.RECEIVE” />
<action android:name=”com.google.android.c2dm.intent.REGISTRATION” />
//Esse pacote é o mesmo que está definido em <manifest package=””>
<category android:name=”<b>com.pacote.manifest.aqui</b>” />
</intent-filter>
</receiver>
<service android:name=”.GCMIntentService” android:enabled=”true”/>

Essa classe GCMIntentService é a classe responsável por gerenciar as notificaçoes recebidas. Em nosso caso, só há dois tipos de notificaçao:
- <action android:name=”com.google.android.c2dm.intent.REGISTRATION” /> que é quando o usuário se registra e logo temos uma ID do dispositivo.
- <action android:name=”com.google.android.c2dm.intent.RECEIVE” /> que é quando se recebe uma notificaçao.

Ou seja, na classe GCMIntentService é necessário que você trate os dois tipos de notificaçao. É importante colocar a classe GCMIntentService no pacote especificado (no nosso caso, com.pacote.manifest.aqui ) ou senao você terá um erro de Classe nao encontrada. Segue um exemplo da classe GCMIntentService:

public class GCMIntentService extends GCMBaseIntentService {
public static final int NOTIFICATION_ID = 1;
// Identificador para GCM
public static final String SENDER_ID = “4815162342″; //No nosso caso aqui é o número que obtivemos no primeiro passo

public GCMIntentService() {
super(SENDER_ID);
}

// Funçao chamada quando é registrado (Caso REGISTRATION)
protected void onRegistered(Context context, String registrationId) {

// Salvando o identificador do celular (usando a classe do SharedPreferences do tutorial passado)
DataHelper.registrationCGMId = registrationId;
DataHelper.saveData();

}

//Funçao chamada quando o dispositivo é desregistrado
protected void onUnregistered(Context context, String registrationId) {

// Eliminando o identificador do celular (usando a classe do SharedPreferences do tutorial passado)
DataHelper.registrationCGMId = null;
DataHelper.saveData();

}

//Funçao chamada ( caso RECEIVE)
protected void onMessage(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
String mensaje = bundle.getString(“alert”);
sendNotification(mensaje, context);
}

// metodo para gerar uma notificaçao em segundo plano
private void sendNotification(String msg, Context context) {
// Criaçao do padrao de vibraçao
long[] pattern = new long[] { 100, 500, 1000 };
// Construçao da Notificaçao
NotificationManager mNotificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(context, MapActivity.class);
intent.putExtra(“winner”, true);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
intent, 0);
//Aqui se cria a notificaçao com um icone do nosso projeto, com o titulo de nome da aplicaçao, com um estilo já definido por Android e o texto que é recibido por parametro.
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
context)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle(
context.getResources().getString(R.string.app_name))
.setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
.setContentText(msg);
// activamos sonido de la notificacion
Uri defaultSound = RingtoneManager
.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
mBuilder.setSound(defaultSound);
// activamos vibracion
mBuilder.setVibrate(pattern);
// activamos color Led
mBuilder.setLights(Color.GREEN, 1, 0);
// activamos cancelacion de notificacion tras click sobre ella
mBuilder.setAutoCancel(true);
// contruimos la notificacion
mBuilder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
}

// Caso se apague uma mensagem
protected void onDeletedMessages(Context context, int total) {
}

// Cuando há um erro, igonramos
public void onError(Context context, String errorId) {

if (errorId.equalsIgnoreCase(“ACCOUNT_MISSING”)
|| errorId.equalsIgnoreCase(“AUTHENTICATION_FAILED”)
|| errorId.equalsIgnoreCase(“PHONE_REGISTRATION_ERROR”)) {

Log.e(“”, “Error sem Conta no Google: ” + errorId);
}
}

protected boolean onRecoverableError(Context context, String errorId) {
return super.onRecoverableError(context, errorId);
}
}

Logo, para registrar o aplicativo e conseguir a GCMID você usa este código:

private void registerGCM() {
// Make sure the device has the proper dependencies.
GCMRegistrar.checkDevice(getApplicationContext());

// Make sure the manifest was properly set – comment out this line
// while developing the app, then uncomment it when it’s ready.
GCMRegistrar.checkManifest(getApplicationContext());
GCMRegistrar.register(getApplicationContext(), GCMIntentService.SENDER_ID);
if (GCMRegistrar.isRegistered(getApplicationContext())) {
//Salvo os dados em minha classe
DataHelper.registrationCGMId = GCMRegistrar.getRegistrationId(getApplicationContext());
DataHelper.saveData();
}else{
GCMRegistrar.register(getApplicationContext(), GCMIntentService.SENDER_ID);
}
}

A grande dica é de chamar esse método na Activity que você quer que use o GCM e, logo depois, caso tenha algum erro, lança o dialog de erro (como verifique sua Conexao com Internet) e logo depois volte a chamar o método sempre depois de erro de quando nao se pode conseguir o valor.

Possíveis Erros:
- Conta do Google nao cadastrada (para conferir isso é necessário que o usuário vá em Google Play e logo ele tem que ver se a tela nao é a inicial, se já colocou seu email e dados ali).
- Hora e Data nao sincronizadas (isso também se pode ver no Google Play, quando entra por lá ele vai acusar que nao há conexao com Internet).
- Sem conexao com Internet (nao é possivel receber notificaçao sem internet).

O grande problema de detectar erros é que a documentaçao nao diz nada e o erro chama-se simplesmente de SERVICE_NOT_AVAILABLE

Dica Útil
Poucos sabem, mas sem algum motivo, o id do dispositivo que esse código consegue da Google muda periodicamente. Isso é complicado, pois o problema é que quando o servidor for enviar uma notificaçao, é possível que o dispositivo já tenha mudado de Id. Ou seja, o ideal é que sempre seja solicitado o Id e atualizado no servidor.

Pintar Botão e Evitar que 2 sejam acionados ao mesmo tempo

Para pintar botoes é necessário um OnTouchListener. Tenho um objeto que pinta de cinza quando aperta um botao e ela é muito útil porque se quero mudar alguma coisa em todos os botoes no momento do toque, eu posso mexer nela por aí. Eu ja tinha esse objeto associado com todos os botoes quando foi preciso resolver o problema de apertar dois botoes ao mesmo tempo só tive que mudar nele. A grande jogada é usar um objeto para sincronizar. Tem gente que usa um booleano para fazer esse controle, mas isso nao é considerado uma boa prática. Segue o Código:

//Objeto para ser sincronizado e fazer o controle
public static final Object lock = new Object();
//Tempo para controlar qual botao vou levar em consideraçao no caso de haver dois botoes
private static long mLastClickTime = 0;
public static OnTouchListener buttonTinter = new OnTouchListener() {

@Override
public boolean onTouch(View v, MotionEvent me) {
synchronized (lock) {
// Pinta no momento que aperto o botao
if (me.getAction() == MotionEvent.ACTION_DOWN) {
Drawable d = v.getBackground();
if (d != null) {
d.setColorFilter(Color.argb(255, 155, 155, 155),
PorterDuff.Mode.MULTIPLY);
}
return false;
//”Despinto” no momento que solto o botao
} else if (me.getAction() == MotionEvent.ACTION_UP
|| me.getAction() == MotionEvent.ACTION_CANCEL) {
Drawable d = v.getBackground();
if (d != null) {
d.setColorFilter(Color.argb(255, 255, 255, 255),
PorterDuff.Mode.MULTIPLY);
}
//Vejo se vou levar em consideraçao ou nao o click. Tempo de 300milissegundos é um tempo encontrado na base de teste para lidar com a solta do click
if (SystemClock.elapsedRealtime() – mLastClickTime < 300) {
return true;
}
mLastClickTime = SystemClock.elapsedRealtime();
return false;
}
return true;
}
}
};

Ao retornar true em um onTouch quer dizer que já foi tratado o toque perfeitamente e nao precisa passar pelo onClick. Ao retornar false, o botao ainda vai passar pelo onClick que faz o que realmente tem que fazer. Ou seja, esse objeto buttonTinter pinta e também já trata o fato de ter dois botoes apertados ao mesmo tempo.

Ter o Contexto de onde esteja

A grande dica de pegar o contexto da Aplicação para fazer o que quiser (exceto para Dialogs) é criar sua própria classe filha de Application, como a seguinte:

public class MyApp extends Application {
private static MyApp applicationInstance;

public void onCreate() {
super.onCreate();
MyApp.applicationInstance = this;
}

public static Context getAppContext() {
if (MyApp.applicationInstance != null) {
return MyApp.applicationInstance.getApplicationContext();
} else
return null;
}
}

Logo, em seu Manifest.xml, você deve colocar o atributo android:name=”pacote.MyApp” na tag <application ( <application
android:name=”com.exemplo.MyApp” ). A partir desse momento, quando quiser usar um contexto para qualquer coisa (ex, sharedPreferences ou qualquer outra que necessite um Contexto menos Dialogs que o contexto é da activity e não da Aplicaçao), poderá usar MyApp.getAppContext().

Métodos Estáticos Úteis

Alguns métodos estáticos são bastante úteis e devem estar em uma classe que contém basicamente estes métodos estáticos que podem ser usado por toda aplicação. Assim evita código duplicado.

Método para mostrar um Dialog padrão da aplicação, mas personalizado em relação ao Android

public static void showDialog(Context c, String title, String msg) {
final Dialog dialog = new Dialog(c,
android.R.style.Theme_Translucent_NoTitleBar);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
//Xml personalizado para o Dialog
dialog.setContentView(R.layout.generic_popup);
OnClickListener closeDialogListener = new OnClickListener() {

@Override
public void onClick(View v) {
dialog.dismiss();
}
};
//Um X no canto superior direito é quase que padrao em muitas aplicaçoes
Button close = (Button) dialog.findViewById(R.id.close);
close.setOnTouchListener(buttonTinter);
//Botao de ok que faz o mesmo que o X do canto superior direito.
Button accept = (Button) dialog
.findViewById(R.id.homeactivityButtonaceptar);
accept.setOnTouchListener(buttonTinter);
accept.setOnClickListener(closeDialogListener);
close.setOnClickListener(closeDialogListener);
dialog.show();
}

Método usado para tirar o teclado da tela, bastante usado para quando vai carregar algo logo depois de um formulário ou se vc quer que esconda por algum outro motivo:

public static void hideSoftKeyboard(Activity activity) {
InputMethodManager inputMethodManager = (InputMethodManager) activity
.getSystemService(Activity.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(activity.getCurrentFocus()
.getWindowToken(), 0);
}

Um possível motivo é quando você quer que o teclado esconda quando o usuario nao toque em um EditText:

public static void setupUI(View view) {
// Set up touch listener for non-text box views to hide keyboard.
if (!(view instanceof EditText)) {
view.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
DataHelper.hideSoftKeyboard(PetitionRegisterActivity.this);
return false;
}
});
}

// If a layout container, iterate over children and seed recursion.
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
View innerView = ((ViewGroup) view).getChildAt(i);
setupUI(innerView);
}
}
}

Logo, para ativar este método, se poe no onCreate da atividade que vc quer setupUI(findViewById(R.id.parent))

Celular não salva no SharedPreferences

Alguns celulares antigos tem um problema que quase ninguém nota. Ás vezes salva, às vezes nao salva no SharedPreferences e isso é um problema . A soluçao é salvar também em disco duro (HD) e verificar sempre se existe o SharedPreferences ou nao.

Para isso, a melhor maneira de fazer isso é com uma classe que geralmente é chamada de AppData ou DataHelper que é repsonsável por salvar os dados e gerenciá-los. A grande jogada de mestre é colocar nele objetos estáticos aí e sempre que eles forem mudados, você tem que chamar o método saveData() para que salve no SharedPreferences e no HD do celular.

public class DataHelper {
public static final String pathSharedPreferences = “nome.pacote”;
public static final String pathFileName = “ProjetoNome_FILE”;
//Objeto exemplo que servirá para exemplificar um dado qualquer para salvar.
public static Objet staticObj1;

//Sempre que chamamos o DataHelper ele carrega os dados.
static {
DataHelper.loadData();
}

public static void loadData() {
if (existSharePreferences()) {
loadDataFromSharePreferences();
} else {
loadDataFromFile();
}
}

private static boolean existSharePreferences() {
try {
// Getting the preferences
SharedPreferences settings = context.getSharedPreferences(pathSharedPreferences,Context.MODE_PRIVATE);
// Si no contiene un dato que siempre se guarda es que no existe
if (settings.contains(“id”)) {
return true;
}
} catch (NoClassDefFoundError e) {
// We are on desktop mode!
}
return false;
}

private static void loadDataFromSharePreferences() {
// Getting the preferences
SharedPreferences settings = context.getSharedPreferences(pathSharedPreferences,Context.MODE_PRIVATE);
// Leo el id de CGM
registrationCGMId = settings.getString(“registrationId”, null);
user = getUserData();
}

private static void loadDataFromFile() {
Data data = (Data) loadFile(pathFileName);
// Si es nulo es que no se ha podido leer
if (data == null) {
return;
}
// Leo el id de CGM
registrationCGMId = data.registrationCGMId;
user = data.user;
}

public static void saveFile(String filename, Object object) {
try {
FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(object);
os.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

public static Object loadFile(String filename) {
Object object = null;
try {
          Log.i(“AppData.Load”, “Trying to load file ” + filename);
FileInputStream fis = context.openFileInput(filename);
ObjectInputStream is = new ObjectInputStream(fis);
object = is.readObject();
is.close();
} catch (Exception e) {
Log.i(“AppData.Load”, “Exception ” + e);
return null;
} catch (NoClassDefFoundError e) {
return null; // when on desktop mode
}
return object;
}

public static void saveData() {
// Guardo con los dos mecanismos
saveDataInSharePreferences();
saveDataInFile();
}

public static void saveDataInSharePreferences() {
SharedPreferences.Editor prefs = MyApp.getAppContext().getSharedPreferences(pathSharedPreferences, Context.MODE_PRIVATE).edit();
prefs.put(“objeto1″, staticObj1);
prefs.commit();
}

public static void saveDataInFile() {
//Objeto criado para gerenciar seus dados. Basicamente vai ter os dados que vc quer salvar. Seria como um envelope.
Data data = new Data();
data.obj1 = staticObj1;
// Guardo en disco
saveFile(pathFileName, data);
}

A classe Data basicamente só terá um atributo, ou seja, será:

public class Data implements Serializable{
Object obj1;
}

Lembre-se sempre de implementar Serializable nos objetos que você quer salvar!