10 erros que todo desenvolvedor Android deve evitar

Fala pessoal! Nesse post irei explicar alguns dos erros mais comuns cometidos por nós desenvolvedores Android, baseado em leituras e minha experiência em desenvolvimento com a plataforma. Em cada trecho, explico o erro e como devemos fazer para evitá-los.

#1: Utilizar experiencias de outras plataformas

Antes, era muito comum ver uma app Android imitando as experiencias de outras plataformas. Geralmente, por causa de um porting da mesma app existente em iOS ou Windows Phone; ou por exigência do cliente ou por utilizar algum framework cross-platform. Usuários Android querem e esperam experiencias da plataforma Android. Algo como adicionar um botão de voltar em sua app (existe um botão físico próprio para isso, sabia?), utilizar abas estáticas abaixo ou adaptar ícones retangulares com cantos arredondados (a menos que faça parte da sua logo) são apenas alguns exemplos de experiencias que se encaixam muito bem em outras plataformas, porém devem ser evitadas em Android. Aliás, há algum tempo, a Google desenvolveu guidelines que sugerem excelentes dicas e orientam como deve ser o desenvolvimento, design e distribuição de um aplicativo Android.

#2: Não investir em design / UX

Está comprovado que a maioria das pessoas, ao comprar um produto, levam em consideração vários fatores, dentre eles primeiramente está seu design, quer seja retrô ou futurista. Mas por quê isso? Por que chama atenção. É bonito de se ver. E assim deve ser a sua app. Os usuários devem ter prazer de utilizá-la. Como dizia o finado Steve Jobs, um bom produto é aquele que não precisa de manual para se utilizar. Ele deve ser tão intuitivo e minimalista para que as pessoas tenham facilidade de usá-lo. Então, invista em um bom design, desde o ícone da sua app, as imagens principais, secundárias, etc. Se você não tem expertise de design, então contrate um. Mesmo assim caso não conheça, existem sites como 99Designs, onde você informa sua ideia e vários designers projetam logos para o seu produto e você escolhe a que achar melhor!

 

#3: Deixar sua app “caduca”

Um dos grandes desafios em Android é acompanhar a evolução desenfreada da plataforma. E a cada novidade, surgem mais APIs, novos frameworks, novos design patterns, novos widgets, etc. Porém, acabamos por procrastinar em atualizar as apps com novidades (exemplo como começar a utilizar Fragments em vez de somente Activity) por medo de “quebrar” o que está funcionando, cultura de que “time que está ganhando não se mexe”. Mas tudo isso você só estará contribuindo cada vez mais para o fracasso da sua app. Então não seja seu próprio vilão e aproveite para estudar as novas APIs, tendencias e benefícios trazidos por essas novidades. Saiba que os seus concorrentes estão fazendo isso agora mesmo. E você não quer ficar para trás, não é?!

#4: Impor o usuário a utilizar sua app de uma única maneira

Outro grande erro que vejo desenvolvedores cometerem é exigir que o usuário utilize seu smartphone ou tablet de uma única maneira, travando a orientação. Saiba que, segundo Neto Marin (Developer Advocate da Google Brasil), travar orientação é um dos principais motivos para que sua app não seja destaque na Google Play Store (exceto jogos). Afinal, quem não sonha em querer ver sua app como uma das mais baixadas ou recomendadas pelos editores da loja de aplicativos? :) Então para evitar isso, vão duas dicas essenciais:

Forneça recursos alternativos quando ocorrer mudanças de configuração: Suponha que você possua um layout em res/layout/activity_main.xml. Adicionando o novo layout em res/layout-land/activity_main.xml, o dispositivo irá carregá-lo quando tiver no modo paisagem (landscape). Além disso, com o uso dos qualifiers, é possível fornecer mais layouts alternativos de acordo com vários aspectos da plataforma, como nível de densidade, versão da API, resolução, etc. Confira aqui a lista de qualifiers disponíveis na plataforma.

– Trate orientação de acordo com o ciclo de vida da Activity: Imagine que você está quase terminando de preencher vários campos de um cadastro em alguma app e, durante o preenchimento, decide segurar o seu dispositivo de maneira mais confortável, por exemplo, no modo paisagem. Daí vem a surpresa: o que você estava digitando se apagou e você terá que digitar tudo novamente, mas não com a mesma paciencia de antes, claro! Por que isso aconteceu? Em Android, quando ocorre qualquer mudança na configuração do dispositivo (ex: orientação, idioma, método de entrada, etc), o ciclo de vida é reiniciado para que as mudanças tenham efeito e a tela seja carregada com recursos alternativos para aquela situação, caso existam. Sendo assim, o método onCreate() é chamado novamente.

Ciclo de vida de uma Activity

Ciclo de vida simplificado de uma Activity

Dessa forma, antes da tela ser pausada para exibir a nova tela em outra orientação, é possível salvar informações pendentes (ex: informações de campos de texto, posição de item selecionado na lista, etc) para que os dados não sejam perdidos. Para isso, basta sobrescrever o método onSaveInstanteState(). E sobrescrevendo o método onRestoreInstanceState() é possível recuperar as informações previamente salvas. Os códigos a seguir, mostram um exemplo salvando um texto de um campo de texto e recuperando-o posteriormente.

@Override
public void onSaveInstanceState(Bundle outState){
    super.onSaveInstanceState(outState);

    // salvando o texto antes da orientação ocorrer
    String texto = campoDeTexto.getText().toString();
    outState.putString("texto", texto);
}
@Override
public void onRestoreInstanceState(Bundle inState){
    super.onRestoreInstanceState(inState);

    // texto recuperado durante a transição de orientação de tela
    String texto = inState.getString("texto");
    campoDeTexto.setText(texto);
}

#5: Atualizar todo conteúdo da Activity dinamicamente

Suponha que você precise atualizar o conteúdo da tela de acordo com diferentes contextos. Como você faria? Uma péssima maneira que deve ser evitada a todo custo é o carregamento de layouts distintos para a mesma Activity, conforme o trecho a seguir.

// método chamado ao clicar em alguns botões
public void onClick(View view){
     if (view == botao1){
        setContentView(R.layout.activity_main1);
        // tratar componentes do layout activity_main1.xml

    } else if (view == botao2){
        setContentView(R.layout.activity_main2);
        // tratar componentes do layout activity_main2.xml

    } else if (view == botao3){
        setContentView(R.layout.activity_main3);
        // tratar componentes do layout activity_main3.xml
    }
}

No código anterior, caso o usuário escolha um botão específico, um novo layout é cattegado. Além disso ser uma má prática de programação, pois aumenta o acoplamento e dificulta a manutenção; toda vez que o conteúdo da tela for alterado, isso pode implicarconsideravelmente na performance da sua app, pois a cada chamada do método setContentView(), a árvore de componentes Java é inflada na memória. Então, veja a seguir duas maneiras adequadas de se atualizar o conteúdo de telas:

– Criar uma Activity por layout: Adaptando o trecho de código anterior, teríamos que criar mais duas Activities para os layouts activity_main2.xml e activity_main3.xml, para encapsular toda a regra de negócio específica de cada tela. Depois, conforme o botão pressionado, basta chamar a tela por meio do método startActivity(). Confira no trecho a seguir como deve ficar.

public class MainActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void onClick(View v){
        if (v == botao2){
            startActivity(new Intent(this, Activity2.class));

        } else if (v == botao3){
            startActivity(new Intent(this, Activity3.class));
        }
    }
}

– Utilizar Fragment: A versão Honeycomb (API Level 11) trouxe uma das grandes novidades utilizadas até hoje: os Fragments. Um Fragment nada mais é do que um componente com seu próprio ciclo de vida e que exibe interface gráfica que pode ser reutilizado em outras telas. Dessa maneira, ficou muito mais simples e prático atualizar apenas porções de telas com Fragment, trazendo mais dinamismo para as apps. Para saber mais sobre Fragment, confira este guia passo-a-passo que a Google preparou.

Exemplo da utilização de Fragments

Exemplo da utilização de Fragments

#6: Projetar sua app para apenas um tipo de dispositivo

Já existem mais de bilhões de dispositivos Android no mundo todo, e a cada dia esse número cresce exponencialmente. Dispositivos de 5, 7, 9, 10 polegadas, rodando várias versões de API, com média, alta definição de tela. Porém, você desenvolveu apenas utilizando como referencia seu próprio smartphone e assume que rodará em qualquer outro dispositivo, pois você escolheu a versão mínima do SDK como a menor possível. E quando você vai publicar sua app, chove de reclamações negativas na Google Play sobre incompatibilidade em tablets, imagens distorcidas em telas maiores, espaçamento excessivo entre os componentes, etc. Então, para evitar esse cenário caótico, projete sua app para rodar na maioria dos dispositivos.

Android tem uma particularidade chamada de density-independent pixel (dp),  que representa uma unidade virtual utilizada para dimensões de layouts, posições e espaçamentos, de maneira independente de densidade de tela. Por exemplo, 1dp equivale a 1 pixel físico em uma tela de 160 dpi, que é baseline para uma tela de densidade média. Dessa forma, em tempo de execução, a plataforma irá escalonar transparentemente as unidades de acordo com a densidade de tela. A conversão para pixels é realizada da seguinte maneira:

px = dp * (dpi / 160)

Nas imagens a seguir – retiradas do próprio Android Developer Guide – três layouts com densidade baixa, média e alta são mostrados quando o mecanismo de independência de densidade não é utilizado; e as mesmas telas, quando há esse mecanismo habilitado. Perceba que na segunda imagem, as figuras se mantêm com o mesmo tamanho.

 

density-test-bad

Telas sem o mecanismo de independência de densidade

density-test-good

Telas com o mecanismo de independência de densidade

 

A maneira mais adequada de realizar isso, é fornecendo recursos alternativos como drawables, layouts por meio dos qualifiers gerais:

  • ldpi: baixa densidade de tela, aproximadamente 120 dpi (depreciado)
  • mdpi: média densidade de tela, aproximadamente 160 dpi
  • hdpi: alta densidade de tela, aproximadamente 240 dpi
  • xhdpi: muito alta densidade de tela, aproximadamente 320 dpi
  • xxhdpi: 2x muito alta densidade de tela, aproximadamente 480 dpi
  • xxxhdpi: 3x muito alta densidade de tela, aproximadamente 960 dpi

Durante o desenvolvimento de um layout, é possível prever como ele irá ficar nos outros tamanhos de tela, selecionando a visão Preview, clicando no ícone do dispositivo no canto superior  e marcando Preview All Screens.

 

#7: Ignorar os avisos do Lint

Sabe aquela lâmpada amarela que aparece quando você está criando um layout em XML ou referenciando alguma API em Java? Pois é, são avisos que a ferramenta Lint fornece para que sua app tenha o máximo de compatibilidade em outros dispositivos. Não apenas isso, mas alerta sobre várias situações, como:

  • Chamada de um comando específico de uma API Level (exemplo, v21). Esse trecho irá quebrar caso o dispositivo esteja usando uma API abaixo (exemplo, v14).
  • Ignorar o atributo android:contentDescription de um ImageView, utilizado especialmente para questões de acessibilidade para usuários com deficiência visual
  • Utilização de strings estáticas em XML (hard-code string). Dessa forma, o recurso de internacionalização será prejudicado, pois a string será a mesma independente de idioma
  • Utilização de espaçamento e margem inicial e final para idiomas com suporte de escrita da direita para a esquerda (R-T-L  – Right To Left)
  • Utilização de aninhamento de layouts que pode prejudicar a performance da sua app

No Android Studio, acessando Settings > Inspections > Android Lint, é possível visualizar todas as inspeções que a ferramenta pode realizar.

Configurações do Lint

Configurações do Lint

#8: Não utilizar animações

Animações trazem fluidez, dinamismo e aspectos de realidade para a sua app. Então, utilize-as para dar um feedback visual melhor, como por exemplo, realizar um efeito de esmaecimento  (fade in/fade out), por exemplo, quando as imagens forem carregadas.

Carregar uma animação é muito fácil e existem várias maneiras de se fazer isso. A API de animação é bem extensa e existe uma excelente documentação no Android Developer Guide que explora todas elas. Para realizar um simples efeito de crossfade de uma imagem, seria necessário o trecho a seguir:

ImageView imageView = (ImageView) findViewById(R.id.view);
Animation animation = AnimationUtils.loadAnimation(this, android.R.anim.fade_in);
animation.startAnimation(imageView);

#9: Bloquear a Thread de UI

Toda ação que é realizada nos métodos de uma Activity ou Fragment são realizados por meio da Thread de UI. Isso significa que, se alguma tarefa pesada for realizada no método onCreate() (lembre-se que nesse momento, o usuário ainda não está interagindo com a app), a arquitetura da plataforma irá exibir a temida tela Application Not Responding (ANR), caso esse processamento demore mais que 5 segundos. Visualmente, o que o usuário irá perceber é que sua app travou e, provavelmente, irá fechar sua app, desinstalar e colocar um comentário não muito agradável na Google Play. Um jeito bem simples de travar sua app é realizando por exemplo uma consulta ao banco de dados interno, que dependendo do tamanho e do poder de processamento dos outros devices dos seus usuários, pode demorar muito mais do que você pensa. Então, para evitar esse transtorno e muitos usuários estressados, realize tarefas pesadas em segundo plano, utilizando Services, AsyncTasks, worker threads juntamente com handlers. Não esqueça sempre de informar o usuário sobre o status do processamento (ex: ProgressDialog, ProgressBar) enquanto as informações estão sendo carregadas. Outra alternativa é utilizar bibliotecas que já fazem tudo isso de forma transparente, como a Picasso, da Square, para carregamento de imagens em segundo plano.

Tela ANR sendo exibida para um processamento muito longo na Thread de UI.

Tela ANR sendo exibida para um processamento muito longo na Thread de UI.

#10: Ignorar o ciclo de vida dos componentes

E o último mas não menos importante erro está relacionado ao fato de alguns desenvolvedores (principalmente os menos experientes) não levarem em consideração o ciclo de vida dos componentes de alto nível: Activity, Broadcast Receiver, Service e Content Provider. Vão aqui algumas dicas essenciais para desenvolver uma app otimizada sem que prejudique a performance:

  • Durante o método onCreate(), não há interação com o usuário (a tela ainda nem está visível). Por isso, ele deve ser utilizado apenas para inicialização de componentes e objetos, como por exemplo inflar os componentes de um layout para serem utilizados em outros métodos futuramente;
  • A partir do método onResume(), há a interação com o usuário, isto é, a UI está totalmente visível e o usuário poderá interagir com a mesma (ex: clicar em botões, realizar eventos de scroll, etc). Dessa forma, implemente trechos que devem ser atualizados a cada vez que a tela estiver para ser exibida, como vincular os dados das preferências nos componentes gráficos, iniciar tarefas pesadas em segundo plano, indicando o status do processamento visualmente, por meio de progressos.
  • O método onPause() é chamado logo que a tela está prestes para ser interrompida. Dessa maneira, aproveite para salvar informações que precisam estar disponíveis assim que o usuário voltar para sua app, como por exemplo, voltar para a fase do game que ele estiver jogando;
  • No método onDestroy() o componente será destruído. Por isso, é uma boa oportunidade para desalocar recursos, como eventos de captura de broadcasts (Intents), atualização de geolocalização, conexão com serviços da Google Play Services, etc;
  • Utilize o método onLowMemory() para deixar sua app em um modo de economia de energia, desalocando recursos que possam consumir muito sua bateria, como conexão de dados ou Wi-Fi, serviço de geolocalização, brilho de tela, etc;
  • Salve informações temporárias durante o método onSaveInstanteState() e recupere no onRestoreInstanceState(), para evitar perda durante a alteração nas configurações do dispositivo (ex: rotacionar uma tela, alterar idioma, alterar modo de entrada, salvar posição de itens na lista, etc)

É isso aí galera, vamos seguir as boas convensões e boas práticas em Android? :) Abraços e até a próxima!

Referências

Android Developer Guide

Android Design Guide

Top 10 Most Common Android Development Mistakes: A Programming Tutorial

Tá safo! no Belém Code 2015

 

belemcodein2015-b1O Belém Code 2015 é um evento de programação de computadores que tem como principal função desmistificar a ideia de que programação é algo difícil, pelo contrário, é fácil e possibilita a participação e integração de forma divertida descobrindo conceitos e técnicas de introdução à programação. Esperamos que esta iniciativa sirva como incentivo a inclusão digital, e mostre que todos somos capazes de programar, seja adultos, jovens ou crianças. (Fonte: site do evento)

O Tá safo! foi convidado para apoiar o evento na divulgação e agregar à programação com alguns palestrantes, de garbo e elegância, para mostrarem um pouco de sua vivência com desenvolvimento de software. Horários de a grade completa você pode ver clicando aqui. Segue as palestras do pessoal do Tá safo! (olha o naipe da galera)

marcelo andrade Marcelo Andrade Apresentando a linguagem Python
marcelosa Marcelo Sá MPS-BR em times ágeis
luiz sanches Luiz Sanches A Linguagem de Programação Ruby
ramon rabello Ramon Rabello O Caminho de um Desenvolvedor Android

O evento é pago, pois tem o objetivo de ajudar a entidade Ação Parceiros, capitaneada pelo Raimundo Xavier que, desde já, tem o nosso agradecimento pelo convite e esperamos que seja mais um evento de sucesso. Nos encontramos por lá para conversar sobre programação e nerdices afins.

Real-Time com XMLHttpRequest

cookiemonster_xmlhttprequest

O XMLHttpRequest ou XHR, mudou tudo. Graças a essa API, o client-side pode transferir dados via Javascript. Ele inseriu o “D” na frente do HTML e nos permitiu obter assincronismo em nossas aplicações web. Antes dele, uma página precisava ser recarregada para enviar ou receber dados do servidor. Apesar de estar presente nos browsers desde o Internet Explorer 5, o XHR ainda é essencial para desenvolvermos aplicações web ricas e pode também ser usado – apesar de existir, hoje, tecnologias como websocket – para aplicações de tempo real.

XHR nos fornece uma maneira eficiente de sincronizar atualizações entre cliente e servidor. Quando necessário, uma requisição XHR envia dados para o servidor. O problema é o inverso.

Notificações em tempo real

O HTTP não fornece nenhuma maneira de o servidor iniciar uma nova conexão com o cliente. Para receber notificações, o cliente deve fazer polling, ou seja, fazer requisições periódicas ao servidor para pegar atualizações. Uma outra forma seria utilizar streaming, mas o suporte a streaming do XHR é limitado.

Dependendo da aplicação, podemos conviver com um delay de segundos, por exemplo. Mas algumas aplicações podem ter um overhead na casa dos milisegundos. A melhor forma de transportar dados irá depender.

Polling é muito simples de implementar. Piece of cake. Mas é frequentemente ineficiente. O intervalo de tempo de cada requisição é crítico. Imagine seu navegador fazendo requisições periódicas em frações de segundo para o servidor. Agora imagine milhões de navegadores fazendo a mesma coisa. Um cenário de terror pra qualquer sysadmin. Vamos olhar a função abaixo:


function checkUpdates(url){
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url);
  xhr.onload = function(){ /* processa atualizações vindas do servidor */ };
  xhr.send();
}

setInterval(checkUpdates('/updates'), 2000);

Nesse exemplo, ocorre a checagem por atualizações a cada 2 segundos.

Cada requisição é comumente chamada de ajax. Ele nada mais é que uma requisição HTTP padrão. Muitas dessas requisições serão desnecessárias, pois não virão atualizações na resposta. Dependendo do caso, essas requisições desnecessárias podem ter um custo alto. Podem sobrecarregar a rede e o servidor, além de acabar com a bateria de dispositivos móveis. Uma requisição HTTP 1.x tem aproximadamente 800 bytes sem cookies. Imagine milhares de clientes trafegando 800 bytes a cada 2 segundos. E agora? Websocket? Ainda não. E se existisse uma maneira de eliminar essas requisições desnecessárias? Pior que existe ó.

Long-Polling

Long-polling, a.k.a COMET, a.k.a HTTP push, a.k.a Ajax reverso. Esta técnica nos permite manter a mesma conexão até que a atualização esteja disponível.

HTTP poll vs Long-polling

HTTP poll vs Long-polling

A conexão fica aberta até a atualização estar disponível. Uma vez que a resposta é entregue ao cliente, a conexão é fechada e outra requisição long-polling é enviada ao servidor, aguardando a próxima mensagem.


function checkUpdates(url){
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url);
  xhr.onload = function(){
    ...
    checkUpdates('/updates');
  };

  xhr.send();
}

checkUpdates('/updates');

Pode testar. A bagaça realmente funciona. Mas e aí? Long-polling é sempre eficiente? Não. Só será eficiente quando o intervalo de tempo de atualizações no servidor for mais ou menos constante. Caso contrário, pode gerar mais requisições ao servidor do que o polling com setInterval.

Em alguns casos, long-polling resolve. A primeira versão do chat do facebook utilizava essa técnica. No entanto, hoje existem formas mais eficientes como websocket e server-sent events. Mais eficientes e bem mais trabalhosos.

Até a próxima.

Utilizando Material Design em Android

Material Design

Fala, galera da Comunidade! Neste post, irei mostrar um pouco sobre como desenvolver uma UI em Android, já aplicando o novo conceito de design da Google: Material Design. Se você é iniciante e gostaria de saber como começar a desenvolver para Android, leia o post Iniciando com Android: Introdução ao Android Studio que está bem legal!

Material Design: O Design Unificado da Google

Antigamente, existiam várias “caras” para os produtos da Google, inclusive para tipos de dispositivos diferentes, como web, tablets e smartphones. Ficava complicado manter uma mesma experiencia de usuário, pois cada produto tinha a sua maneira de design. Com o tempo, a Google percebeu isso e reuniu todos designers para que de certa forma pudesse contornar esse problema.

Continuar lendo

O #tasafoemacao voltou!!

tasafo_em_acao_chuck_norris1

O campeão voltou! Em Abril, teremos um Tá Safo em Ação no auditório da FIEPA. Neste encontro teremos palestras com temas diversos, entre gestão, mobile e web, com palestrantes com grande experiência em suas respectivas áreas:

  • Como aumentar a produtividade e a qualidade através da convenção REST em aplicativos web ou mobile com Felipe Iketani
  • SOA – Princípios de Arquiteturas Orientadas a Serviços com Cássio Noronha
  • Elaboração de um Product Backlog Efetivo com Fábio Aguiar
  •  Android Wear: estendendo sua app para relógios inteligentes com Bryan Ollivie e Ramon Rabello

O evento irá ocorrer no dia 08 de Abril, com início às 19:00 horas.

O auditório da FIEPA fica na FIEPA, lógico, na Travessa Quintino Bocaiúva 1588.

Página do evento: http://palestrascoletivas.com.br/events/ta-safo-em-acao-2015

 

Até lá!

A importância da Programação Orientada a Objetos – Parte I

Atualmente, é comum pessoas terem ideias inovadoras. Porém, esses insights não servem para nada se eles permanecerem somente no campo cerebral. Para começar a ver o valor de determinada percepção intelectual é necessário criar o produto que permita concretizar a ideia. Com a evolução dos computadores ao redor do mundo e a franca expansão destas máquinas, um dos caminhos para “materializar” a ideia é desenvolver software.

O algoritmo – uma sequência de passos finitos com o objetivo de solucionar um problema para desenvolver software – não é único e também não é trivial. É necessário entender de diversos conceitos que permeiam o estudo da computação. Dentre eles, destacam-se: Análise de Requisitos, Diagramas UML, Programação Orientada a Objetos (POO), Padrões de Projeto (Design Patterns), Design de Interface do Usuário (UI) e Experiência do Usuário (UX); e Testes Automatizados. Além disso tudo, é preciso realizar leitura de muito material (maioria em inglês) e atualizar-se quase que permanentemente.

Nesta primeira parte sobre a importância de POO, iremos falar exclusivamente dos principais conceitos deste paradigma de programação. Continuar lendo