terça-feira, 20 de dezembro de 2016

Fragments: o que são e para que servem?

Fragments são como "pedaços" de activities que podem ser reutilizados. Os fragments não funcionam sozinhos, pois precisam de uma activity para funcionar. Você pode montar uma activity com vários fragments. Para criar um Fragment basta criar uma classe que herde de Fragment, como no exemplo a seguir:

public class MeuFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View layout = inflater.inflate(R.layout.fragment_layout, container, false);

        return layout;
    }
}


Repare que foi necessário sobrescrever o método onCreateView, cujo retorno será uma view que foi inflada de um layout criado em xml. Pronto. Criamos nosso primeiro fragment, mas como iremos utilizá-lo em uma activity? Primeiramente, é necessário posicionar um FrameLayout no layout xml da activity:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <FrameLayout
        android:id="@+id/localDoFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"><FrameLayout>

</LinearLayout>


Agora temos que fazer com que o Fragment apareça nesse FrameLayout que foi criado. Dentro do método onCreate da activity, use um FragmentTransaction:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_layout);

    FragmentTransaction ft = getFragmentManager().beginTransaction();
    ft.replace(R.id.localDoFragment, new MeuFragment());
    ft.commit();
}


Pronto! Conseguimos posicionar um fragment em uma activity. Usando o mesmo procedimento, é possível posicionais e reutilizar fragments. Isso se torna útil, por exemplo, quando precisamos fazer um layout diferente para uso em tablets, reaproveitando as telas que foram criadas para um celular.

quinta-feira, 15 de dezembro de 2016

Material Palette: Cores Padronizadas da Google

Você tem dúvidas com relação a quais cores usar em seu aplicativo? É um programador e não um designer? Seus problemas acabaram! A Google fornece um padrão de cores a serem usadas em seu aplicativos. Basta escolher uma cor primária e uma secundária no site Material Palette, depois faça o download em formato XML. Cole-o dentro de seu 'res/values'. Depois use essas cores no style.xml. Assim as cores padrão de seu app serão as do novo arquivo XML baixado.

Veja também:
Para fazer com que o seu app siga o padrão das cores baixadas, o seu arquivo style.xml deverá ficar parecido com o exemplo a seguir:

     <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/primary</item>
        <item name="colorPrimaryDark">@color/primary_dark</item>
        <item name="colorAccent">@color/accent</item>
        <item name="android:textColorPrimary">@color/primary_text</item>
        <item name="android:textColorSecondary">@color/secondary_text</item>
        <item name="android:icon">@color/icons</item>
        <item name="android:colorButtonNormal">@color/primary_dark</item>
        <item name="android:divider">@color/divider</item>
    </style>


That's all! ;)

segunda-feira, 12 de dezembro de 2016

Criando um Menu Suspenso (Menu de Contexto) no Android

Um menu suspenso ou menu de contexto, no Android, pode ser criado para aparecer ao se clicar em uma view vinculada a ele. Por exemplo, suponha que você deseje que, ao se clicar em um item de uma ListView, surja um menu sobre ela com algumas opções, como deletar um item. A ListView continuará parcialmente visível, pois um menu de opções ficará sobre ela. Para que isso seja feito, basta sobrescrever o método onCreateContextMenu que está na activity. As opções do menu podem ser criadas programaticamente (sem ter que ser via xml) como no código a seguir:

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
    menu.add("Primeira Opção do Menu");
    menu.add("Segunda Opção do Menu");
    menu.add("Deletar Item");

    super.onCreateContextMenu(menu, v, menuInfo);
}


Agora vai uma pergunta: como o menu suspenso saberá o momento de surgir? Em outras palavras, como eu faço a ligação de uma view com o menu suspenso? Basta registrá-la no menu, usando o método registerForContextMenu que deverá ser colocado dentro do onCreate() da activity:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_meu_laytout);

    registerForContextMenu(MinhaView);
}


O menu de contexto será acionado sempre que houver um clique longo na view com a qual está vinculado. Por isso, caso você já tenha implementado nessa view o método setOnItemLongClickListener, será necessário fazer com que seu retorno se torne 'false', para que não consuma sozinho o evento. Se deixar como 'true', o menu suspenso não será acionado. Caso coloque como 'false', ambos eventos serão acionados. Nesse último caso, os dois métodos podem ser utilizados em conjunto, como, por exemplo, o método setOnItemLongClickListener pode ser usado para pegar um item de uma ListView, armazenando em uma variável de instância, enquanto o menu suspenso pode se utilizar de informações desse item para fazer alguma ação, como deletá-lo, pois o menu não seria capaz de enxergar qual item da ListView é que foi clicado.

O método add do menu retorna um MenuItem cuja referência pode ser armazenada em uma variável de mesmo tipo e, assim, podemos setar nele uma ação que ocorrerá após ser clicado ou mesmo ligá-lo a uma intent. No código abaixo, conecta-se o menu a uma intent, que é executada sem precisar usar o método startActivity. O exemplo a seguir mostra como isso é usado para enviar um sms:

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
    MenuItem menuSMS = menu.add("Enviar SMS");
    Intent intentSMS = new Intent(Intent.ACTION_VIEW);
    intentSMS.setData(Uri.parse("sms:" + meuContato.getTelefone()));
    menuSMS.setIntent(intentSMS);

    super.onCreateContextMenu(menu, v, menuInfo);
}


No caso de um sms, não é necessário adicionar uma permissão no manifest, pois é outro aplicativo do celular que usuará o recurso de envio de sms.

Como já foi dito, posso adicionar um evento de clique no MenuItem, como se fosse um botão. No exemplo a seguir, faço isso com o objetivo de fazer uma ligação do celular.

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {

    MenuItem menuLigar = menu.add("Fazer chamada");
    menuLigar.setOnMenuItemClickListener(new MenuItem.setOnMenuItemClickListener() {
        @Override
        public boolean onMenuItemClick(MenuItem item) {
           

            if(ActivityCompat.checkSelfPermission(NomeActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {

                ActivityCompat.requestPermission(NomeActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 1);

            } else {
                Intent intentLigar = new Intent(Intent.ACTION_CALL);
                intentLigar.setData(Uri.parse("tel:" + meuContato.getTelefone()));

                startActivity(intentLigar);
            }

            return false;
        }
    });
   
    super.onCreateContextMenu(menu, v, menuInfo);
}


É necessário pedir para o dono do celular que ele permita o uso dessa funcionalidade do aparelho. Por isso, faz-se necessário adicionar no manifest a permissão:

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

O código anterior apenas verifica se possui permissão para fazer uma ligação. Caso não possua, o aplicativo solicita uma permissão do usuário. Caso já tenha permissão, então executa uma intent implícita, que busca um recurso de chamada do celular.

sábado, 10 de dezembro de 2016

Usando SQLite e padrão DAO para persistir dados

Uma das formas de persistir os dados em um aplicativo nativo Android é por meio do SQLite. Ele é um mini-SGBD que vai acoplado junto a sua aplicação. Para usá-lo, você pode criar uma classe padrão DAO (Data Access Object), utilizada para manipulação de dados, que irá gerenciar os dados conjuntamente com o SQLite. Para exemplificar, vamos utilizar um model Usuario que possui um id do tipo long, um nome do tipo String e um e-mail do tipo String como variáveis de instância. Então, vamos criar um UsuarioDAO, que irá criar o banco de dados, uma tabela de usuários e cuidará da manipulação e recuperação de usuarios do banco da dados.

Para isso, crie uma classe UsuarioDAO e faça com que ela herde de SQLiteOpenHelper. Depois sobrescreva os métodos onCreate e onUpgrade. O método onCreate é executado sempre que a aplicação é instalada e utilizada pela primeira vez. O método onUpgrade é executado sempre que ocorre uma mudança de versão do banco de dados. Tendo em vista essa características, vamos criar a tabela de Usuarios no onCreate. O código abaixo servirá para exemplificar o que foi dito até aqui:

public class UsuarioDAO extends SQLiteOpenHelper {
    private static final String DATABASE = "appExemplo";
    private static final String TABELA = "Usuarios";
    private static final int VERSAO = 1;

    public UsuarioDAO(Context context) {
        super(context, DATABASE, null, VERSAO);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE " + TABLE + " ("
            + COL_ID + " INTEGER PRIMARY KEY, "
            + COL_NOME + " TEXT UNIQUE NOT NULL, "
            + COL_EMAIL + " TEXT"
            + ");";
       
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        String sql = "DROP TABLE IF EXISTS " + TABLE;

        db.execSQL(sql);
        onCreate(db);
    }
}


É preciso tomar muito cuidado com o uso da versão. Ela deve começar com o valor 1 e sempre que ocorrer qualquer mudança na estrutura do banco de dados, ela deve ser alterada sempre acrescentando um valor a mais. Ou seja, inicia-se com 1, caso seja acrescentado um atributo novo como telefone, ou um atributo do banco tenha tido seu nome alterado, então, a próxima versão deve ter o valor 2 e depois 3, 4 e assim por diante. Para o programador isso não fará muita diferença, mas, para o cliente que comprou o seu aplicativo fará muita diferença, pois uma mudança de versão da maneira incorreta poderá fazer com que o cliente perca todos os dados que ele já possuía. Portanto, cuidado!

Temos a nossa tabela de usuário e agora precisamos inserir e recuperar usuários do banco de dados. Para inserir, não podemos usar o método execSQL para executar o código SQL diretamente, pois isso é muito perigoso. Caso fique utilizando execução direta de SQL, pode ser que alguém se aproveite disso para fazer uma injeção de SQL (SQL Injection), causando danos a sua base da dados. Por isso, devemos inserir usuários por meio de um objeto ContentValues que funciona como um HashMap. Após colocar os dados de um usuário em um ContentValues, basta inseri-lo passando-o como parâmetro de um getWritableDatabase().insert(). Veja o exemplo a seguir:

    public void inserir(Usuario usuario) {
        ContentValues values = new ContentValues();

        values.put(COL_NOME, usuario.getNome());
        values.put(COL_EMAIL, usuario.getEmail());

        getWritableDatabase().insert(TABELA, null, values);
    }


Inserimos o usuário e agora temos que recuperá-lo. Temos que posicionar um cursor para o local em que gostaríamos de recuperar os dados, então você usa um SELECT para pegar os dados que você quer, depois use o método moveToNext() para movimentar o cursor que irá se posicionar na próxima linha da tabela. Então, é só fazer um loop que irá percorrer todas as linhas da tabela. Vá colocando todos os dados em uma lista e, então, retorne-a. Veja o exemplo a seguir:

    public List<Usuario> getLista() {
        List<Usuario> lista = new ArrayList<>();

        String sql = "SELECT * FROM " + TABELA + ";";
        Cursor c = getReadableDatabase().rawQuery(sql, null);

        Usuario usuario = null;

        while(c.moveToNext()) {
            usuario = new Usuario();

            usuario.setId(c.getString(c.getColumnIndex(COL_ID)));
            usuario.setNome(c.getString(c.getColumnIndex(COL_NOME)));
            usuario.setEmail(c.getString(c.getColumnIndex(COL_EMAIL)));

            lista.add(usuario);
        }

        return lista;
    }


Além de inserir um usuário, é preciso deletar algum quando necessário. Por isso, também precisamos criar um método deletar que receba um usuário como parâmetro e o exclua do banco por meio da checagem de seu id. Vamos utilizar o método getWritableDatabase().delete para evitarmos uma sql injection. Ele receberá como parâmetro um array de strings. Neste caso, iremos passar o id do usuário para informar qual usuário é que deve ser excluído. Um exemplo de código pode ser visto a seguir:

    public void deletar(Usuario usuario) {
        String[] argumentos = {usuario.getId().toString()};
        getWritableDatabase().delete(TABELA, COL_ID + "=?", argumentos);
    }


Já inserimos, recuperamos e excluímos usuários do banco. Agora precisamos alterar ou dar um update no conteúdo de um determinado usuário que se encontra no banco. Para isso, precisamos novamente receber um usuário como parâmetro de um método alterar, que conterá todas as novas informações a serem alteradas em um usuário já inserido. A seguir segue um exemplo de código:

    public void alterar(Usuario usuario) {
        ContentValues values = new ContentValues();
        values.put(COL_NOME, usuario.getNome());
        values.put(COL_EMAIL, usuario.getEmail());

        String[] argumentos = {usuario.getId().toString()};
        getWritableDatabase().update(TABELA, values, COL_ID + "=?", argumentos);
    }


Pronto, agora a nossa classe já está bem funcional e pode ser utilizada em uma activity:

UsuarioDAO dao = new UsuarioDAO(MinhaActivity.this);
List<Usuario> usuarios = dao.getLista();


Suponha agora que você tenha que alterar a estrutura de banco de dados, aumentando mais um campo, que neste exemplo será o endereço do usuário. Para isso, precisamos adicionar o novo campo no onCreate para aqueles que irão instalar o aplicativo pela primeira vez e precisamos apenas atualizar a nova estrutura do banco no celular daqueles que já possuem o aplicativo instalado. No segundo caso, é preciso modificar o método onUpgrade(). Além disso, é imprescindível trocar a versão, que atualmente está 1, para o valor 2. Então, o onCreate ficará assim:

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE " + TABLE + " ("
            + COL_ID + " INTEGER PRIMARY KEY, "
            + COL_NOME + " TEXT UNIQUE NOT NULL, "
            + COL_EMAIL + " TEXT, "
            + COL_ENDERECO + " TEXT"
            + ");";
       
        db.execSQL(sql);
    }


Enquanto que o onUpgrade não apenas mais derrubará a tabela e a criará novamente. Ele agora será utilizado para alterar a tabela antiga, acrescentando mais uma coluna:

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        String sql = "ALTER TABLE " + TABLE + " ADD COLUMN " + COL_ENDERECO + " TEXT;";

        db.execSQL(sql);
    }


Lembre-se também de adicionar o novo campo nos métodos inserir, getLista e alterar.

E se, por acaso, decidirmos alterar novamente o banco de dados? O banco irá para a versão 3, mas o que acontece com aqueles que instalaram o aplicativo e estão ainda com versões anteriores? Além de atulizar o método onCreate e alterar o inserir, getLista e alterar, é preciso tomar cuidado com o método onUpgrade. Ele ficará responsável para progressivamente realizar todas as atualizações desde a que está instalada no celular até chegar na atualização atual.

Suponha então que vamos adicionar o telefone do usuário. O onUpgrade ficaria assim:

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        String sql;

        switch(oldVersion) {
            case 1:
                sql = "ALTER TABLE " + TABLE + " ADD COLUMN " + COL_ENDERECO + " TEXT;";
                db.execSQL(sql);
            case 2:
                sql = "ALTER TABLE " + TABLE + " ADD COLUMN " + COL_TELEFONE + " TEXT;";
                db.execSQL(sql);
        }
    }


Assim, se a versão antiga instalada for a 2, será feita a atualização do banco executando o que está no case 2. Se a versão antiga instalada for a 1, será feita a atualização do banco executando o que está tanto no case 1 quanto no case 2. Repare que eu não usei o break!

O código completo de nosso UsuarioDAO pode ser visto logo a seguir:

public class UsuarioDAO extends SQLiteOpenHelper {
    private static final String DATABASE = "appExemplo";
    private static final String TABELA = "Usuarios";
    private static final int VERSAO = 3;
    private static final String COL_ID = "id";
    private static final String COL_NOME = "nome";
    private static final String COL_EMAIL = "email";
    private static final String COL_ENDERECO = "endereco";
    private static final String COL_ENDERECO = "endereco";

    public UsuarioDAO(Context context) {
        super(context, DATABASE, null, VERSAO);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE " + TABLE + " ("
            + COL_ID + " INTEGER PRIMARY KEY, "
            + COL_NOME + " TEXT UNIQUE NOT NULL, "
            + COL_EMAIL + " TEXT, "
            + COL_ENDERECO + " TEXT, "
            + COL_TELEFONE + " TEXT"
            + ");";
      
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        String sql;

        switch(oldVersion) {
            case 1:
                sql = "ALTER TABLE " + TABLE + " ADD COLUMN " + COL_ENDERECO + " TEXT;";
                db.execSQL(sql);
            case 2:
                sql = "ALTER TABLE " + TABLE + " ADD COLUMN " + COL_TELEFONE + " TEXT;";
                db.execSQL(sql);
        }
    }

    public void inserir(Usuario usuario) {
        ContentValues values = new ContentValues();

        values.put(COL_NOME, usuario.getNome());
        values.put(COL_EMAIL, usuario.getEmail());
        values.put(COL_ENDERECO, usuario.getEndereco());
        values.put(COL_TELEFONE, usuario.getTelefone());

        getWritableDatabase().insert(TABELA, null, values);
    }

    public List<Usuario> getLista() {
        List<Usuario> lista = new ArrayList<>();

        String sql = "SELECT * FROM " + TABELA + ";";
        Cursor c = getReadableDatabase().rawQuery(sql, null);

        Usuario usuario = null;

        while(c.moveToNext()) {
            usuario = new Usuario();

            usuario.setId(c.getString(c.getColumnIndex(COL_ID)));
            usuario.setNome(c.getString(c.getColumnIndex(COL_NOME)));
            usuario.setEmail(c.getString(c.getColumnIndex(COL_EMAIL)));
            usuario.setEndereco(c.getString(c.getColumnIndex(COL_ENDERECO)));
            usuario.setTelefone(c.getString(c.getColumnIndex(COL_TELEFONE)));

            lista.add(usuario);
        }

        return lista;
    }

    public void deletar(Usuario usuario) {
        String[] argumentos = {usuario.getId().toString()};
        getWritableDatabase().delete(TABELA, COL_ID + "=?", argumentos);
    }

    public void alterar(Usuario usuario) {
        ContentValues values = new ContentValues();
        values.put(COL_NOME, usuario.getNome());
        values.put(COL_EMAIL, usuario.getEmail());
        alues.put(COL_TELEFONE, usuario.getTelefone());

        String[] argumentos = {usuario.getId().toString()};
        getWritableDatabase().update(TABELA, values, COL_ID + "=?", argumentos);
    }
}


That's all! ^^

quarta-feira, 7 de dezembro de 2016

Hello World em JSF2 no Eclipse passo a passo

Usou-se eclipse neon.1 e tomcat 8 para a criação deste hello world.

1) Crie um Dynamic web project (File -> New -> Dynamic web project).

O nome do projeto será JSF2HelloWorld. Já selecione o "Apache Tomcat v8.0" em Target runtime. Vá dando next até não poder mais. Na última tela marque a checkbox "Generate web.xml deployment descriptor". Clique em finish e o projeto estará criado.

Outra forma de adiconar o tomcat seria clicando com o botão direito em cima do projeto, indo em "Properties" > "Java Build Path" > "Add Library" > Selecionar "Server Runtime" e dar next e, então selecionaria o Tomcat. Mas para que ele possa aparecer em "Server Runtime" é necessário que ele tenha antes sido baixado da internet, seja descompactado (não precisa instalá-lo) e que seja indicado o seu caminho na aba "Servers" do Eclipse Java EE.

2) Baixando e configurando o JSF2.

Clique com o botão direito do mouse sobre o projeto criado. Vá em "Properties" e depois em "Project facets". Marque o checkbox "JavaServer Faces". Irá aparece abaixo "Further configuration required...". Clique nele. Ao abrir a tela "Modify Faceted Project" clique no botão Download library que tem o desenho de um disquete. Faça o download da JSF Mojarra, que é o criado pela Oracle e que serve de referência para as demais. Feito o download, coloque no URL Mapping Patterns: *.jsf e clique em OK.

Perceba agora que em seu projeto foi criado um arquivo chamado "faces-config.xml" e o seu "web.xml" foi criado com o seguinte código:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>JSF2HelloWorld</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>
  <context-param>
    <description>State saving method: 'client' or 'server' (=default). See JSF Specification 2.5.2</description>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>
  <context-param>
    <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
    <param-value>resources.application</param-value>
  </context-param>
  <listener>
    <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
  </listener>
</web-app>


3) Criando um arquivo xhtml

O JSF2 é utilizado junto com xhtml. Por isso, vamos criar um "helloWorld.xhtml". Clique com o botão direito sobre "WebContent" > "New" > "HTML File". Ponha o nome do arquivo de "helloWorld.xhtml", dê um "next" e escolha um template "New XHTML File (1.0 strict)" e clique em finish. Cole no seu xhtml o código abaixo:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:c="http://java.sun.com/jsf/core"
      xmlns:ui = "http://java.sun.com/jsf/facelets"
      xmlns:h = "http://java.sun.com/jsf/html">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Insert title here</title>
</head>
<h:body>

    <h:outputLabel value="Hello World"></h:outputLabel>   

</h:body>
</html>


Para que esse seu "helloWorld.xhtml" seja a primeira página a aparecer ao executar o projeto é necessário fazer uma pequena modificação no seu arquivo web.xml. Basta modificar o conteúdo da sua tag welcome-file, como no código abaixo:

  <display-name>JSF2HelloWorld</display-name>
  <welcome-file-list>
    <welcome-file>helloWorld.xhtml</welcome-file>
  </welcome-file-list>


Rode a sua aplicação pelo servidor e então veja o seu resultado pelo link:
http://localhost:8080/JSF2HelloWorld/faces/helloWorld.xhtml

Se você tiver posto URL Mapping Patterns: *.jsf na hora de configurar o jsf, então o link será:
http://localhost:8080/JSF2HelloWorld/index.jsf

O hello world de JSF2 está feito. Mas, caso queira produzir algo mais elaborado é preciso ter em mente os seguinte pontos:

1) Criando as Beans

As classes do java que irão de comunicar com as páginas jsf (xhtml) deverão estar no padrão Java Bean, ou seja:

- Variáveis de instância devem ser private e só podem ser acessadas por getters e setters.
- Exemplo: se a classe tiver um variável "String nome", deve ser criado um getNome() e setNome(). Se for um boolean, deve ser usado "is" em vez de "get", como em "isLigada()" para um variável "boolean ligada", por exemplo. Seguir esse padrão Java Bean para o uso do JSF é imprescindível!
- Deve ser criado um construtor sem parâmetros.
- A classe deve implementar java.io.Serializable.

Beans gerenciados (Managed beans) são beans que pode ser acessados a partir de uma página JSF. Devem ter um nome e um escopo. Existem alguns escopos, como "@ApplicationSocoped", "@SessionScoped", "@RequestScoped", entre outros. O escopo determina o tempo de existência da instância do objeto, se a instância irá existir durante toda a aplicação, ou apenas durante a sessão ou apenas o tempo de duração de uma requisição.

2) Mapeando a classe Bean

Após criada, a classe Bean ainda não pode ser acessada, mesmo que tenha seguido todos os padrões de nomenclatura. Para isso é necessário mapeá-la. O mapeamento pode ser feito pelo arquivo "faces-config.xml" ou via anotações (annotations).

Uma opção de anotação é a @Named que pertence ao pacote javax.inject.Named. Veja o exemplo abaixo:

@Named("usuario")
public class UsuarioBean implements Serializable { ... }


Outra opção é a anotação @ManagedBean do pacote javax.faces.bean. Se nenhum parâmetro for informado, o nome utilizado será o mesmo da classe, porém com a 1ª letra minúscula. Também podemos utilizar o parâmetro name para especificar um nome qualquer, como no exemplo abaixo:

@ManagedBean(name="usuario")
public class UsuarioBean implements Serializable { ... }


Veja abaixo um exemplo:

@ManagedBean
@ViewScoped
public class UsuarioBean implements Serializable {
    private int idade;
   
    public UsuarioBean() { }
   
    public int getIdade() { return this.idade; }
    public void setIdade(int idade) { this.idade = idade; }
    public void incrementaIdade() { this.idade = this.idade + 1; }
}


<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h = "http://java.sun.com/jsf/html">
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Exemplo JSF</title>
</h:head>
<h:body>
    Idade: #{usuarioBean.idade}
    <h:form>
        <h:inputText value="#{usuarioBean.idade}" />
        <h:commandButton value="Altera" />
        <h:commandButton value="Incrementa" action="#{usuarioBean.incrementaIdade}" />   
    </h:form>
</h:body>
</html>


A expression language #{usuarioBean.idade} serve tanto para pegar o valor da idade, como para mudar. Portanto, é usado tanto como um set e um get dependendo da ação que é feita. Se for só mostrar, funciona como um get, se for para enviar um valor por meio de uma submissão de uma requisição, então funciona como um set.

A expression language #{usuarioBean.incrementaIdade} serve para utilizar o método incrementaIdade que existe no objeto.

That's all.

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.

segunda-feira, 31 de outubro de 2016

Como usar o método startActivityForResult?


O startActivityForResult funciona com a utilização de 3 métodos em conjunto:

1) startActivityForResult, vai receber uma intenção e um código identificador. Assim, ele poderá abrir uma outra activity solicitando um resultado dela.

Exemplo:
private final static  int CODIGO_IDENTIFICADOR = 1;
Intent intent = new Intent(PrimeiraActivity.this, SegundaActivity.class);
startActivityForResult(intent, CODIGO_IDENTIFICADOR);


2) setResult, vai pegar um intent que possui dados extras e associar ao resultado pelo código identificador setado pela startActivityForResult. O setResult deve estar na SegundaActivity, pois é ela que retornará os dados pedidos pela primeira.

Exemplo:

Intent intent = getIntent();
intent.putExtra("resultado", result.toString());
setResult(Activity.RESULT_OK, intent);
finish();


3) onActivityResult(), vai recuperar o resultado setado de acordo com o código identificador passado. Logicamente o onActivityResult() deve estar na PrimeiraActivity, pois é ela que irá recuperar o resultado.

Exemplo:
protected void onActivityResult(int requestCode, int resultCode, Intent data) { 
    super.onActivityResult(requestCode, resultCode, data); 
            if(requestCode == CODIGO_IDENTIFICADOR) { 
                    String message = data.getStringExtra("resultado");  
                        Result.setText("Resultado: "+message); 
           
}
 

Colocando um botão Up na ActionBar

Depois de aprender a criar uma ActionBar no post Criando e manipulando uma ActionBar no Android, agora vem uma dica rápida para colocar um botão de up (aquela setinha que faz voltar para a activity anterior) na ActionBar.

É muito simples! Basta colocar o seguinte código na Activity:

        ActionBar actionBar = getSupportActionBar();
        actionBar.setDisplayHomeAsUpEnabled(true);


Também é necessário sobrescrever o método onOptionsItemSelected:

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            finish();
        }
        return super.onOptionsItemSelected(item);
    }


Assim, se o id do item clicado for o android.R.id.home, que corresponde ao botão de up, então é dado um finish na activity para que volte à tela anterior.

 That's it.

Criando e manipulando uma ActionBar no Android

Para colocar um ícone na ActionBar é necessário 3 coisas:

1) A Activity deve herdar de AppCompatActivity;
2) Criar um xml do tipo Menu;
3) Sobrescrever os métodos:
- "onCreateOptionsMenu"
- "onPrepareOptionsMenu"
- "onOptionsItemSelected"

CRIANDO UM XML DO TIPO MENU

1) Clicar em 'res' com o botão direito do mouse e ir em 'New' e depois em 'Android Resource File';

2) Escolher um nome e por 'Menu' como o 'Resource type';

3) Por um item no código xml do menu criado, como no código abaixo:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_add"
        android:title="@string/adicionar"
        android:icon="@drawable/ic_action_add"
        app:showAsAction="always"
    />

</menu>


O valor 'always' do atributo 'showAsAction' significa que o ícone sempre será mostrado. Se escolhesse 'ifRoom', o ícone só apareceria se tivesse espaço na tela e, se escolhesse 'never', o ícone ficaria suprimido dentro de um menu de opções (overflow menu).

Cuidado! Como você está utilizando o  AppCompatActivity, o showAsAction deve ser antecedido de app:, assim como é mostrado no exemplo acima. Se, em vez de app: você utilizar o android:, ou seja, android:showAsAction="always", o ícone não será mostrado e ficará compactado em um menu, como se, em vez de "always" tivesse sido escolhido "never".

Obs. Caso não tenha a imagem de um ícone para colocar, pode-se utilizar algum dos ícones disponívels pelo próprio Android Studio da seguinte maneira: clicar com o botão direito do mouse em cima de drawble, depois indo em 'New', 'Image Asset'. Escolha o 'Icon Type' como 'Action Bar and Tab Icons'. Esse ícone é que poderá ser usado no item do código xml criado acima (android:icon="@drawable/ic_action_add");

SOBRESCREVENDO OS MÉTODOS "onCreateOptionsMenu", "onPrepareOptionsMenu" e "onOptionsItemSelected"

Implementar os métodos "onCreateOptionsMenu", "onPrepareOptionsMenu" e "onOptionsItemSelected" na Activity em que se está criando a ActionBar. Como no código a seguir:

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        menu.findItem(R.id.action_add).setVisible(true);
        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.action_add) {
            Intent intent = new Intent(MainActivity.this, CadastraPlaylistActivity.class);
            startActivity(intent);
        }
        return super.onOptionsItemSelected(item);
    }


O que faz o onCreateOptionsMenu?
Ele vai inflar o menu em xml que você criou na tela. Por isso você deve indicar o caminho dele que é R.menu.nome_do_menu.

O que faz o onPrepareOptionsMenu?
Esse método é executado sempre antes do menu ser mostrado. Ele pode ser usado para habilitar/desabilitar itens ou modificar algum conteúdo dinamicamente. No exemplo acima, o ícone "action_add" foi setado para ficar sempre visível, sem testar qualquer condição (setVisible(true)), mas é possível fazer com que ele só se torne visível após o teste de alguma condição. Por exemplo, talvez só seja interessante que o ícone apareça após o usuário entrar com alguma string, que será enviada via intent. Nesse caso, o método deveria testar se essa string está vazia ou não, caso esteja, o setVisible ficará false. Ao fazer esse teste condicional, é preciso que esses métodos sejam executados novamente, pois eles só são executados na hora em que a ActionBar está sendo carregada. Assim, caso queira que esses métodos sejam executados novamente é necessário colocar um "supportInvalidateOptionsMenu();" em alguma parte do código.

Outro exemplo, seria o caso de só mostrar um ícone na ActionBar após o preenchimento de um EditText. Poderia-se usar o  supportInvalidateOptionsMenu(), como no exemplo abaixo:

        meuEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { }

            @Override
            public void afterTextChanged(Editable editable) { supportInvalidateOptionsMenu(); }
        });


O que faz o onOptionsItemSelected?
É a ação que ocorre após o ícone da ActionBar ser clicado. No exemplo acima, após o ícone ser clicado, ele verifica se é o ícone que eu estou pensando que é (R.id.action.add). Caso seja, por meio da intent, ele sai da MainActivity e vai para outra activity chamada CadastraPlaylistActivity.

quarta-feira, 12 de outubro de 2016

Intent-filter para link compartilhado

Você está usando um browser em seu celular e deseja compartilhar o link do site que está vendo. Como fazer com que seu aplicativo receba esse link e trate ele para ser usado dentro da aplicação? Basta criar a seguinte intent-filter dentro de seu AndroidManifest.xml:

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
            </intent-filter>
        </activity>


Isso fará com que a sua activity, no exemplo .MainActivity, enxergue uma intenção de envio no formato texto. Após isso, como receber essa informação e utilizá-la dentro da aplicação? Basta criar um intent e verificar se ela se refere a uma ACTION_SEND. Caso seja, pegue as informações com o método getStringExtra passando como parâmetro Intent.EXTRA_SUBJECT para pegar o título da página e Intent.EXTRA_TEXT para pegar o texto compartilhado que, no caso, é o link da página que está sendo compartilhado:

        Intent meulink = getIntent();
        if(savedInstanceState == null && meulink != null) {
            if(meulink.getAction().equals(Intent.ACTION_SEND)) {
                String mensagem = meulink.getStringExtra(Intent.EXTRA_SUBJECT);
                mensagem += "\n\n";
                mensagem += meulink.getStringExtra(Intent.EXTRA_TEXT);
                textView.setText(mensagem);
            }
        }

terça-feira, 11 de outubro de 2016

Enviando RadioButton escolhido para outra Activity

Criei um app com duas activities. Na main activity, há um radio group com dois radio buttons dentro. Na segunda activity, é necessário receber a informação da main activity dizendo qual radio button foi escolhido. Assim, ao clicar em um botão da main activity, ela enviar uma string que caracteriza qual radio button foi marcado. Veja o código como ficou:

    botaoCalcular.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int idRadioButtonEscolhido = radioGroup.getCheckedRadioButtonId();
                String str = "";

                if (idRadioButtonEscolhido > 0) {
                    switch (idRadioButtonEscolhido) {
                        case R.id.radioButton1Id:
                            str = "button1Text";
                            break;
                        case R.id.radioButton2Id:
                            str = "button2Text";
                            break;
                    }

                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.setPackage(getPackageName());
                    intent.putExtra("opcao", str);
                    startActivity(intent);

                } else {
                    Toast.makeText(MainActivity.this, "Escolha uma opção.", Toast.LENGTH_SHORT).show();
                }
            }
        });


Assim, a outra activity pode usar a "opcaoEscolhida" como bem quiser, recuperando essa informação da seguinte maneira:

    Bundle extra = getIntent().getExtras();

        if(extra != null) {
            opcaoEscolhida = extra.getString("opcao");
        }

sexta-feira, 7 de outubro de 2016

Aprendendo Collections Framework

Para entender como as principais estruturas de dados já implementadas na API Java funcionam, escrevi o seguinte código:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

public class Colecoes {
    //Método que remove strings duplicadas de uma Lista de Strings
    public static List<String> removeDuplicates(List<String> lista) {
        List<String> listaCopy = new ArrayList<String>();
        List<String> posicoesCopy = new ArrayList<String>();
       
        for(int i = 0; i < lista.size(); i++) {
            listaCopy.add(lista.get(i));
        }
       
        for(int i = 0; i < lista.size(); i++) {
            for(int j = 0; j < listaCopy.size(); j++) {
                if(lista.get(i).equals(listaCopy.get(j)) && j > i) posicoesCopy.add(String.valueOf(j));
            }
        }
       
        for(int i = 0; i < posicoesCopy.size(); i++) {
            lista.remove(Integer.parseInt(posicoesCopy.get(i)));
        }
       
        return lista;
    }

    public static void main(String[] args) {
        List<String> frutaArrayList = new ArrayList<String>();
        Set<String> frutaTreeSet = new TreeSet<String>();
        HashMap<String, String> frutaHashMap = new HashMap<String, String>();
        List<String> frutaLinkedList = new LinkedList<String>();
        Set<String> frutaHashSet = new HashSet<String>();
        LinkedHashMap<String, String> frutaLinkedHashMap = new LinkedHashMap<String, String>();
       
        String[] frutas = {"banana", "morango", "abacaxi", "damasco",
                "abacaxi", "banana", "abacate"
        };
       
        for(int i = 0; i < frutas.length; i++) {
            frutaArrayList.add(frutas[i]);
            frutaTreeSet.add(frutas[i]);
            frutaHashMap.put(String.valueOf(i), frutas[i]);
            frutaLinkedList.add(frutas[i]);
            frutaHashSet.add(frutas[i]);
            frutaLinkedHashMap.put(String.valueOf(i), frutas[i]);
        }
       
        System.out.println("ArrayList: "+frutaArrayList);
        System.out.println("TreeSet: "+frutaTreeSet);
        System.out.println("HashMap: "+frutaHashMap);
        System.out.println("LinkedList: "+frutaLinkedList);
        System.out.println("HashSet: "+frutaHashSet);
        System.out.println("LinkedHashMap: "+frutaLinkedHashMap);
       
        System.out.println("\nRemovendo duplicadas da ArrayList: ");
        System.out.println(removeDuplicates(frutaArrayList));
    }
}

Como resultado da execução do código acima, obtive a seguinte saída:

ArrayList: [banana, morango, abacaxi, damasco, abacaxi, banana, abacate]
TreeSet: [abacate, abacaxi, banana, damasco, morango]
HashMap: {0=banana, 1=morango, 2=abacaxi, 3=damasco, 4=abacaxi, 5=banana, 6=abacate}
LinkedList: [banana, morango, abacaxi, damasco, abacaxi, banana, abacate]
HashSet: [banana, morango, abacaxi, abacate, damasco]
LinkedHashMap: {0=banana, 1=morango, 2=abacaxi, 3=damasco, 4=abacaxi, 5=banana, 6=abacate}

Removendo duplicadas da ArrayList:
[banana, morango, abacaxi, damasco, abacate]

Com isso, pude aprender que as estruturas do tipo 'Set' eliminam as palavras duplicadas automaticamente, sendo que a TreeSet, além de fazer isso, já põe em ordem alfabética. Também aprendi que não se pode percorrer uma estrutura do tipo 'Set', pois ela não possui um método get(). A HashSet trocou de posição as palavras 'abacate' e 'damasco', o que demonstra que estruturas do tipo 'Set' não respeitam a ordem de adição, organizando os objetos de qualquer maneira ou em ordem alfabética, no caso da TreeSet.

É possível ainda melhorar o método "removeDuplicates", pois a lista está sendo percorrida várias vezes desnecessariamente. Assim, se essa lista fosse muito grande, isso causaria lentidão no processamento. Uma maneira de melhoria pode ser vista logo abaixo:

public static List<String> removeDuplicates(List<String> lista) {
        List<String> result = new ArrayList<String>();
        Set<String> itemsSet = new HashSet<>();
   
        for(int i = 0; i < lista.size(); i++) {
            String s = lista.get(i);
            if (!itemsSet.contains(s)) {
                itemsSet.add(s);
                result.add(s);
            }
        }
        return result;
}

O segredo está no uso de um 'Set', no caso, o HashSet itemsSet, pois, com ele, não é preciso percorrer a lista para encontrar o que se quer. Ele vai diretamente ao ponto por meio de uma tabela hash e verifica com o método contains se existe ou não determinado elemento. Isso evita aquele aninhamento desnecessário de loops. Além disso, impede-se que elementos duplicados sejam adicionados a nova lista (result), pois a adição só ocorre se não existir o item analisado dentro de itemsSet. 

segunda-feira, 3 de outubro de 2016

Ícones gratuitos para projetos Android

A google disponibiliza ícones gratuitos para serem usados no desenvolvimento de seus aplicativos Android, basta ir no link Material icons.

Clique sobre um dos ícones, escolha a cor, tamanho e formato e faça o download. Assim, será baixado um arquivo compactado com diversos tamanhos já adequadamente organizados e prontos para utilização em seu projeto. Vá na pasta "android" e lá estará as seguintes pastas:

drawable-xxxhdpi
drawable-xxhdpi
drawable-xhdpi
drawable-mdpi
drawable-hdpi

Depois de feito o download, visualize o seu projeto no Android Studio por meio de "Project" e cole essas pastas dentro de "res" (src > main > res).

Dica de uso dos ícones: baixe sempre os ícones com a cor branca, depois mude-a via Android Studio utilizando a propriedade "tint" no arquivo .xml. 

quarta-feira, 28 de setembro de 2016

Fatorial e Fibonacci com Recursividade em Java

Para treinar recursividade em Java, fiz dois programinhas. O primeiro calcula o fatorial de um número digitado pelo usuário e o segundo imprime a sequência de Fibonacci do tamanho solicitado também pelo usuário. Seguem os códigos logo abaixo.

//Desenvolva um algoritmo que calcule o fatorial de um número n.
import java.util.Scanner;

public class FatorialRecursivo {
   
    //Método que calcula fatorial de forma recursiva
    static int Fatorial(int numero) {
        if(numero > 1)
            return (numero * Fatorial(numero - 1));
        else
            return numero;
    }

    //Testando o método Fatorial()
    public static void main(String[] args) {
        Scanner leitor = new Scanner(System.in);
        System.out.print("Digite um número inteiro cujo fatorial deseje calcular: ");
        int numero = leitor.nextInt();
        System.out.println(numero+"! = "+Fatorial(numero));
        leitor.close();
    }
}



/***************************************************
 * Desenvolva um algoritmo que calcule o n-ésimo termo de uma série de fibonacci.
 * Série de fibonacci: 1, 1, 2, 3, 5, 8, 13, 21, 34 . . .
 * OBS.: O n-ésimo termo é obtido a partir dos dois anteriores.
***************************************************/


import java.util.Scanner;

public class FibonacciRecursivo {
   
    //Método que imprime sequência de Fibonacci recursivamente   
    static int Fibonacci(int quantidade, int i, int j) {
        if(quantidade > 0) {
            System.out.print(j+" ");
            return Fibonacci(quantidade - 1, j, i+j);
        } else {
            return 1;
        }
    }

    public static void main(String[] args) {
        Scanner leitor = new Scanner(System.in);
        System.out.print("Quantidade de número na sequência de Fibonacci: ");
        Fibonacci(leitor.nextInt(), 0, 1);
        leitor.close();
    }
}

Error parsing XML: must not undeclare prefix

Ao atualizar meu Android Studio para a versão 2.2, acabo me deparando com o seguinte erro:

Error parsing XML: must not undeclare prefix

Ele aponta esse erro para a linha destacada abaixo:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:app2=""
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"


Entretanto, percebo que o erro se encontra no acréscimo automático da seguinte linha:

xmlns:app2=""

Ao excluí-la, tenho meu aplicativo emulado corretamente sem erros. Não sei por que o Android Studio 2.2 acrescentou essa linha, mas espero que esta postagem possa ajudar a alguém. ^^

segunda-feira, 26 de setembro de 2016

Qual é o processo de geração de um aplicativo android?


A máquina virtual para Android (Dalvik) não gera bytecodes e sim arquivos .dex que são gerados a partir das classes java compiladas. Essa conversão é feita pela ferramenta dx que vem com o Android SDK. O arquivo .apk é criado a partir da junção do arquivo .dex com os demais recursos do projeto, como imagens e ícones.