quinta-feira, 17 de novembro de 2016

Uso de Singleton e Application customizado

Suponha que você tenha criado uma classe Livro, com o título, descrição, nome do autor e etc. Após isso tenha criado uma classe LivroControl que possuirá um ArrayList de Livros e que conterá os métodos necessários para o controle dessa lista de livros. Acontece que você deseja que essa lista seja única e acessível para toda a aplicação. O que você faz?

Os dois caminhos mais comuns são:
1) Instanciar um objeto LivroControl no método onCreate da classe Application
2) Usar o padrão Singleton

Caso 1: Instanciando LivroControl no Application

Application existe em todo aplicativo Android. É criado na abertura do aplicativo e, também possui um ciclo de vida, que é diferente de uma Activity. Mas, como vou alterar o meu Application?

Para alterá-lo é preciso criar uma classe filha e depois, basta sobrescrever o método onCreate, como no exemplo abaixo:

public class CustomApplication extends Application{

    private LivroControl livroControl;

    @Override
    public void onCreate() {
        super.onCreate();
        livroControl = new LivroControl();
    }

    public LivroControl getLivroControl() {
        return livroControl;
    }
}


Para usar esse CustomApplication no lugar do Application é necessário alterar um atributo xml chamado 'name' que está dentro da tag <application> do AndroidManifest.xml. Segue um exemplo:

    <application
        android:name=".application.CustomApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".ui.activity.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>


Feito isso, é possível acessar a CustomApplication de qualquer lugar:

final CustomApplication customApplication = getCustomApplication();
List<Livro> listaLivros = customApplication.getLivroControl().retrieveLista();


Caso 2: Usando o padrão Singleton


No padrão Singleton, o construtor de LivroControl deverá ser privado. Portanto, não poderá ser instanciado por alguém fora da classe. Quem fará a única instanciação será um método chamado getInstance, que instanciará o LivroControl apenas uma única vez. O método getInstance deve ser estático e thread-safe. Segue o código de exemplo:

public final class LivroControl {
    private final List<Livro> listaLivro = new ArrayList<>();
    private static ItemControl INSTANCE;

    private ItemControl() { }

    public static synchronized ItemControl getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new ItemControl();
        }
        return INSTANCE;
    }
}


Também é possível fazer esse mesmo Singleton usando Enumerações. Basta jogar todos os métodos de LivroControl para dentro de uma Enum e os métodos do LivroControl apenas acessarão os métodos do Enum. Veja o exemplo baixo:

public final class LivroControl {
    public void adicionarLivro(Livro livro) {
        ListaLivros.INSTANCE.adicionarLivro(livro);       
    }

    public enum ListaLivros {
        INSTANCE;

        private static List<Livro> listaLivro;
        static {
            listaLivro = new ArrayList<>();
        }

        private ListaLivros() { }

        public void adicionarLivro(Livro livro) {
            listaLivro.add(livro);
        }
    }
}


Recomendo a leitura desta discussão caso queira se aprofundar no uso de Enumerações em Singletons:
What is an efficient way to implement a singleton pattern in Java?

sexta-feira, 4 de novembro de 2016

Criando um ListView customizado

Usa-se o ArrayAdapter para ListViews bem simples em que seja necessário exibir apenas TextViews que são preenchidos com o toString() do objeto. Enquanto que, para se criar ListViews complexas, é necessário desenvolver um adapter customizado. Mas, para isso, é importante conhecer como funciona a interação entre a ListView e o adapter.

A ListView pergunta ao adapter quantas linhas ele terá que exibir e solicita, para cada objeto da lista, sua representação visual, que retornará uma View preenchida com as informações do objeto.

Para criar um adapter customizado é preciso fazer uma classe que herde da classe BaseAdapter, que é a superclasse de todos os adapters do Android. Essa superclasse possui um construtor com dois atributos: um contexto e uma lista de objetos que se quer exibir. O contexto é usado para carregar os recursos da aplicação (layout, imagens...), além de guardar uma referência para a activity que é passada como parâmetro no construtor.

Ao criar um adapter, tem-se que implementar 4 métodos: getCount(), getItem(int), getItemId(int) e getView(int, View, ViewGroup).

getCount(): retorna a quantidade de linhas que a ListView exibirá (basta pegar o tamanho da lista de um determinado objeto);

Ex.
@Override
public int getCount() { return listaDeObjetos.size(); }


getItem(int): retorna um objeto, da lista de objetos, com base em sua posição;

Ex.
@Override
public Object getItem(int posicao) { return listaDeObjetos.get(posicao); }


getItemId(int): retorna um identificador único de um objeto da lista, como um id;

Ex.
@Override
public long getItemId(int posicao) { return posicao; }


getView(int, View, ViewGroup): método que gera a View que representará cada item da lista; esse processo é feito em 4 etapas:

1) Obter o objeto pela posição passada como parâmetro;
2) Carregar layout;
3) Preencher os componentes com os atributos do objeto;
4) Retornar layout com os componentes devidamente preenchidos com as informações do objeto.

Ex.
public View getView(int position, View convertView, ViewGroup parent) {
    // 1 passo
    MeuObjeto meuObjeto = listaObjetos.get(position);
    // 2 passo
    View linha = LayoutInflater.from(contexto).inflate(R.layout.item_lista, null);
    // 3 passo
    TextView nomeObjeto = (TextView) linha.findViewById(R.id.nome_objeto);
    nomeObjeto.setText(meuObjeto.getNome());
    // 4 passo
    return linha;
}


Para carregar o arquivo de layout, utiliza-se a classe LayoutInflater, que, ao chamar o método inflate(int, Viewgroup) retorna o elemento-raiz do mesmo. Com esse objeto, consegue-se obter a referência dos componentes que ele contém usando o findViewById(int). Uma vez tendo as instâncias dos componentes, basta preencher cada um com as informações do objeto.

O layout da linha pode ser feito da maneira que você quiser. No exemplo acima, o "R.layout.item_lista" só possuia um único TexView, que foi vinculado ao TextView nomeObjeto e depois setado pelo setText. Mas, poderia ter sido criado um layout com uma ImageView e mais dois TextViews, por exemplo. Então, no passo 3, a imagem e os TextViews deveriam ser setados, para que a view linha seja retornada completamente preenchida.

Como a listView só mostra os itens que cabem na tela, não é necessário instanciar todas as views existentes para cada item da lista, apenas as que estão sendo mostradas. Além disso, as views que foram mostradas, mas que, após os rolamento da lista, deixaram de ser mostradas, podem ser utilizadas novamente para apresentar novos itens, sendo necessário apenas uma atualização de seus dados. É para isso que existe o segundo parâmetro do getView: "View convertView". Esse parâmetro é passado pelo Android e faz referência a view que foi instanciada e visualizada na tela, mas que agora não está mais visível devido ao rolamento da listView. Segue um código de utilização desse parâmetro:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View linha;
       
        if(convertView == null) {
            linha = activity.getLayoutInflater().inflate(R.layout.item_lista, parent, false);
        } else {
            linha = convertView;
        }


    TextView nomeObjeto = (TextView) linha.findViewById(R.id.nome_objeto); 
        MeuObjeto meuObjeto = (MeuObjeto) getItem(position);
        nomeObjeto.setText(meuObjeto.getNome());

        return linha;
    }


Feito o adapter customizado, tem-se que utilizá-lo como parâmetro de criação de uma listView. Para isso, basta colocar no onCreate da activity um código parecido com esse:

ListView listView = new ListView(this);
MeuAdapter adapter = new MeuAdapter(this, ListaObjetos);
listView.setAdapter(adapter);


Sempre que a lista de objetos do adapter for modificada, no caso de um acréscimo de um novo item, por exemplo, é necessário informar à listView que isso ocorreu. Para isso, usa-se o método notifyDataSetChanged(). Veja abaixo um exemplo de uso desse método:

    public void setItems(List<MeuObjeto> novaLista) {
        listaObjetos.clear();
        listaObjetos.addAll(novaLista);

        notifyDataSetChanged();
    }


Esse método setItems pode ser criado dentro do adapter. Assim, sempre que se queira modificar a lista de objetos do adapter, basta utilizá-lo, pois ele limpa uma lista de objeto e adiciona uma nova. Ou seja, se a lista foi modificada ao ser adicionado um novo item, a antiga lista será limpa e substituída pela novaLista. Ao final, informa-se à listView que os dados foram modificados pelo notifyDataSetChanged.

Sugestão de leitura: Utilizando o padrão ViewHolder.

quarta-feira, 2 de novembro de 2016

O que significa o atributo xmlns em códigos XML?

Para uma introdução às características do XML, sugiro que leia a postagem "Quais as diferenças entre XML e HTML ?". Agora vamos ao entendimento do atributo 'xmlns'.
Veja esse código XML:

<table>
  <tr>
    <td>Melancia</td>
    <td>Carambola</td>
  </tr>
</table>


Agora veja esse outro código XML:

<table>
  <name>Mesa de Café Africana</name>
  <width>80</width>
  <length>120</length>
</table>


O primeiro código utiliza a tag <table> para se referir a uma tabela com suas linhas e colunas, enquanto que o segundo código utiliza a tag <table> para se referir às características de uma mesa. Dessa maneira, pode ser que ocorra conflito entre esses códigos por possuírem tags idênticas que se relacionam a conteúdos diversos. Por isso, faz-se necessário o uso de prefixos, como no código abaixo:

Primeiro código com prefixo:

<h:table>
  <h:tr>
    <h:td>Melancia</h:td>
    <h:td>Carambola</h:td>
  </h:tr>
</h:table>


Segundo código com prefixo:

<f:table>
  <f:name>Mesa de Café Africana</f:name>
  <f:width>80</f:width>
  <f:length>120</f:length>
</f:table>


Após o uso dos prefixos 'h' e 'f' os códigos acima ficam livres de sofrer conflitos. Entretanto, faz-se necessário o uso do atributo 'xmlns' para identificar os atribuitos usados. Quando um 'xmlns' é definido para um prefixo, todos os elementos filhos com o mesmo prefixo terão o mesmo identificador (namespace). Usa-se como identificador uma Uniform Resource Identifier (URI) que é um conjunto de caracteres que representa uma fonte na internet. O propósito da URI é dar um nome único ao identificador. Uma URI pode ser uma URL (Uniform Resource Locator) por exemplo. Veja o próximo exemplo:

<root
xmlns:h="http://www.w3.org/TR/html4/"
xmlns:f="http://www.w3schools.com/furniture">

<h:table>
  <h:tr>
    <h:td>Melancia</h:td>
    <h:td>Carambola</h:td>
  </h:tr>
</h:table>

<f:table>
  <f:name>Mesa de Café Africana</f:name>
  <f:width>80</f:width>
  <f:length>120</f:length>
</f:table>
</root>


Para não ter que usar o prefixo em todos os elementos filhos, é possível criar um xmlns padrão. Assim, todas as tags que não estiverem prefixas assumirão automaticamente o identificar padrão. Como no código abaixo:

<table xmlns="http://www.w3.org/TR/html4/">
  <tr>
    <td>Melancia</td>
    <td>Carambola</td>
  </tr>
</table> 

Quais as diferenças entre XML e HTML ?

O XML é uma linguagem de marcação, assim como o HTML, entretanto possui diferentes objetivos e características. Abaixo listo as diferenças entre essas linguagens para que possa ficar mais claro.

  • Quanto aos objetivos da linguagem...
XML: feito para carregar/transportar dados com foco no que o dado representa.
HTML: feito para apresentar os dados visualmente com foco no visual.

  • Quanto a pré-definição de tags e estrutura...
XML: não possui tags e nem estrutura pré-definidas, ou seja, o desenvolvedor é que inventa suas próprias tags e estrutura.
HTML: possui tags pré-definidas, como <h1> ou <img> e deve seguir um padrão, por exemplo, as tags <head> e <body> devem estar dentro da tag <html>.

  • Quanto às tags de fechamento...
XML: sempre deve existir tag de fechamento.
HTML: algumas tags funcionam bem mesmo sem tag de fechamento.

  • Quanto ao "case sensitive"...
XML: as tags são case sensitive.
HTML: não são.

  • Quanto ao aninhamento de tags...
XML: tags devem ser aninhadas corretamente. ex. <b><i>texto</i></b>
HTML: pode funcionar mesmo com aninhamento incorreto. ex. <b><i>texto</b></i>

  • Quanto ao truncamento de espaços em branco...
XML: não faz truncamento de espaços em branco, ou seja, 3 espaços em branco continuam sendo 3 espaços em branco.
HTML: faz o truncamento de espaço em branco, ou seja, 3 espaços em branco se transformam em um único espaço em branco.

Outras características do XML:
  • É auto-descritivo. As tags informam o que significa cada dado.
  • XML não faz nada. Caso se queira utilizar essa informação, deve-se criar algum software que leia, carregue, armazene e mostre as informações com base nas tags.
  • É extensível. Se adicionar novas ou remover antigas tags, aplicações antigas ainda funcionarão.
  • Simplifica o compartilhamento, o transporte, a mudança de plataformas e a disponibilidades dos dados.
  • Armaneza dados em formato texto e isso gera uma indepedência de software e de hardware com relação a armanezamento, transporte e compartilhamento de dados.
  • Assim como o HTML, o XML também segue uma estrutura de árvore.
  • O XML prolog, ou seja, o código <?xml version="1.0" encoding="UTF-8"?> é opcional, mas, se for colocado, deve ser posto logo no início.
  • O valor dos atributos sempre deve possuir aspas. Ex. <note date="12/11/2007">, ficaria incorreto caso as aspas não fossem usadas, como a seguir <note date=12/11/2007>.
  • Elementos vazio são tags sem conteúdo. Elementos vazio pode ser expressos de duas maneiras: 1. <element></element> ou 2. <element />.

terça-feira, 1 de novembro de 2016

Como aplicar uma Toolbar em seu app

Antes de utilizar uma Toolbar, é importante conhecer o funcionamento de uma ActionBar. Feito isso, pode continuar a leitura desta postagem.

É aconselhável usar uma Toolbar em vez de uma ActionBar padrão, ou seja, usar uma Toolbar como ActionBar. Isso se deve por conta da Toolbar ser compatível com um maior número de versões android e, além disso, a ActionBar pode variar seu padrão de funcionamento dependendo da versão android que se está usando.

Passos para se utilizar uma Toolbar:
1) Activity deve herdar da AppCompatActivity;
2) No manifest, o <application> deve usar como theme um "...NoActionBar"

<application
    android:theme="@style/Theme.AppCompat.Light.NoActionBar"
    />


3) No layout xml da activity, deve-se criar um elemento toolbar como no código abaixo:

<android.support.v7.widget.Toolbar
   android:id="@+id/my_toolbar"
   android:layout_width="match_parent"
   android:layout_height="?attr/actionBarSize"
   android:background="?attr/colorPrimary"
   android:elevation="4dp"
   android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
   app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>


4) No onCreate() da activity, deve-se chamar o método setSupportActionBar(), passando-se como parâmetro a sua toolbar criada, como no código abaixo:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_my);
    Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
    setSupportActionBar(myToolbar);

    }


Obs. O uso do setSupportActionBar(myToolbar) deve ser feito após o setContentView(R.layout.activity_my), caso contrário o título e os botões da toolbar não aparecem. Além disso, deve-se colocar <item name="windowActionBar">false</item> no style que está sendo usado.