Migrando Para o AndroidX e Construindo a Galeria Para Foto de Perfil - Android M-Commerce
(5764)
CategoriasAndroid, Design, Protótipo
AutorVinÃcius Thiengo
VÃdeo aulas186
Tempo15 horas
ExercÃciosSim
CertificadoSim
CategoriaEngenharia de Software
Autor(es)Vaughn Vernon
EditoraAlta Books
Edição1ª
Ano2024
Páginas160
Tudo bem?
Neste artigo vamos finalizar os trabalhos de interface na tela de formulário de perfil de usuário do projeto Android BlueShoes, nosso aplicativo de mobile-commerce.
Aqui chegamos também ao ponto de migrar o projeto da Support Library para o pacote AndroidX, isso principalmente devido a API de galeria que estaremos utilizando, mas de qualquer forma: "esse dia de mudança para o AndroidX iria chegar, cedo ou tarde".
Antes de prosseguir, não deixe de se inscrever 📫na lista de emails do Blog para ter acesso exclusivo às novas aulas do projeto e também a outros conteúdos exclusivos do Blog.
A seguir os pontos abordados:
- Iniciando no projeto Android BlueShoes;
- Estratégia para a tela de galeria de imagens e câmera:
- AndroidX:
- O que é?;
- Por que migrar o código agora?;
- Primeiros passos para a migração;
- Ajustes necessários de migração:
- Atualização do gradle.properties;
- Atualização do Gradle Nível de Aplicativo;
- Atualização do Gradle Nível de Projeto;
- Atualização da classe utilitária NavMenuItemDetails;
- Atualização da classe utilitária NavMenuItemDetailsLookup;
- Atualização da classe NavMenuItemKeyProvider;
- Atualização da classe NavMenuItemPredicate;
- Atualização da atividade abstrata FormActivity;
- Atualização da atividade FormEmailAndPasswordActivity;
- Atualização da atividade de login;
- Atualização da atividade de cadastro de novo usuário;
- Atualização da atividade de itens de configuração de conta;
- Atualização do adapter da AccountSettingsActivity;
- Atualização da atividade de formulário de perfil de usuário;
- Atualização do fragmento Sobre;
- Atualização do fragmento de Contato;
- Atualização do fragmento de Políticas de Privacidade;
- Atualização do adapter de itens de menu gaveta;
- Atualização da atividade principal.
- Trabalhando a tela de galeria e de câmera:
- Por que a ImagePicker API foi escolhida?;
- Atualizando o Gradle de Nível de Projeto;
- Atualizando o Gradle de Nível de Aplicativo;
- Adicionando permissões ao AndroidMenifest;
- Arquivo de Strings;
- Sobrescrevendo estilos da ImagePicker API;
- O novo campo de imagem do formulário de perfil;
- Configurando a galeria e a câmera.
- Testes e resultados;
- Vídeos;
- Conclusão;
- Fontes.
Iniciando no projeto Android BlueShoes
Se você estiver tendo somente agora o primeiro contato com o projeto Android BlueShoes, nosso projeto de mobile-commerce, saiba que é preciso primeiro consumir algumas outras aulas para depois poder prosseguir com está 14ª aula do projeto.
Seguem as aulas:
- 1ª aula - Android Mobile-Commerce, Apresentação e Protótipo do Projeto;
- 2ª aula - Início de Projeto e Menu Gaveta Customizado - Android M-Commerce;
- 3ª aula - Fragmento da Tela Sobre e Links Sociais - Android M-Commerce;
- 4ª aula - Localização com Rota GPS, E-mail e Telefones - Android M-Commerce;
- 5ª aula - Políticas de Privacidade e Porque não a GDPR - Android M-Commerce;
- 6ª aula - Login com ConstraintLayout e TextWatcher Para Validação - Android M-Commerce;
- 7ª aula - Refatoração do Login, Pavimentando o Caminho Para Outros Formulários - Android M-Commerce;
- 8ª aula - Como Criar a Tela de Recuperação de Acesso - Android M-Commerce;
- 9ª aula - Criando a Tela de Cadastro de Usuário - Android M-Commerce;
- 10ª aula - Aplicando o Padrão Template Method Para Limpar o Login e o Cadastro - Android M-Commerce;
- 11ª aula - Como Criar a UI de Configurações de Conta de Usuário - Android M-Commerce;
- 12ª aula - Entendendo o Bug do Menu, Link de Cadastro e Parcelize - Android M-Commerce;
- 13ª aula - Construindo o Formulário de Atualização de Perfil - Android M-Commerce.
Ressaltando que as aulas são liberadas semanalmente para a lista de e-mails 📩 do Blog, logo, não esqueça de se inscrever nela, é gratuito.
Estratégia para a tela de galeria de imagens e câmera
Apesar de o desenvolvimento da tela de galeria e câmera ser simples, pois vamos utilizar uma API especifica para isso, ainda será preciso bastante atualização em projeto, principalmente devido à migração para o AndroidX.
Vamos prosseguir com o seguinte roteiro:
- Primeiro vamos a migração de todo o código para ele passar a utilizar o pacote AndroidX;
- Em seguida vamos a configuração das partes estáticas de projeto que serão utilizadas, ou fazem parte da configuração, da área de galeria e câmera;
- Por fim vamos às configurações em código dinâmico para que seja possível utilizar a API responsáveis pelo correto trabalho com a galeria de imagens e câmera.
Lembrando que o projeto Android BlueShoes está disponível no repositório dele em: https://github.com/viniciusthiengo/blueshoes-kotlin-android.
Protótipo estático
A seguir o protótipo estático da área de galeria de imagens e de câmera do formulário de perfil de usuário:
Galeria de fotos do aparelho | Área de fotografia |
AndroidX
Antes de partirmos para as explicações mais detalhadas sobre o AndroidX é importante informar que o processo de migração do código do projeto, da conhecida Support Library, para o AndroidX é algo que em algum momento do desenvolvimento nós teríamos de fazer.
O momento chegou, pois uma das APIs úteis em projeto necessita que ele já esteja utilizando o AndroidX para ela funcionar como esperado.
O que é?
O AndroidX é, resumidamente, a melhoria do pacote Support Library. E como a Support Library, o AndroidX é separado do sistema operacional (SO) Android e, ainda melhor:
- Ele é compatível com versões antigas do Android, como a Support Library;
- As atualizações de pacotes internos agora é independente, algo que não ocorria na Support Library.
O AndroidX é o local onde se encontram as bibliotecas do Jetpack Android. O Jetpack é um conjunto de componentes Android, também separados do Android SO, que buscam facilitar o desenvolvimento de aplicativos nesta plataforma, permitindo ao desenvolvedor maior foco em código de domínio de problema.
Um informe importante sobre a necessidade de migrar projetos Android para o AndroidX, informe direto da documentação oficial:
Todo novo desenvolvimento da Support Library ocorrerá na biblioteca do AndroidX. Isso inclui a manutenção dos artefatos originais da Support Library e a introdução de novos componentes do Jetpack.
Por que migrar o código agora?
Para trabalho com a galeria de imagens e câmera, para permitir que o usuário do aplicativo facilmente escolhesse uma imagem de perfil, fomos atrás de uma API adequada para está funcionalidade.
A mais consistente, também em relação ao layout definido em protótipo estático, completa e utilizando a Apache License 2.0 é uma API que já exige em projeto o trabalho com o AndroidX, caso contrário ela simplesmente não funciona.
Bom, vamos falar a verdade, se trabalhássemos um pouco mais seria sim possível utilizar essa API de seleção de imagem ainda com o projeto utilizando a Support Library, mas sabendo que a migração para a AndroidX ocorreria, uma hora ou outra, então escolhemos o caminho certo e que seria inevitável.
Primeiros passos para a migração
Felizmente o Google Android têm todo o conteúdo de migração de um projeto Android Support Library para um projeto Android com AndroidX, incluindo todo o conteúdo em português.
Confesso que a migração automática, utilizando o Android Studio, é "meia boca", mas já adianta muito do trabalho manual que será necessário.
Importante: Antes de partirmos para a migração automática é preciso que o SDK de compilação do projeto (compileSdkVersion) seja colocado na versão 28 (Pie) ou superior do Android. Essa atualização ocorre no Gradle Nível de Aplicativo (build.gradle (Module: app)). Isso, pois o AndroidX é compatível a partir deste SDK em compilação.
Assim, com o Android Studio aberto (assumindo que você está com uma versão deste IDE igual ou superior a versão 3.2) acesse o menu de topo:
- Clique em Refactor;
- Logo depois clique em Migrate to AndroidX....
Por segurança, aceite realizar o backup do projeto em um arquivo ZIP:
Por fim clique em Migrate.
Please, you do not be so happy! Sim, não fique feliz achando que esses poucos cliques foram o suficiente. A precisão do Android Studio ainda não é das melhores, ao menos na versão 3.4.1 deste IDE (que é a versão que eu estou utilizando no momento da construção desta parte do projeto), pois teremos de migrar alguns trechos na "unha".
Ajustes necessários de migração
Os ajustes extras vão vir com auxílio da página de migração para o AndroidX: Como migrar para o AndroidX.
Vamos primeiro aos arquivos de configuração do projeto, depois vamos por classes e layouts, se houver algum layout vinculado a classe em mudança.
A partir deste ponto nós vamos verificar as modificações necessárias em projeto. Caso em seu aplicativo, depois da migração automática ainda falte alguma importação ou referência apresentada nos próximos tópicos, então atualize como apresentado aqui.
Note que em muitos conjuntos de importação apresentados haverá o nome de pacote thiengo.com.br.blueshoes, pois este é o package name de minha versão do projeto BlueShoes, em seu caso certamente será outro nome, sendo assim, nessas partes, continue com o seu nome de pacote.
Atualização do gradle.properties
No arquivo gradle.properties (propriedades de projeto) adicione as duas linhas a seguir caso elas já não estejam presentes:
...
android.useAndroidX=true
android.enableJetifier=true
A função de cada uma destas configurações é:
- android.useAndroidX: quando definida como true, o plug-in do Android usa a biblioteca do AndroidX adequada em vez de uma Support Library. Por padrão o valor é false;
- android.enableJetifier: quando definida como true, o plug-in do Android migra automaticamente as bibliotecas de terceiros existentes para usar o AndroidX, reescrevendo os respectivos binários. Por padrão o valor é false.
Atualização do Gradle Nível de Aplicativo
No bloco dependencies do Gradle Nível de Aplicativo, build.gradle (Module: app), certifique-se de que as dependências de projeto estão como abaixo, ao menos os trechos em destaque, com o pacote AndroidX sendo utilizado, caso contrário (alguns poderão não estar) atualize para que fique:
...
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
/* SelectionTracker API */
implementation 'androidx.recyclerview:recyclerview-selection:1.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
/* RoundedImageView API */
implementation 'com.makeramen:roundedimageview:2.3.0'
/* AndroidUtilCode API */
implementation 'com.blankj:utilcode:1.23.7'
}
...
Note que todas as referências que continham com.android.support foram atualizadas para referências androidx, mas, diferente do que a documentação de migração para o AndroidX apresenta, isso não é padrão.
Ainda não há a necessidade de sincronização do projeto, temos de atualizar / verificar mais pontos do código do app.
Atualização do Gradle Nível de Projeto
Apesar de não ser uma atualização devido ao uso do AndroidX, neste ponto do projeto foi liberada uma atualização da versão do Kotlin.
Importante: não deixe de aplicar todas as atualizações que o Android Studio IDE indica assim que ele é inicializado.
No Gradle Nível de Projeto, build.gradle (Project: BlueShoes), atualize o ext.kotlin_version como a seguir:
buildscript {
ext.kotlin_version = '1.3.40'
...
}
...
Ainda não sincronize o projeto.
Atualização da classe utilitária NavMenuItemDetails
A configuração de imports da classe NavMenuItemDetails agora é como a seguir:
...
import android.view.MotionEvent
import androidx.recyclerview.selection.ItemDetailsLookup
import thiengo.com.br.blueshoes.domain.NavMenuItem
...
Note que, em todo o artigo, o que foi atualizado estará em destaque.
Atualização da classe utilitária NavMenuItemDetailsLookup
A seguir a nova configuração de imports da classe NavMenuItemDetailsLookup:
...
import android.view.MotionEvent
import androidx.recyclerview.selection.ItemDetailsLookup
import androidx.recyclerview.widget.RecyclerView
import thiengo.com.br.blueshoes.view.NavMenuItemsAdapter
...
Importante ressaltar que em toda esta atualização de projeto para o AndroidX somente alguns imports e algumas referências em layout é que serão alteradas, os códigos dinâmicos continuarão sendo os mesmos.
Atualização da classe NavMenuItemKeyProvider
Abaixo a nova configuração de importações da classe NavMenuItemKeyProvider:
...
import androidx.recyclerview.selection.ItemKeyProvider
import thiengo.com.br.blueshoes.domain.NavMenuItem
...
Atualização da classe NavMenuItemPredicate
Assim a configuração de importações da classe NavMenuItemPredicate:
...
import androidx.recyclerview.selection.SelectionTracker
import thiengo.com.br.blueshoes.data.NavMenuItemsDataBase
import thiengo.com.br.blueshoes.view.MainActivity
...
Atualização da atividade abstrata FormActivity
Agora, já no pacote /view (em meu projeto é /view, em alguns outros é /ui), vamos a atualização da configuração de imports da atividade ancestral de atividades que contêm formulário, FormActivity.
Deixe as importações desta atividade como a seguir:
...
import android.graphics.Color
import android.graphics.PorterDuff
import android.os.Bundle
import android.os.SystemClock
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ImageSpan
import android.view.KeyEvent
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.content_form.*
import kotlinx.android.synthetic.main.proxy_screen.*
import thiengo.com.br.blueshoes.R
...
Note que no caso do componente SnackBar o import era android.support.design.widget.Snackbar e agora é com.google.android.material.snackbar.Snackbar, e é isso mesmo, sem o androidx como parte do namespace, mas não deixa de se parte do AndroidX.
A FormActivity já é uma das classes que referencia também a layouts. Com isso vamos a verificação (e a atualização, caso não tenha ocorrido no processo de migração automática do Android Studio) destes layouts.
Os trechos atualizados para a versão AndroidX estarão em destaque.
Primeiro o layout /res/layout/activity_form.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
...>
...
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Agora o layout de barra de topo /res/layout/app_bar.xml:
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.AppBarLayout
...>
<androidx.appcompat.widget.Toolbar
.../>
</com.google.android.material.appbar.AppBarLayout>
AppBarLayout é outra das novas referências que não utiliza como parte do namespace o termo androidx.
E por fim o layout de bloco de conteúdo /res/layout/content_form.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
...>
...
</androidx.core.widget.NestedScrollView>
Note que dentro dos conjuntos de layouts apresentados há outros layouts, porém eles continuam com a mesma configuração de quando utilizando a Support Library, logo, nós não os mostraremos aqui, pois não há necessidade.
Atualização da atividade FormEmailAndPasswordActivity
Ainda no contexto de formulários, vamos a atualização de outra classe abstrata, a FormEmailAndPasswordActivity. As importações ficam como a seguir:
...
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.blankj.utilcode.util.KeyboardUtils
import kotlinx.android.synthetic.main.text_view_privacy_policy_login.*
import thiengo.com.br.blueshoes.R
...
Apesar de ser uma atividade, a FormEmailAndPasswordActivity não trabalha diretamente com nenhum arquivo de layout.
Atualização da atividade de login
Para a LoginActivity as importações agora ficam como a seguir:
...
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.blankj.utilcode.util.ActivityUtils
import com.blankj.utilcode.util.ScreenUtils
import kotlinx.android.synthetic.main.content_login.*
import thiengo.com.br.blueshoes.R
import thiengo.com.br.blueshoes.util.isValidEmail
import thiengo.com.br.blueshoes.util.isValidPassword
import thiengo.com.br.blueshoes.util.validate
...
No layout desta atividade, /res/layout/content_login.xml, somente a referência a ConstraintLayout é que é atualizada:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
...>
...
</androidx.constraintlayout.widget.ConstraintLayout>
Novamente: os layouts referenciados internamente e que não têm necessidade de atualizações não serão citados.
Atualização da atividade de cadastro de novo usuário
Para a SignUpActivity as importações ficam como abaixo:
...
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.blankj.utilcode.util.ActivityUtils
import com.blankj.utilcode.util.ScreenUtils
import kotlinx.android.synthetic.main.content_sign_up.*
import thiengo.com.br.blueshoes.R
import thiengo.com.br.blueshoes.util.isValidEmail
import thiengo.com.br.blueshoes.util.isValidPassword
import thiengo.com.br.blueshoes.util.validate
...
E o único trecho de atualização no layout da atividade de cadastro, /res/layout/content_sign_up.xml, é o mesmo da atividade de login:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
...>
...
</androidx.constraintlayout.widget.ConstraintLayout>
Atualização da atividade de itens de configuração de conta
Na AccountSettingsActivity as importações ficam como a seguir:
...
import android.os.Bundle
import androidx.core.content.ContextCompat
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.content_account_settings.*
import thiengo.com.br.blueshoes.R
import thiengo.com.br.blueshoes.data.AccountSettingsItemsDataBase
import thiengo.com.br.blueshoes.domain.User
...
No layout /res/layout/activity_account_settings.xml temos a seguinte nova configuração:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
...>
...
</androidx.coordinatorlayout.widget.CoordinatorLayout>
E no layout /res/layout/content_account_settings.xml temos a seguinte nova referência em <RecyclerView>:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
...>
...
<androidx.recyclerview.widget.RecyclerView
.../>
</LinearLayout>
Ainda temos a classe adaptadora de itens do framework de lista em AccountSettingsActivity.
Atualização do adapter da AccountSettingsActivity
Em AccountSettingsItemsAdapter teremos a seguinte configuração de importações:
...
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import thiengo.com.br.blueshoes.R
import thiengo.com.br.blueshoes.domain.AccountSettingItem
import thiengo.com.br.blueshoes.domain.User
...
O layout de item, /res/layout/account_settings_item.xml, continua o mesmo como quando com a Support Library.
Atualização da atividade de formulário de perfil de usuário
Para a ConfigProfileActivity teremos a seguinte configuração de imports:
...
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.blankj.utilcode.util.KeyboardUtils
import com.blankj.utilcode.util.ScreenUtils
import kotlinx.android.synthetic.main.content_config_profile.*
import thiengo.com.br.blueshoes.R
import thiengo.com.br.blueshoes.domain.User
import thiengo.com.br.blueshoes.util.validate
...
Para o layout de ConfigProfileActivity, /res/layout/content_config_profile.xml, temos a referência a ConstraintLayout sendo atualizada:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
...>
...
</androidx.constraintlayout.widget.ConstraintLayout>
Atualização do fragmento Sobre
Agora vamos às entidades ligadas diretamente a MainActivity. Começando pelos imports do fragmento AboutFragment:
...
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_about.*
import thiengo.com.br.blueshoes.R
...
Assim a única atualização do layout deste fragmento, /res/layout/fragment_about.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
...>
...
</androidx.core.widget.NestedScrollView>
Atualização do fragmento de Contato
Agora os imports do fragmento ContactFragment:
...
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_contact.*
import kotlinx.android.synthetic.main.info_block.*
import thiengo.com.br.blueshoes.R
...
Assim, como em AboutFragment, a única atualização do layout deste fragmento, /res/layout/fragment_contact.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
...>
...
</androidx.core.widget.NestedScrollView>
Atualização do fragmento de Políticas de Privacidade
Assim os imports do fragmento PrivacyPolicyFragment:
...
import android.graphics.Typeface
import android.os.Bundle
import android.text.Annotation
import android.text.Spannable
import android.text.SpannableString
import android.text.SpannedString
import android.text.method.LinkMovementMethod
import android.text.style.ForegroundColorSpan
import android.text.style.RelativeSizeSpan
import android.text.style.StyleSpan
import android.text.style.URLSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_privacy_policy.*
import thiengo.com.br.blueshoes.R
import thiengo.com.br.blueshoes.util.CustomTypefaceSpan
...
Por fim a simples atualização do layout deste fragment, o layout /res/layout/fragment_privacy_policy.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
...>
...
</androidx.core.widget.NestedScrollView>
Atualização do adapter de itens de menu gaveta
Agora a atualização da classe adaptadora de itens de menu gaveta, a classe NavMenuItemsAdapter.
As importações desta classe ficam como a seguir:
...
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.selection.SelectionTracker
import androidx.recyclerview.widget.RecyclerView
import thiengo.com.br.blueshoes.R
import thiengo.com.br.blueshoes.domain.NavMenuItem
import thiengo.com.br.blueshoes.util.NavMenuItemDetails
...
O layout de item desta classe adaptadora continua o mesmo como antes da atualização para o AndroidX.
Atualização da atividade principal
Por fim a atualização da MainActivity. A seguir a nova configuração de imports desta classe:
...
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment
import androidx.recyclerview.selection.SelectionTracker
import androidx.recyclerview.selection.StorageStrategy
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.nav_header_user_logged.*
import kotlinx.android.synthetic.main.nav_header_user_not_logged.*
import kotlinx.android.synthetic.main.nav_menu.*
import thiengo.com.br.blueshoes.R
import thiengo.com.br.blueshoes.data.NavMenuItemsDataBase
import thiengo.com.br.blueshoes.domain.NavMenuItem
import thiengo.com.br.blueshoes.domain.User
import thiengo.com.br.blueshoes.util.NavMenuItemDetailsLookup
import thiengo.com.br.blueshoes.util.NavMenuItemKeyProvider
import thiengo.com.br.blueshoes.util.NavMenuItemPredicate
...
Assim as atualizações de layouts referenciados a partir da MainActivity e ainda não atualizados.
Primeiro o layout principal, /res/layout/activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
...>
...
</androidx.drawerlayout.widget.DrawerLayout>
Agora o layout de menu gaveta, /res/layout/nav_menu.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
...>
...
<androidx.recyclerview.widget.RecyclerView
.../>
...
<androidx.recyclerview.widget.RecyclerView
.../>
</LinearLayout>
Por fim o layout que contém a área de topo e de conteúdo da MainActivity, /res/layout/app_bar_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
...>
...
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Agora sim é um bom momento para sincronizar o projeto. Não deverá ter erros ao final da sincronia.
Trabalhando a tela de galeria e de câmera
Vamos agora ao desenvolvimento da tela de galeria, tela que terá também a responsabilidade de permitir que o usuário tire uma foto para poder coloca-la como foto de perfil.
Primeiro vamos aos trechos que não exigem lógica de negócio: explicações, configurações e arquivos estáticos. Posteriormente vamos aos códigos dinâmicos.
Por que a ImagePicker API foi escolhida?
Primeiro: trabalhar com todo o código nativo do Android para poder solicitar permissões necessárias em tempo de execução, apresentar imagens do SDCard e ainda permitir a fotografia, todo o código que representa estas necessidades tomaria muito de nosso tempo.
O nosso domínio de problema é: vendas pelo aplicativo mobile BlueShoes. Ou seja, temos de investir mais tempo no processo de apresentação e compra dos calçados.
O que temos de fazer em todos os outros pontos do aplicativo é permitir que o usuário acesse qualquer funcionalidade de maneira consistente e amigável.
Junte a isso tudo a possibilidade de o nosso código de seleção de imagens não ser tão bom quanto o código de uma API construída especificamente para isso e já utilizada por inúmeros desenvolvedores.
Com o que foi explicado nos parágrafos acima já dá para entender a importância de escolhermos uma API de terceiro para a funcionalidade de galeria do projeto.
Agora o porquê de termos escolhido a ImagePicker API é simples:
- É uma API ainda mantida pelo principal desenvolvedor dela - atualizada de acordo com as atualizações do Android SDK;
- Eu já trabalhei com ela em outros projetos, onde o funcionamento foi exatamente como esperado;
- Ela já tem código embutido para a solicitação de permissão em tempo de execução;
- A configuração e personalização da galeria é simples.
Há inúmeras outras APIs de seleção de imagens, incluindo APIs com 20 vezes mais estrelas no GitHub do que a ImagePicker API, porém com um código, em meu ponto de vista, pouco eficiente em relação ao contexto de: acesso a imagens e a câmera do aparelho.
Se quiser saber mais sobre a ImagePicker API, não deixe de acessar a documentação oficial dela (em inglês): nguyenhoanglam/ImagePicker.
Atualizando o Gradle de Nível de Projeto
Explicado o porquê de estarmos utilizando a ImagePicker API, agora vamos a integração dela ao aplicativo. No Gradle Nível de Projeto, build.gradle (Projeto: BlueShoes), adicione os trechos em destaque:
...
allprojects {
repositories {
...
maven { url "https://maven.google.com" }
maven { url "https://jitpack.io" }
}
}
...
Ainda não sincronize o projeto.
Atualizando o Gradle de Nível de Aplicativo
Agora no Gradle Nível de Aplicativo, build.gradle (Module: app), adicione as dependências em destaque:
...
dependencies {
...
/* Image Gallery e Camera API */
implementation 'com.github.nguyenhoanglam:ImagePicker:1.3.3'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
}
...
Assim, sincronize o projeto.
Adicionando permissões ao AndroidMenifest
Apesar de a documentação oficial da ImagePicker API não informar sobre a necessidade de colocarmos em AndroidManifest as permissões necessárias de acesso ao SDCard e de acesso à câmera do dispositivo, eu fortemente recomendo que você faça como em nosso projeto e sempre adicione as permissões que poderão ser solicitadas.
No AndroidManifest.xml adicione os códigos em destaque:
<?xml version="1.0" encoding="utf-8"?>
<manifest
...>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
Todas as permissões definidas acima são "dangerous permissions", mas vale ressaltar que todo o código de solicitação de permissão em tempo de execução é também administrado pela ImagePicker API.
Arquivo de Strings
Agora vamos a um dos arquivos mais editados do projeto, o arquivo de Strings, /res/values/strings.xml. Vamos adicionar rótulos e sobrescrever um especifico do layout de galeria da ImagePicker:
<resources>
...
<!-- Image Picker API - Adição e sobreposição de String -->
<string name="imagepicker_msg_no_images">
Não há imagens na galeria.
</string>
<string name="imagepicker_gallery_activity">Galeria</string>
<string name="imagepicker_cam_photos_activity">
Fotos câmera BlueShoes
</string>
<string name="imagepicker_selection_limit">
Apenas uma imagem pode ser selecionada.
</string>
</resources>
Um dos pontos negativos da API: ela não tem o arquivo /strings.xml para o português do Brasil. Sendo assim é preciso também a sobrescrita, que não deixa de ser um passo simples.
Ok, Thiengo. Mas como você sabe que os rótulos utilizados no layout da ImagePicker API são esses definidos no strings.xml do projeto?
Na verdade eu somente tive de descobrir um rótulo, o imagepicker_msg_no_images, pois a API não me dá um método para a atualização deste texto, que aparece quando não há imagens no SDCard do aparelho.
Eu encontrei este rótulo navegando pela documentação da API, pois na descrição para desenvolvedores não havia essa informação.
Todos os outros rótulos, mesmo que com o prefixo imagepicker_, foram criados por mim em conformidade com os rótulos já utilizados internamente pela API, porém estes são utilizados em código dinâmico por meio de métodos que a própria API fornece para mudança de alguns textos dentro dela.
Sobrescrevendo estilos da ImagePicker API
Sem a sobrescrita de tema o que obteríamos como barra de topo da ImagePicker API seria:
Sendo que no protótipo estático temos a seguinte exigência:
E, acredite, a sobrescrita é de um mesmo tema, imagepicker_ToolbarTitleText (também encontrado por meio da navegação do projeto no GitHub), porém em mais de um folder, para atender a diferentes versões do Android.
Em /res/values/styles.xml adicione os trechos em destaque:
<resources>
...
<!--
Sobrescrevendo o tema imagepicker_ToolbarTitleText da
ImagePicker API para que o estilo de título em barra de
topo do aplicativo BlueShoes também seja aplicado às
atividades desta API.
-->
<style
name="imagepicker_ToolbarTitleText"
parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
<item name="android:fontFamily">@font/pathway_gothic_one_regular</item>
<item name="android:textSize">22sp</item>
<item name="android:textStyle">bold</item>
</style>
</resources>
Agora em /res/values-v21/styles.xml adicione os trechos em destaque:
<resources>
...
<style
name="imagepicker_ToolbarTitleText"
parent="@android:style/TextAppearance.Material.Widget.ActionBar.Title">
<item name="android:fontFamily">@font/pathway_gothic_one_regular</item>
<item name="android:textSize">22sp</item>
<item name="android:textStyle">bold</item>
</style>
</resources>
O estilo em styles.xml v21 também foi necessário, pois a ImagePicker também tem uma versão de estilo no styles.xml v21 dela.
O novo campo de imagem do formulário de perfil
Infelizmente não é inteligente manter o uso do ImageView como campo de imagem de perfil no formulário de usuário, pois para mantermos a imagem selecionada pelo usuário com o shape arredondado será mais crítico do que se estivéssemos apenas utilizando uma RoundedImageView, API já presente em projeto.
Sendo assim, vamos às mudanças necessárias para passarmos a utilizar uma RoundedImageView no formulário de perfil.
Primeiro a imagem padrão, ou hint image, quando não há imagem nenhuma no campo. Antes tínhamos apenas um ícone de fotografia, agora todo o campo é uma imagem. Imagem que pode ser descarregada diretamente da documentação oficial de nosso projeto:
- /res/drawable-mdpi/profile_hint.png;
- /res/drawable-hdpi/profile_hint.png;
- /res/drawable-xhdpi/profile_hint.png;
- /res/drawable-xxhdpi/profile_hint.png.
Coloque as imagens em seus respectivos folders drawable.
Agora a atualização em layout, /res/layout/content_config_profile.xml, onde a RoundedImageView entrará no lugar do ImageView (o ID da View também será atualizado):
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
...>
<com.makeramen.roundedimageview.RoundedImageView
android:id="@+id/riv_profile"
android:layout_width="108dp"
android:layout_height="108dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:src="@drawable/profile_hint"
android:scaleType="centerCrop"
android:onClick="callGallery"
app:riv_border_color="@color/colorViewLine"
app:riv_border_width="1dp"
app:riv_corner_radius="108dp"
app:riv_mutate_background="false"
app:riv_oval="true"
app:riv_tile_mode="clamp"/>
<TextView
...
app:layout_constraintTop_toTopOf="@+id/riv_profile"
app:layout_constraintBottom_toBottomOf="@+id/riv_profile"
app:layout_constraintLeft_toRightOf="@+id/riv_profile"
android:maxLines="10"
... />
...
</androidx.constraintlayout.widget.ConstraintLayout>
Note que não mais estamos utilizando o arquivo de background bg_image_view_profile.xml, logo, delete-o do projeto. Delete também todas as versões do ícone ic_photo_black_24dp.png.
Abaixo o novo diagrama do layout content_config_profile.xml:
Ainda são necessárias algumas poucas atualizações em ConfigProfileActivity para que riv_profile seja utilizado no lugar de iv_profile:
class ConfigProfileActivity : ... {
...
override fun blockFields( ... ){
riv_profile.isEnabled = !status
...
}
...
private fun changeTargetViewConstraints( ... ){
val photoProfileId = riv_profile.id
val parent = riv_profile.parent as ConstraintLayout
...
}
...
}
E uma última atualização, nesta seção, para que o formulário de atualização de perfil já inicie com alguma imagem de perfil caso o objeto user tenha alguma presente:
class ConfigProfileActivity : ... {
override fun onCreate( ... ) {
...
riv_profile.setImageResource(
user?.image ?: R.drawable.profile_hint
)
}
...
}
O algoritmo acima, ao final do método onCreate() da ConfigProfileActivity, é temporário até o momento que começarmos a trabalhar com a imagem de perfil em back-end Web, onde certamente teremos o path dela e assim uma API como a Picasso será utilizada para o carregamento remoto.
Assim podemos partir para a configuração final da galeria.
Configurando a galeria e a câmera
Ainda na atividade ConfigProfileActivity, mas agora no método callGallery(), adicione os códigos ImagePicker em destaque:
...
fun callGallery( view: View ){
val colorPrimary = ColorUtils.int2ArgbString(
ColorUtils.getColor(R.color.colorPrimary)
)
val colorPrimaryDark = ColorUtils.int2ArgbString(
ColorUtils.getColor(R.color.colorPrimaryDark)
)
val colorText = ColorUtils.int2ArgbString(
ColorUtils.getColor(R.color.colorText)
)
val colorWhite = ColorUtils.int2ArgbString(
Color.WHITE
)
ImagePicker
.with( this ) /* Inicializa a ImagePicker API com um context (Activity ou Fragment) */
.setToolbarColor( colorPrimary )
.setStatusBarColor( colorPrimaryDark )
.setToolbarTextColor( colorText )
.setToolbarIconColor( colorText )
.setProgressBarColor( colorPrimaryDark )
.setBackgroundColor( colorWhite )
.setMultipleMode( false )
.setFolderMode( true )
.setShowCamera( true )
.setFolderTitle( getString(R.string.imagepicker_gallery_activity) ) /* Nome da tela de galeria da ImagePicker API (funciona quando FolderMode = true). */
.setLimitMessage( getString(R.string.imagepicker_selection_limit) )
.setSavePath( getString(R.string.imagepicker_cam_photos_activity) ) /* Folder das imagens de câmera, tiradas a partir da ImagePicker API. */
.setKeepScreenOn( true ) /* Mantém a tela acionada enquanto a galeria estiver aberta. */
.start()
}
...
Os métodos que não estão seguidos de comentários têm os rótulo autoexplicativos sobre eles. Há inúmeras outras opções de configuração, mas as definidas no código anterior são as que atendem a nossa necessidade em projeto.
Por fim a configuração do método onActivityResult() para podermos trabalhar a resposta da tela de galeria:
...
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent? ) {
if( requestCode == RC_PICK_IMAGES
&& resultCode == Activity.RESULT_OK
&& data != null ){
val images = data.getParcelableArrayListExtra<Image>( EXTRA_IMAGES )
if( images.isNotEmpty() ){
riv_profile.setImageURI(
Uri.parse( images.first().path )
)
}
}
/*
* Note que em nossa lógica de negócio, se não houver imagem
* selecionada, o que estiver atualmente presente como imagem
* de perfil continua sendo a imagem de perfil.
* */
/*
* A invocação a super.onActivityResult() tem que
* vir após a verificação / obtenção da imagem.
* */
super.onActivityResult( requestCode, resultCode, data )
}
...
Como sempre estaremos esperando 0 ou 1 imagem, é seguro trabalharmos com images.first().path, digo, o método first() na collection images.
Assim podemos partir para os testes.
Testes e resultados
Em seu Android Studio, acesse o menu de topo e logo depois acesse "Build", então clique em "Rebuid project". Ao final do rebuild execute o aplicativo em seu emulador Android.
Não esqueça de colocar o objeto user como um "usuário conectado". Este objeto está na MainActivity:
...
val user = User(
"Thiengo Vinícius",
R.drawable.user,
true /* Usuário conectado. */
)
...
Acessando a tela de atualização de dados de perfil e então seguindo à galeria, temos:
Agora acessando a câmera por meio da galeria, temos:
Com isso finalizamos mais um passo importante para o projeto Android mobile-commerce: a atualização para o AndroidX e a finalização da UI de edição de dados de perfil de usuário.
Antes de prosseguir, não esqueça de se inscrever na 📫 lista de emails do Blog para receber também todas as aulas do projeto Android BlueShoes.
Se inscreva também no canal do Blog em: YouTube Thiengo.
Vídeos
A seguir os vídeos com o passo a passo de atualização do projeto para uso do AndroidX e a integração da ImagePicker API para as funcionalidades de galeria e câmera:
O código do projeto pode ser acessado pelo GitHub dele em: https://github.com/viniciusthiengo/blueshoes-kotlin-android.
Conclusão
Em resumo, e já um clichê, temos: não reinvente a roda. Se a funcionalidade em trabalho em seu projeto não é uma funcionalidade diretamente ligada ao domínio de problema dele, então busque alguma API para a fácil integração, mas busque uma API consistente, mesmo que ela seja pouco conhecida.
Sobre o AndroidX, como informado anteriormente: adiantamos uma dor que seria inevitável, a dor da migração de código. Porém agora nosso projeto está mais atual.
Agora é focar nas próximas partes de UI de nosso Android mobile-commerce para logo logo entrarmos na área de lógica, onde também teremos a parte Web.
Caso você tenha dúvidas ou dicas para este projeto, deixe logo abaixo nos comentários.
Curtiu o conteúdo? Não esqueça de compartilha-lo. E, por fim, se inscreva na 📩 lista de emails, respondo às suas dúvidas também por lá.
Abraço.
Fontes
Get color resource as string - Resposta de N J
Android; Check if file exists without creating a new one - Resposta de Maikel Bollemeijer
Show Image View from file path? - Resposta de Paresh Mayani
Kotlin documentation - Null Safety
Comentários Facebook