Mostrando postagens com marcador android. Mostrar todas as postagens
Mostrando postagens com marcador android. Mostrar todas as postagens

terça-feira, 7 de fevereiro de 2017

Solicitar e Tratar Permissões para API 23 ou superior

A partir da API 23 (Android Marshmallow), o usuário tem o controle de aceitar individualmente as permissões que o aplicativo solicita. Portanto, além de colocar as uses-permission no manifest, é necessário solicitar todas as permissões individualmente ao usuário. Para isso, temos que criar um código parecido com o que está abaixo:

//Crie um array com as permissões necessárias
private String[] permissoesNecessarias = new String[] {
            android.Manifest.permission.SEND_SMS,
            android.Manifest.permission.INTERNET
};


//Crie uma classe para checar se as permissões foram aceitas
public class Permissao {

    public static boolean validaPermissoes(int requestCode, Activity activity, String[] permissoes) {

        if(Build.VERSION.SDK_INT >= 23) {
            List<String> listaPermissoes = new ArrayList<>();


            //Percorre as permissões passadas, verificando uma a uma se já tem a permissão liberada
            for (String permissao : permissoes) {
                Boolean validaPermissao = ContextCompat.checkSelfPermission(activity, permissao) == PackageManager.PERMISSION_GRANTED;
                if(!validaPermissao) listaPermissoes.add(permissao);
            }

            if(listaPermissoes.isEmpty()) return true;


            //Solicita permissão
            ActivityCompat.requestPermissions(activity, listaPermissoes.toArray(new String[0]), requestCode);
        }

        return true;
    }
}



//Gera AlertDialog para avisar que o Usuário só pode usar o App se aceitar as permissões
private void alertaValidacaoPermissao() {

    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle("Permissões negadas");
    builder.setMessage("Para utilizar este app, é necessário aceitar as permissões");

    builder.setPositiveButton("CONFIRMAR", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialogInterface, int i) {
            finish();
        }
    });

    AlertDialog dialog = builder.create();
    dialog.show();
}

 
//Tratar Negação de Permissão
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResult) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResult);
    for(int resultado : grantResult) {
        if(resultado == PackageManager.PERMISSION_DENIED) {
            alertaValidacaoPermissao();
        }
    }
}

segunda-feira, 6 de fevereiro de 2017

MaskFormatter: biblioteca Android para formatar Strings usando Máscaras

Para formatar e limitar uma entrada em um EditText, como datas e números de telefone, basta usar a classe MaskTextWatcher e SimpleMaskFormatter. Mas, antes de usá-las, é necessário adicionar a biblioteca colando o código a seguir em seu build.gradle (Module: app):

compile 'com.github.rtoshiro.mflibrary:mflibrary:1.0.0'

Exemplo de uso:

private EditText telefone;
telefone = (EditText) findViewById(R.id.edit_telefone);

// Crie uma máscara de formatação de string, 'N' para representar apenas números
SimpleMaskFormatter simpleMaskTelefone = new SimpleMaskFormatter("(NN) NNNN-NNNN");
MaskTextWatcher maskTelefone = new MaskTextWatcher(telefone, simpleMaskTelefone);
telefone.addTextChangedListener(maskTelefone);


Dessa maneira o telefone será formato como, por exemplo: (88) 9956-1517

A classe SimpleMaskFormatter vem com 5 padrões pré-definidos:
N - para números.
L - para letras.
L - para números e letras.
l - para letras minúsculas.
U - para letras maiúsculas.

Também é possível customizar os nossos próprios formatos. Veja o exemplo abaixo:

// permite apenas os números 0, 1, 2 e 3.
MaskPattern mp03 = new MaskPattern("[0-3]");
// permite os números de 0 a 9.
MaskPattern mp09 = new MaskPattern("[0-9]");
// permite apenas os números 0 e 1.
MaskPattern mp01 = new MaskPattern("[0-1]");

// Cria-se então uma nova máscara
MaskFormatter mf = new MaskFormatter("[0-1][0-9]/[0-3][0-9]/[0-9][0-9][0-9][0-9]");

// Registra-se os padrões no objeto MaskFormatter
mf.registerPattern(mp01);
mf.registerPattern(mp03);
mf.registerPattern(mp09);


Para maiores informações, basta acessar o link dessa biblioteca no GitHub:
https://github.com/rtoshiro/MaskFormatter

Espero ter ajudado! =)

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! ^^

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.

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");
        }

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

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.