Aparelhos, resoluções e definições para compra ou desenvolvimento móvel

Estudando um pouco sobre design responsivo e Webapps, encontrei alguns artigos sobre essa discussão de aparelhos móveis com alta resolução, tela de retina e outros.
Existe alguns sites que podem ser usados como referência sobre esse estudo, para quem gosta da matemática das coisas:
http://blogs.discovermagazine.com/badastronomy/2010/06/10/resolving-the-iphone-resolution/

Sobre especificações de hardware e UX:
http://www.nngroup.com/articles/hardware-specs-vs-user-experience/

E sobre estudos de distâncias do olho humano do dispositivo:
http://www.w3.org/TR/css3-values/#reference-pixel

Resumindo, a escolha do aparelho deve ser baseado no tamanho da tela, sua resolução e a maneira de usa-lo.
Lógico que quanto mais pixel ratio o aparelho tiver, melhor será o as visualização do conteúdo.
Escolher um aparelho para o conforto adequado a seu olho e a distância que pretende usa-lo é essêncial para a compra do dispositivo.
Abraços,
André Rezende

Anúncios

KSoap2 Android e .NET

Como todos sabem, a MS tem alguma iniciativas que fogem dos padrões. Um deles é WS e SOAP.
Para se trabalhar com Android e WS em .NET utilizamos a Ksoap2, onde é necessário informar ao envelope que está trabalhando com .NET. Sendo assim a lib irá remover algumas propriedades dos elementos do envelope.
Caso esse atributos forem enviados, por exemplo na versão 11, teremos então uma falha de recebimento do envelope no WS .NET.
Assim como essa padronização diferenciada da MS, o KSoap2 em Android também apresenta alguns BUGs, tal como trabalhar com objetos complexos e enviar ao WS .NET que o atributo “anytype” não deverá ser enviada.

Procurei em vários forums(Stackoverflow meu favorito) alguma forma para resolver esse problema, não a encontrei e então resolvi sobrescrever o método createRequestData da classe HttpTransportSE, resolvendo assim esse bug para remover atributos indesejados.

A finalidade abaixo é somente remover o atributo “anytype” de seu envolope, mas necessitante de excluir outros atributos, basta modificar o código abaixo.

public class CustomHttpSE extends HttpTransportSE {
	...
	
	@Override
    protected byte[] createRequestData(SoapEnvelope envelope)
            throws IOException {
        byte[] envelopeReturned = super.createRequestData(envelope);

        String requestObject = new String(envelopeReturned);
        String pattern = "i:type=\"d:anyType\"";

        SoapSerializationEnvelope soapEnvelope = (SoapSerializationEnvelope) envelope;
        if (soapEnvelope.dotNet) {
            if (requestObject.contains(pattern)) {
                requestObject = requestObject.replace(pattern, "");
            }
        }
        
        return requestObject.getBytes();
    }
}

Espero ter ajudado,
André Rezende

Sessões SOAP dotNet em Ksoap-android.

Pela falta de informação sobre esse assunto resolvi publicar esse POST para não cair em esquecimento.

Se você precisa armazenar a sessão de uma conexão WS no Android utilizando o Ksoap, já adianto, não é possível fazer utilizando a biblioteca de versão 2.6.5. Mas para tanto, existe uma solução simples, mas que leva tempo para descobrir e segue abaixo.

package br.com.cacula.fv.servicos.soap.connection;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.ksoap2.HeaderProperty;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;
import org.xmlpull.v1.XmlPullParserException;

public class CustomHttpSE extends HttpTransportSE {
    private String sessionID;
    
    public CustomHttpSE(String URL, int timeout) {
        super(URL, timeout);
    }

    public String getSessionID() {
        return sessionID;
    }

    public void setSessionID(String sessionID) {
        this.sessionID = sessionID;
    }

    @Override
    public List call(String soapAction, SoapEnvelope envelope)
            throws IOException, XmlPullParserException {
        SoapSerializationEnvelope soapEnvelop
        headers = new ArrayList<HeaderProperty>();
        
        if (soapEnvelope.dotNet && !"".equals(sessionID)) {
            headers.add(new HeaderProperty("Cookie", "ASP.NET_SessionId="
                    + sessionID));
        }
        return super.call(soapAction, envelope, headers);
    }

}

Agora utilize a CustomHttpSE ao invés da HttpTransportSE que vem dentro do Ksoap, lembre-se de fazer um set ao sessionID e as seguintes opções de dotNET.

envelope.dotNet = true;
envelope.implicitTypes = true;
envelope.setAddAdornments(false);

Abraços,
André Rezende

Existe uma alternativa melhor do que usar emulador no Android?

virtualbox

Sim, isso é possível.
O emulador para Android tem vários bugs tais como: perda de configuração, lentidão para testes, travamentos inesperados dentre outros. Aí vem o fulano e diz, tem que usar o “device”, não tem como usar outra coisa.
Eu mesmo já pensei dessa forma até o dia de ontem.

Quem gosta de atrasar um cronograma de desenvolvimento por causa de emulador?
Felizmente a Google fornece uma linguagem, que no meu ponto, excelente, pois aumenta e muito a produtividade do pessoal que já está acostumado a trabalhar com Java, por outro lado, continua com esse emulador que parece evoluir a passos lentos.

Talvez não tenham interesse em disponibilizar algo mais “usável” aos desenvolvedores, outra estratégia de venda de dispositivos. Nós, não temos condição de ficar comprando vários dispositivos somente para uso nos testes de nossas aplicações.
Então, como posso fazer de uma forma mais rápida?
Já digo de antemão, a forma que irei descrever abaixo funciona com processadores de número igual ou maior de 4 núcleos.
Testei em algumas com dois núcleos e está tão lento quanto o emulador. A vantagem nesses casos com 2 núcleos, configurar uma única vez e inicializar sempre do mesmo modo, mesma configuração e outros.

Vamos aos passos: Antes de mais nada, os requisitos são a instalação(setup) do Oracle Virtual Machine e da Android ISO.

Realize a instalação do Oracle Virtualbox no processo, next, next e finish.

O tutorial para instalação da Android ISO.

Após esses passos serem realizados com sucesso, abra um terminal de prompt de comando.
Entre no diretório de instalação do VirtualBox.
No Windows, execute: cd /d C:\Program Files\Oracle\VirtualBox
Aperta a tecla ENTER

VBoxManage.exe setextradata "Android ICS" "CustomVideoMode1" "540x960x16"

A resolução informada no último parâmetro deverá ser a desejada, eu utilizei 549×960.
No VirtualBox, inicialize a VM;
Ao entrar na tela de escolha do SO, no GRUB, aperte a tecla a;
Na próxima tela, digite espaço e adiciona vga=ask, aperte a tecla ENTER;
Procura a resoluçao desejada e digite-a.
Você deve digitar o valor de hexdecimal transformado para decimal(por exemplo 360 hex é 864 em decimal).
Pronto, a sua VM será inicializada na configuração escolhida.

Para sempre incializar a com a configuração desejada.
Selecione no GRUB menu “Debug Mode”
Vá ao diretório /mnt/grub
Abra no vi o menu.lst

vi menu.lst

Adicione vga=864 a linha:

kernel /android-2.3-RC1/kernel quiet root=/dev/ram0 androidboot_hardware=eeepc acpi_sleep=s3_bios,s3_mode DPI=160 UVESA_MODE=320x480 SRC=/android-2.3-RC1 SDCARD=/data/sdcard.img vga=864

Salve o arquivo:

:wq

Desmonte a unidade:

mount -o remount,rw /mnt

Reinicialize a WM:

cd /
umount /mnt
reboot -f

Inicialize a VM do Android ICS, abra o terminal e digite:

ip a

Obtenha o endereço IP para realizar a conexão com seu adb.
Abra o ADB do Android, em minha máquina está no diretório \sdk\platform-tools
No prompt de comando, execute:

adb connect <ENDEREÇO IP OBTIDO NO ANDROID ICS>

Abra sua ferramente de desenvolvimento e automaticamente será reconhecido a VM como um dispositivo, semelhante ao emulador.

No VirtualBox é possível Exportar e Importar as configurações da VM, gerando um arquivo de extensão .ova.
Utilize essa opção se deseja realizar desenvolvimento e testes em mais de uma máquina, utilizei e funciona perfeitamente.

Abraços,
André Rezende

XML, ListView e Adapters no Android

Para criar uma lista simples na tela do Android, obtida de um banco de dados ou XML é necessários criar seu layout e então um Adapter para popular esses valores.
Abaixo um exemplo simples que lê um arquivo XML e exibe os valores em uma lista na tela principal.
Para isso crie um projeto Android no Eclipse.
Na pasta /res do seu projeto, crie uma pasta xml e adicione um arquivo XML de nome todolist.xml

Adicione o código abaixo no arquivo criado:

<?xml version="1.0" encoding="UTF-8"?>
<todoitems>
    <item
        title="Alimentar cão"/>
    <item
        title="Churrasco"/>
    <item
        titulo="Ir ao Pub"/>
    <item
        titulo="Tomar banho"/>
</todoitems>

No xml da atividade padrão, localizado na pasta /layout do seu projeto, substitua o código por:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <ListView
        android:id="@+id/listview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    </ListView>
</RelativeLayout>

Na activity criada pelo Wizard do Eclipse, adicione o código abaixo:

import java.util.ArrayList;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;

import com.example.listview.adapters.CustomAdapter;

public class MainActivity extends Activity {
    static ArrayList<String> todoItems = new ArrayList<String>();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final ListView listview = (ListView) findViewById(R.id.listview);
        final CustomAdapter adapter = new CustomAdapter(this,
                android.R.layout.simple_list_item_1, getResources().getXml(
                        R.xml.todolist));
        listview.setAdapter(adapter);

    }
}

Ao final, adicione o Adapater, que será responsável por adicionar e exibir os valores contidos no arquivo XML:

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.xmlpull.v1.XmlPullParserException;
import android.app.Activity;
import android.content.res.XmlResourceParser;
import android.util.Log;
import android.widget.ArrayAdapter;

public class CustomAdapter extends ArrayAdapter<String> {
    public CustomAdapter(Activity a, int textViewResourceId,
            XmlResourceParser xmlParsers) {
        super(a, textViewResourceId, getListFromXml(xmlParsers));

    }

    private static List<String> getListFromXml(XmlResourceParser xmlParser) {
        List<String> todoItems = new ArrayList<String>();
        int eventType = -1;
        while (eventType != XmlResourceParser.END_DOCUMENT) {
            if (eventType == XmlResourceParser.START_TAG) {

                String strNode = xmlParser.getName();
                if (strNode.equals("item")) {
                    todoItems.add(xmlParser.getAttributeValue(null, "titulo"));
                }
            }

            try {
                eventType = xmlParser.next();
            } catch (XmlPullParserException e) {
                Log.e("Teste", e.getMessage());
            } catch (IOException e) {
                Log.e("Teste", e.getMessage());
            }
        }
        return todoItems;
    }
}

O método getListFromXml() lê o arquivo XML e retorna lista dos itens ao construtor da classe ArrayAdapter. Caso isso não aconteça na chamada do super, os valores não irão ser exibidos.
Abraços,
André Rezende

Trocando máscara de um EditText no Android em tempo de execução

Mascara - CNPJ

Uma das dificuldades encontradas com relação a máscara, é a troca de um campo do tipo EditText em tempo de execução.
Para isso, criei um projeto Android no Eclipse e criei a seguinte classe, a qual é responsável por altear a máscara para o tipo desejado.

package br.com.rezende.mascaras;

import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;

public abstract class Mask {
    public static String unmask(String s) {
        return s.replaceAll("[.]", "").replaceAll("[-]", "")
                .replaceAll("[/]", "").replaceAll("[(]", "")
                .replaceAll("[)]", "");
    }

    public static TextWatcher insert(final String mask, final EditText ediTxt) {
        return new TextWatcher() {
            boolean isUpdating;
            String old = "";

            public void onTextChanged(CharSequence s, int start, int before,
                    int count) {
                String str = Mask.unmask(s.toString());
                String mascara = "";
                if (isUpdating) {
                    old = str;
                    isUpdating = false;
                    return;
                }
                int i = 0;
                for (char m : mask.toCharArray()) {
                    if (m != '#' && str.length() > old.length()) {
                        mascara += m;
                        continue;
                    }
                    try {
                        mascara += str.charAt(i);
                    } catch (Exception e) {
                        break;
                    }
                    i++;
                }
                isUpdating = true;
                ediTxt.setText(mascara);
                ediTxt.setSelection(mascara.length());
            }

            public void beforeTextChanged(CharSequence s, int start, int count,
                    int after) {
            }

            public void afterTextChanged(Editable s) {
            }
        };
    }
}

O método insert() é responsável por criar um objeto do tipo TextWatcher que contém a máscara desejada, armazene esse objeto em sua Activity para uso posterior, conforme código abaixo:

package br.com.rezende.mascaras;

import android.app.Activity;
import android.os.Bundle;
import android.text.TextWatcher;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;

public class MainActivity extends Activity {
    private TextWatcher cpfMask;
    private TextWatcher cnpjMask;
    private RadioGroup radioGroup;
    private RadioButton rdCNPJ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final EditText cpf = (EditText) findViewById(R.id.txtCPF);
        // Armazene seus TextWatcher para posterior uso
        cpfMask = Mask.insert("###.###.###-##", cpf);
        cpf.addTextChangedListener(cpfMask);

        cnpjMask = Mask.insert("##.###.###/####-##", cpf);
        rdCNPJ = (RadioButton) findViewById(R.id.rdCNPJ);

        radioGroup = (RadioGroup) findViewById(R.id.radioGroup);
        radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                int opcao = radioGroup.getCheckedRadioButtonId();
                if (opcao == rdCNPJ.getId()) {
                    cpf.removeTextChangedListener(cpfMask);
                    cpf.addTextChangedListener(cnpjMask);
                } else {
                    cpf.removeTextChangedListener(cnpjMask);
                    cpf.addTextChangedListener(cpfMask);
                }
            }
        });

    }
}

Observe que nas linhas 36 e 39 é realizado um removeTextChangedListener antes do mesmo adicionar a nova máscara ao campo.

Assim, toda vez que um tipo de documento (CPF ou CNPJ) for escolhido em sua Activity, a máscara de edição é alterada.
Se preferir criar sua xml de tela, segue código.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >

        <EditText
            android:id="@+id/txtCPF"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:inputType="text" >

            <requestFocus />
        </EditText>

        <RadioGroup
            android:id="@+id/radioGroup"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <RadioButton
                android:id="@+id/rdCPF"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:checked="true"
                android:text="CPF" />

            <RadioButton
                android:id="@+id/rdCNPJ"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:text="CNPJ" />
        </RadioGroup>
    </LinearLayout>

</RelativeLayout>

Essa tela deverá ficar com o seguinte layout:

CPF

 

Abraços,
André Rezende