Como Melhorar a Área de Configurações de Conta - Android M-Commerce

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes! Você receberá um email de confirmação. Somente depois de confirma-lo é que eu poderei lhe enviar os conteúdos semanais exclusivos. Os artigos em PDF são entregues somente para os inscritos na lista.

Email inválido.
Blog /Android /Como Melhorar a Área de Configurações de Conta - Android M-Commerce

Como Melhorar a Área de Configurações de Conta - Android M-Commerce

Vinícius Thiengo
(3727)
Go-ahead
"O método consciente de tentativa e erro é mais bem-sucedido que o planejamento de um gênio isolado."
Peter Skillman
Prototipagem Android
Capa do curso Prototipagem Profissional de Aplicativos
TítuloAndroid: Prototipagem Profissional de Aplicativos
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
Acessar Curso
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Lendo
TítuloDomain-driven Design Destilado
CategoriaEngenharia de Software
Autor(es)Vaughn Vernon
EditoraAlta Books
Edição
Ano2024
Páginas160
Conteúdo Exclusivo
Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba gratuitamente conteúdos Android sem precedentes!
Email inválido

Tudo bem?

Neste artigo vamos à melhoria do código da área de configurações de conta do usuário BlueShoes, nosso aplicativo Android de mobile-commerce.

Ainda há muito código duplicado e isso é um problema sério principalmente no momento da manutenção, de colocar ou remover funcionalidades. Por isso à necessidade de melhoria antes de partirmos para a próxima área do aplicativo.

Animação da área de configurações de conta do usuário

Pelo mais incrível que pareça, a refatoração será bem simples, provavelmente mais simples do que as outras já aplicadas em projeto, isso, pois o código duplicado na área de configurações de conta é bem "óbvio".

Antes de prosseguir, não deixe de se inscrever 📫na lista de e-mails do Blog para receber, em primeira mão, todas as atualizações do projeto BlueShoes e de outros conteúdos de desenvolvimento Android, exclusivos aqui do Blog.

A seguir os tópicos abordados em aula:

Iniciando no Android mobile-commerce

Se você chegou agora neste projeto, saiba que está é na verdade a 18ª aula dele. E sim: estamos focados em ir até a "entrega" do aplicativo.

Logo, para seguir o desenvolvimento com consistência, mesmo que você seja um completo iniciante no desenvolvimento Kotlin Android, não deixe de primeiro consumir as outras aulas, na ordem apresentada a seguir:

É muito importante que você também se inscreva na 📫 lista de e-mails do Blog, pois por lá, além de receber as atualizações do projeto, eu também tiro suas principais dúvidas.

Estratégia de refatoração

A estratégia aqui não será muito diferente do que já esta sendo utilizado em projeto:

  • Primeiro vamos trabalhar as classes e layouts mais simples, que vão afetar menos outras entidades do projeto quando forem atualizadas;
  • Depois vamos aos trechos mais críticos, atividadesfragmentoslayouts que antes de serem atualizados precisam que uma série de outras entidades já estejam com os códigos prontos, refatorados.

Eu sei que você provavelmente já esta ciente da informação que vou enfatizar no próximo paragrafo, mas não custa nada lembrar, certo?

O projeto Android BlueShoes esta também disponível no GitHub, mais precisamente em: https://github.com/viniciusthiengo/blueshoes-kotlin-android.

É uma excelente escolha acompanhar a sua evolução no projeto junto ao que venho disponibilizando no repositório dele. Lembrando que obter o projeto no GitHub não anula de forma alguma a importância de seguir o projeto pelos artigos e vídeos disponibilizados aqui no Blog.

Pois são nestes que eu explico por completo o que esta sendo abordado, o porquê de APIs XYPZ estarem sendo utilizadas, entre outros.

Então, vamos a refatoração!

Pacotes

Nossa estrutura física de projeto, mais precisamente a estrutura em /config, ainda necessita de mais um pacote, isso pois a ConfigProfileActivity, responsável pela tela de atualização de nome e foto de perfil do usuário, se encontra fora do pacote principal das classes, da camada de visualização, da área de configurações de conta.

Estrutura física antiga do projeto no Android Studio

Profile

Para que a classe ConfigProfileActivity entre no pacote principal e o mantenha organizado, dentro de /config crie um novo pacote, com o rótulo profile.

Assim teremos:

Estrutura física do pacote /config

Colocando as classes que faltam

Por fim, para está atualização de pacotes, coloque a classe ConfigProfileActivity dentro do novo pacote /profile.

Coloque também, mas agora na raiz do pacote /config, as classes AccountSettingsActivity e AccountSettingsItemsAdapter. Classes estas que são as responsáveis por permitir ao usuário o acesso às outras partes da área de configurações de conta.

Ao final teremos a seguinte configuração física de projeto a partir do pacote /config:

Estrutura física do pacote /config com o pacote /profile

Retirando a verbosidade

Certamente você deve estar pensando: "Retirando a verbosidade"? O que isso significa?

Simples: os rótulos grandes, verbosos, das classes da área de configurações de conta de usuário.

Quer um exemplo?

Veja, dentro do pacote /config/deliveryaddress tem uma classe com o rótulo ConfigDeliveryAddressesListItemsAdapter.

Será mesmo que ao menos o termo Config precisa fazer parte do nome desta classe?

Acredite, se está classe não tiver ao menos o termo Config em seu nome isso não afetará em nada, negativamente, a leitura do código. Lembrando que a qualidade na leitura do código é algo que estamos dando importância desde o início do projeto.

Sendo assim, devemos renomear algumas classes que estão com rótulos "verbosos".

Classes em /connectiondata

No pacote /connectiondata nós temos os seguintes rótulos:

  • ConfigConnectionDataActivity;
  • ConfigConnectionDataSectionsAdapter;
  • ConfigEmailFragment;
  • ConfigPasswordFragment.

Para renomear cada uma das classes temos que seguir o mesmo roteiro que faremos agora para a classe ConfigConnectionDataActivity:

  • Clicar com o botão direito do mouse em ConfigConnectionDataActivity;
  • Acessar "Refactor";
  • Clicar em "Rename";
  • Colocar como novo rótulo ConnectDataActivity;
  • E por fim clicar em "Refactor".

Atualizando o nome da atividade ConfigConnectionDataActivity

Seguindo o mesmo roteiro para as outras classes do pacote /connectiondata, porém com os rótulos novos indicados abaixo:

  • De ConfigConnectionDataSectionsAdapter para ConnectDataSectionsAdapter;
  • De ConfigEmailFragment para FormEmailFragment;
  • De ConfigPasswordFragment para FormPasswordFragment.

Teremos ao final:

Estrutura física do pacote /connectiondata

Classes em /creditcard

No pacote /creditcard temos as classes de rótulos:

  • ConfigCreditCardsActivity;
  • ConfigCreditCardsListFragment;
  • ConfigCreditCardsListItemsAdapter;
  • ConfigCreditCardsSectionsAdapter;
  • ConfigNewCreditCardFragment.

E modificando de acordo com a listagem a seguir:

  • De ConfigCreditCardsActivity para CreditCardsActivity;
  • De ConfigCreditCardsListFragment para CreditCardsListFragment;
  • De ConfigCreditCardsListItemsAdapter para CreditCardsListAdapter;
  • De ConfigCreditCardsSectionsAdapter para CreditCardsSectionsAdapter;
  • De ConfigNewCreditCardFragment para FormCreditCardFragment.

Ao final teremos em /creditcard:

Estrutura física do pacote /creditcard

Note que não utilizei o rótulo FormNewCreditCardFragment e sim FormCreditCardFragment, pois há somente um formulário para cartões de crédito, o de adição de um novo. Desta forma não vi a necessidade de também ter de incluir o termo New no rótulo do fragmento.

Classes em /deliveryaddress

No pacote /deliveryaddress temos as classes de seguintes rótulos:

  • ConfigDeliveryAddressesActivity;
  • ConfigDeliveryAddressesListFragment;
  • ConfigDeliveryAddressesListItemsAdapter;
  • ConfigDeliveryAddressesSectionsAdapter;
  • ConfigDeliveryAddressHostFragment;
  • ConfigNewDeliveryAddressFragment;
  • ConfigUpdateDeliveryAddressFragment.

Modificando os rótulos das classes deste pacote como a seguir:

  • De ConfigDeliveryAddressesActivity para DeliveryAddressesActivity;
  • De ConfigDeliveryAddressesListFragment para DeliveryAddressesListFragment;
  • De ConfigDeliveryAddressesListItemsAdapter para DeliveryAddressesListAdapter;
  • De ConfigDeliveryAddressesSectionsAdapter para DeliveryAddressesSectionsAdapter;
  • De ConfigDeliveryAddressHostFragment para DeliveryAddressHostFragment;
  • De ConfigNewDeliveryAddressFragment para FormNewDeliveryAddressFragment;
  • De ConfigUpdateDeliveryAddressFragment para FormUpdateDeliveryAddressFragment.

Temos ao final:

Estrutura física do pacote /deliveryaddress

Classe em /profile

Agora, no novo pacote /profile, devemos fazer a seguinte atualização:

  • De ConfigProfileActivity para ProfileActivity.

Ao final teremos:

Estrutura física do pacote /profile

Também na raiz de /config

E por fim, na raiz de /config, para seguir o modelo de rótulos de entidades que são na verdade classes adaptadoras de frameworks de lista, devemos fazer a seguinte atualização:

  • De AccountSettingsItemsAdapter para AccountSettingsListAdapter.

Assim, teremos:

Nova estrutura física do pacote /config

Será que não poderíamos encurtar ainda mais os rótulos?

Vamos lá: dentro do pacote /connectiondata temos a classe CreditCardsActivity. Ou seja, é possível que somente o rótulo Activity fosse o suficiente, certo?

Porém está mesma estratégia deveria ser seguida no pacote /deliveryaddress para a classe DeliveryAddressesActivity. Assim teríamos outra classe, dentro de /config, com o mesmo rótulo, Activity.

Acima eu dei um simples exemplo.

O problema é que seria necessário exigir do desenvolvedor que ele também identifica-se o pacote da Activity atual para saber de qual Activity que se trata em código: da Activity de /connectiondata; ou da Activity de /deliveryaddress.

Ou seja, certamente não seria algo benéfico ao projeto. Por isso mantive os termos, presentes nos pacotes, também em classes que ficariam com nomes iguais, mesmo sabendo que os packages eram diferentes.

Note que eu nem mesmo levei em consideração que no Android SDK já existe uma classe Activity, classe que é bem conhecida no mundo do desenvolvimento Android, algo que complicaria ainda mais a leitura do código.

Removendo códigos duplicados

Agora chegamos a parte que mais vai impactar os algoritmos da área de configurações de conta de usuário. É a partir daqui que removeremos o máximo possível de códigos repetidos.

E, como informado na começo desta aula, vamos começar pelas classes mais simples e depois seguiremos para as mais críticas, com mais dependências.

Fragmento parent dos fragmentos com formulário

Todos os fragmentos, dentro de /config, que têm formulários dentro deles já herdam de FormFragment (fragmento alias que continuará fora do pacote /config).

Ou seja, os códigos dentro desses fragmentos já são únicos em domínio de problema, a duplicação é mínima e aceitável.

Porém nenhum desses fragmentos têm a obrigatoriedade de ter como parte de seu código um "título de tab", algo necessário para eles que fazem parte de atividades que contêm tabs.

Barra de topo com tabs

É, eu sei, esses fragmentos têm a constante TAB_TITLE. Mas ainda é algo opcional, não obrigatório.

E confesso que não pretendo colocar essa regra de negócio na classe FormFragment, pois a principio essa regra é exclusiva para os fragmentos de formulários da área de configurações de conta de usuário e FormFragment certamente será também utilizada em outros pontos do projeto.

Sendo assim eu resolvi criar um novo fragmento ancestral, que também herda de FormFragment, porém exigindo de suas subclasses a implementação de um método abstrato title().

Em /config, na raiz deste pacote, crie a classe ConfigFormFragment com o seguinte código:

abstract class ConfigFormFragment :
FormFragment() {

abstract fun title() : Int
}

 

Ok, Thiengo. Mas não seria melhor colocar a sintaxe Kotlin para deixar uma constante em ConfigFormFragment ser sobrescrita nas subclasses?

Certamente não. Primeiro porque constante em Kotlin é final, ou seja, não seria possível a sobrescrita.

Segundo porque a "obrigatoriedade" ainda não existiria, tendo em mente que o valor de TAB_TITLE em ConfigFormFragment seria o valor padrão se uma subclasse não fornecesse o seu próprio título.

Assim podemos seguir com as atualizações dos "fragmentos formulários".

FormEmailFragment

No pacote /connectiondata, mais precisamente na classe FormEmailFragment, adicione os trechos em destaque:

class FormEmailFragment :
ConfigFormFragment() {

override fun title()
= R.string.config_connection_data_tab_email

...
}

 

Note que a partir das atualizações deste tópico algumas classes do projeto, em sua instalação do Android Studio, vão apresentar problemas. Não se preocupe, ao final de todas as atualizações tudo estará normal e funcionando.

Note que a única parte removida de FormEmailFragment foi a constante TAB_TITLE.

FormPasswordFragment

Ainda no pacote /connectiondata, no fragmento FormPasswordFragment, remova TAB_TITLE e coloque os códigos em destaque:

class FormPasswordFragment :
ConfigFormFragment() {

override fun title()
= R.string.config_connection_data_tab_password

...
}

 

FormCreditCardFragment

Agora no pacote /creditcard, mais precisamente no fragmento FormCreditCardFragment, remova a constante TAB_TITLE e adicione os códigos em destaque:

class FormCreditCardFragment :
ConfigFormFragment(),
View.OnFocusChangeListener {

override fun title()
= R.string.config_credit_cards_tab_new

...
}

FormNewDeliveryAddressFragment

Então no pacote /deliveryaddress. No fragmento FormNewDeliveryAddressFragment remova somente a constante TAB_TITLE, permaneça com PAGER_POS. E adicione os trechos em destaque a seguir:

open class FormNewDeliveryAddressFragment :
ConfigFormFragment() {

companion object{
/*
* A constante abaixo representa a posição
* deste fragmento no ViewPager. Os
* posicionamentos em ViewPager começam
* em 0.
* */
const val PAGER_POS = 1
}

override fun title()
= R.string.config_delivery_address_tab_new

...
}

 

E é isso mesmo, o fragmento FormUpdateDeliveryAddressFragment, como não é responsável por título de tab e também porque herda de FormNewDeliveryAddressFragment, esse não passará por nenhuma atualização.

DeliveryAddressHostFragment

Por fim, ainda no pacote /deliveryaddress, no fragmento DeliveryAddressHostFragment adicione os trechos em destaque:

class DeliveryAddressHostFragment
: ConfigFormFragment() {

override fun title()
= DeliveryAddressesListFragment.TAB_TITLE

override fun onCreateView(...)
...
}

override fun getLayoutResourceID() = 0

override fun backEndFakeDelay() {}

override fun blockFields(status: Boolean) {}

override fun isMainButtonSending(status: Boolean) {}
}

 

É, eu sei. Este fragmento não é um fragmento de formulário, porém, quando na parte de refatoração dos adapters de ViewPager, utilizar sempre subclasses de ConfigFormFragment será uma melhor escolha, por isso a necessidade aqui de já realizar essa atualização.

Eu sei também que não é nada legal essa quantidade de métodos não utilizados e fora do contexto da classe, mas a principio, ao menos até o final desta refatoração, é a melhor opção.

Fragmento ancestral de lista de itens

Se você vem acompanhando nosso projeto Android mobile-commerce "a risca" você certamente ficou preocupado com as classes refatoradas anteriormente.

Digo, ficou preocupado, pois os fragmentos com listas de itens, cartões de crédito e endereços, não foram incluídos.

Esses dois fragmentos têm bastante código em comum e merecem, ao invés de herdarem diretamente de ConfigFormFragment, um fragmento ancestral responsável por manter todo o algoritmo comum a eles.

E sim, esse novo fragmento, ancestral de fragmentos com lista em /config, herdará de ConfigFormFragment.

Layout unificado

Mas antes de irmos para os códigos dinâmicos do novo fragmento, vamos à um layout único, que será utilizado nesse novo fragmento e será essencial para todas as subclasses dele.

Em /res/layout crie o layout fragment_config_list.xml com a seguinte estrutura:

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

<TextView
android:visibility="gone"
android:id="@+id/tv_empty_list"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="30dp"/>

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_items"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"/>
</FrameLayout>

 

Abaixo o diagrama do layout anterior:

Diagrama do layout fragment_config_list.xml

E sim, já podemos remover de /res/layout os layouts fragment_config_credit_cards_list.xml e fragment_config_delivery_addresses_list.xml, pois eles não mais serão utilizados.

ConfigListFragment

Ainda na raiz de /config crie o fragmento ConfigListFragment, abstrato, como a seguir:

abstract class ConfigListFragment : ConfigFormFragment() {

var callbackMainButtonUpdate : (Boolean)->Unit = {}
var callbackBlockFields : (Boolean)->Unit = {}
var callbackRemoveItem : (Boolean)->Unit = {}

override fun getLayoutResourceID()
= R.layout.fragment_config_list

override fun blockFields(status: Boolean) {
callbackBlockFields( status )
}

override fun isMainButtonSending( status: Boolean ) {
callbackMainButtonUpdate( status )
callbackRemoveItem( status )
}

/*
* Método utilizado para receber os callbacks do adapter
* do RecyclerView para assim poder atualizar os itens
* de adapter.
* */
fun callbacksToChangeItem(
mainButtonUpdate: (Boolean)->Unit,
blockFields: (Boolean)->Unit,
removeItem: (Boolean)->Unit ){

callbackMainButtonUpdate = mainButtonUpdate
callbackBlockFields = blockFields
callbackRemoveItem = removeItem
}

override fun onActivityCreated( savedInstanceState: Bundle? ) {
super.onActivityCreated( savedInstanceState )

updateFlFormToFullFreeScreen()
initItems()
}

/*
* Método que inicializa a lista de cartões de crédito.
* */
private fun initItems(){
rv_items.setHasFixedSize( false )

val layoutManager = LinearLayoutManager( activity )
rv_items.layoutManager = layoutManager

val adapter = getRecyclerViewAdapter()
adapter.registerAdapterDataObserver( RecyclerViewObserver() )
rv_items.adapter = adapter
}

abstract fun getRecyclerViewAdapter() : RecyclerView.Adapter<out RecyclerView.ViewHolder>

/*
* Com o RecyclerView.AdapterDataObserver é possível
* escutar o tamanho atual da lista de itens vinculada
* ao RecyclerView e caso essa lista esteja vazia, então
* podemos apresentar uma mensagem ao usuário informando
* sobre a lista vazia.
* */
inner class RecyclerViewObserver :
RecyclerView.AdapterDataObserver() {

override fun onItemRangeRemoved(
positionStart: Int,
itemCount: Int ) {

super.onItemRangeRemoved( positionStart, itemCount )

tv_empty_list.visibility =
if( rv_items.adapter!!.itemCount == 0 )
View.VISIBLE
else
View.GONE
}
}
}

 

Volte e estude por completo o código da classe acima, ConfigListFragment. Todos os códigos comuns em CreditCardsListFragment e em DeliveryAddressesListFragment foram adicionados a ela.

Em initItems() apenas o trecho de criação de instância adaptadora de itens de RecyclerView era distinto, sendo assim também criamos um método abstrato, getRecyclerViewAdapter(), para que as subclasses forneçam cada uma essa parte única delas.

Com isso podemos partir para as atualizações nos dois fragmentos que contêm listas de itens.

CreditCardsListFragment

No pacote /creditcard, o fragmento CreditCardsListFragment agora tem a seguinte estrutura completa de código:

class CreditCardsListFragment :
ConfigListFragment() {

override fun title()
= R.string.config_credit_cards_tab_list

override fun backEndFakeDelay() {
backEndFakeDelay(
true,
getString( R.string.credit_card_removed )
)
}

override fun onActivityCreated( savedInstanceState: Bundle? ) {
super.onActivityCreated( savedInstanceState )

tv_empty_list.setText( R.string.credit_card_list_empty )
}

override fun getRecyclerViewAdapter()
= CreditCardsListAdapter(
this,
CreditCardsDataBase.getItems()
)
}

 

Note que em onActivityCreated() é preciso atualizar o texto em tv_empty_list, pois agora temos o mesmo layout para os dois fragmentos que contêm framework de lista.

DeliveryAddressesListFragment

Agora no pacote /deliveryaddress coloque a estrutura de código da classe DeliveryAddressesListFragment como a seguir:

class DeliveryAddressesListFragment :
ConfigListFragment() {

companion object{
const val TAB_TITLE = R.string.config_delivery_addresses_tab_list
}

override fun title() = TAB_TITLE

override fun backEndFakeDelay() {
backEndFakeDelay(
true,
getString( R.string.delivery_address_removed )
)
}

override fun onActivityCreated( savedInstanceState: Bundle? ) {
super.onActivityCreated( savedInstanceState )

tv_empty_list.setText( R.string.delivery_address_list_empty )
}

override fun getRecyclerViewAdapter()
= DeliveryAddressesListAdapter(
this,
DeliveryAddressesDataBase.getItems()
)
}

 

TAB_TITLE foi mantido, pois ele está sendo utilizado em DeliveryAddressHostFragment. E sim, o método title() não terá nenhuma funcionalidade na classe acima. Ócios do ofício! 🤔

E as classes adaptadoras

Nós, a principio, não vamos tentar a reutilização do código das classes adaptadoras de itens, mais precisamente as classes CreditCardsListAdapterDeliveryAddressesListAdapter.

Isso, pois são poucos os pontos que podem ser reutilizados e eles mesmo assim exigirão a criação de alguns métodos abstratos que no final das contas aumentará ainda mais a quantidade de código em projeto sem um ganho real na leitura dele.

Adaptador padrão para ViewPager

Se repararmos bem, as classes adaptadoras de páginas para ViewPager, aquelas que em nosso projeto têm o sufixo SectionsAdapter. Todas elas têm "praticamente" os mesmos algoritmos.

Veja, por exemplo, o código de ConnectDataSectionsAdapter:

class ConnectDataSectionsAdapter(
val context: Context,
fm: FragmentManager
) : FragmentPagerAdapter( fm ) {

companion object{
const val TOTAL_PAGES = 2
const val EMAIL_PAGE_POS = 0
}

override fun getItem( position: Int )
= when( position ){
EMAIL_PAGE_POS -> FormEmailFragment()
else -> FormPasswordFragment()
}

override fun getPageTitle( position: Int )
= context.getString(
when( position ){
EMAIL_PAGE_POS -> FormEmailFragment.TAB_TITLE
else -> FormPasswordFragment.TAB_TITLE
}
)

override fun getCount(): Int {
return TOTAL_PAGES
}
}

 

... como é similar ao código de CreditCardsSectionsAdapter:

class CreditCardsSectionsAdapter(
val context: Context,
fm: FragmentManager
) : FragmentPagerAdapter( fm ) {

companion object{
const val TOTAL_PAGES = 2
const val CREDIT_CARDS_PAGE_POS = 0
}

override fun getItem( position: Int )
= when( position ){
CREDIT_CARDS_PAGE_POS -> CreditCardsListFragment()
else -> FormCreditCardFragment()
}

override fun getPageTitle( position: Int )
= context.getString(
when( position ){
CREDIT_CARDS_PAGE_POS -> CreditCardsListFragment.TAB_TITLE
else -> FormCreditCardFragment.TAB_TITLE
}
)

override fun getCount(): Int {
return TOTAL_PAGES
}
}

 

E também tem o código de DeliveryAddressesSectionsAdapter, que, acredite, é praticamente o mesmo - mas vou poupa-lo desta fadiga.

Sem contar que os TAB_TITLEs apresentados anteriormente não mais existem em código, foram removidos e deram lugar ao método title(), que exige uma instância ao invés de apenas uma referência à classe host.

Sendo assim eu percebi, como uma melhor escolha, criar uma única classe FragmentPagerAdapter, adaptadora de páginas ViewPager, para todas as atividades com tabs, digo, atividades da área de configurações de conta do usuário.

Com isso, na raiz de /config, crie a classe ConfigSectionsAdapter como a seguir:

/**
* Um FragmentPagerAdapter que retorna um fragmento correspondendo
* a uma das sections/tabs/pages.
*
* Mesmo que o método getItem() indique que mais de uma instância
* do mesmo fragmento será criada, na verdade objetos
* FragmentPagerAdapter mantêm os fragmentos em memória para que
* eles possam ser utilizados novamente, isso enquanto houver
* caminho de volta a eles (transição entre Tabs, por exemplo).
*/
class ConfigSectionsAdapter(
private val context: Context,
fm: FragmentManager,
private vararg val fragments: ConfigFormFragment
) : FragmentPagerAdapter( fm ) {

companion object{
const val TOTAL_PAGES = 2
const val FIRST_PAGE_POS = 0
const val SECOND_PAGE_POS = 1
}

/*
* getItem() é invocado para devolver uma instância do
* fragmento correspondendo a posição (seção/página)
* informada.
* */
override fun getItem( position: Int )
= when( position ){
FIRST_PAGE_POS -> fragments[ FIRST_PAGE_POS ]
else -> fragments[ SECOND_PAGE_POS ]
}

override fun getPageTitle( position: Int )
= context.getString(
when( position ){
FIRST_PAGE_POS -> fragments[ FIRST_PAGE_POS ].title()
else -> fragments[ SECOND_PAGE_POS ].title()
}
)

override fun getCount()
= TOTAL_PAGES
}

 

Note que é a primeira vez em projeto que estamos fazendo uso de varargs no Kotlin:

class ConfigSectionsAdapter(
private val context: Context,
fm: FragmentManager,
private vararg val fragments: ConfigFormFragment
) ... { ... }

 

Este é, em resumo:

Um recurso de linguagem poderoso para trabalharmos com arrays implícitos.

E é isso mesmo que você está pensando. Com o nosso novo adapter, ConfigSectionsAdapter, podemos seguramente remover todos os outros do projeto, digo, remover os a seguir:

  • ConnectDataSectionsAdapter;
  • CreditCardsSectionsAdapter;
  • DeliveryAddressesSectionsAdapter.

Superclasse das atividades de /config

Outras entidades que são muito similares são as atividades:

  • ConnectDataActivity;
  • CreditCardsActivity;
  • DeliveryAddressesActivity.

O que realmente muda nelas, com as novas configurações que já temos em projeto, são os argumentos que devem entrar nas instâncias de ConfigSectionsAdapter, onde cada atividade terá a sua.

Sendo assim nós podemos com segurança criar uma nova atividade abstrata que conterá todo o código duplicado e também a regra de negócio, método abstrato, solicitando uma instância especifica de ConfigSectionsAdapter.

Na raiz de /config crie a atividade ConfigFormActivity com o seguinte código:

abstract class ConfigFormActivity : AppCompatActivity() {

override fun onCreate( savedInstanceState: Bundle? ) {
super.onCreate( savedInstanceState )
setContentView( R.layout.activity_tabs_user_config )
setSupportActionBar( toolbar )

/*
* Para liberar o back button na barra de topo da
* atividade.
* */
supportActionBar?.setDisplayHomeAsUpEnabled( true )
supportActionBar?.setDisplayShowHomeEnabled( true )

/*
* Hackcode para que a imagem de background do layout não
* se ajuste de acordo com a abertura do teclado de
* digitação. Caso utilizando o atributo
* android:background, o ajuste ocorre, desconfigurando o
* layout.
* */
window.setBackgroundDrawableResource( R.drawable.bg_activity )

/*
* Criando o adaptador de fragmentos que ficarão expostos
* no ViewPager.
* */
val sectionsPagerAdapter = getSectionsAdapter()

/*
* Acessando o ViewPager e vinculando o adaptador de
* fragmentos a ele.
* */
view_pager.adapter = sectionsPagerAdapter

/*
* Acessando o TabLayout e vinculando ele ao ViewPager
* para que haja sincronia na escolha realizada em
* qualquer um destes componentes visuais.
* */
tabs.setupWithViewPager( view_pager )
}

/*
* Para permitir que o back button tenha a ação de volta para
* a atividade anterior.
* */
override fun onOptionsItemSelected( item: MenuItem ) : Boolean {
if( item.itemId == android.R.id.home ){
finish()
return true
}
return super.onOptionsItemSelected( item )
}

abstract fun getSectionsAdapter() : FragmentPagerAdapter
}

 

Lembrando que o layout activity_tabs_user_config.xml já era utilizado em todas as atividades citadas anteriormente.

ConnectDataActivity

No pacote /connectiondata deixe a atividade ConnectDataActivity como a seguir:

class ConnectDataActivity :
ConfigFormActivity() {

override fun getSectionsAdapter()
= ConfigSectionsAdapter(
this,
supportFragmentManager,
FormEmailFragment(),
FormPasswordFragment()
)
}

CreditCardsActivity

Agora no pacote /creditcards deixe a classe CreditCardsActivity como abaixo:

class CreditCardsActivity :
ConfigFormActivity() {

override fun getSectionsAdapter()
= ConfigSectionsAdapter(
this,
supportFragmentManager,
CreditCardsListFragment(),
FormCreditCardFragment()
)
}

DeliveryAddressActivity

Por fim, no pacote /deliveryaddress, deixe o código da atividade DeliveryAddressesActivity como a seguir:

class DeliveryAddressesActivity :
ConfigFormActivity() {

override fun getSectionsAdapter()
= ConfigSectionsAdapter(
this,
supportFragmentManager,
DeliveryAddressHostFragment(),
FormNewDeliveryAddressFragment()
)

override fun onOptionsItemSelected( item: MenuItem ): Boolean {

if( item.itemId == android.R.id.home ){
onBackPressed()
return true
}

return super.onOptionsItemSelected( item )
}

override fun onBackPressed() {
val fragmentsInStack = supportFragmentManager.backStackEntryCount

/*
* Se houver algum fragmento em pilha de fragmentos
* e o fragmento atual em tela não for o fragment de
* formulário de novo endereço de entrega, então o
* próximo fragmento da pilha de fragmentos é que
* deve ser apresentado.
*
* Caso contrário, volte a atividade anterior via
* finish().
* */
if( fragmentsInStack > 0
&& isNewDeliveryAddressFormNotInScreen() ){
supportFragmentManager.popBackStack()
}
else {
finish()
}
}

private fun isNewDeliveryAddressFormNotInScreen() : Boolean
= view_pager.currentItem != FormNewDeliveryAddressFragment.PAGER_POS
}

 

Note que o que era único dela, da atividade DeliveryAddressesActivity, foi mantido.

Estrutura até aqui

Com isso, depois de todas essas atualizações, temos a seguinte estrutura física em /config:

Nova estrutura física do pacote /config

Testes e resultados

Com a sua instalação do Android Studio aberta, acesse:

  • O menu de topo;
  • Clique em "Build";
  • Clique em "Rebuid project";
  • Ao final do rebuild execute o app em seu AVD Android.

Não esqueça de colocar o objeto user como um "usuário conectado". Objeto que se encontra na MainActivity:

...
val user = User(
"Thiengo Vinícius",
R.drawable.user,
true /* Usuário conectado. */
)
...

 

Acessando a área de atualização de dados de perfil, temos:

Animação da área de atualização de dados de perfil

Acessando a área de atualização de dados de conexão, temos:

Animação da área de atualização de dados de conexão

Acessando a área de endereços, temos:

Animação da área de endereços

Acessando a área de cartões de crédito, temos:

Animação da área de cartões de crédito

Com isso terminamos mais uma importante parte de nosso projeto Android de mobile-commerce, BlueShoes.

Muitos trechos de códigos duplicados foram isolados e poderão, em outras partes do projeto, ser novamente reutilizados.

Antes de seguir para a conclusão, não esqueça de se inscrever na 📫 lista de e-mails do Blog para receber semanalmente as aulas do projeto Android BlueShoes e outros conteúdos exclusivos.

Se inscreva também no canal do Blog em YouTube Thiengo.

Vídeos

A seguir os vídeos com o passo a passo da refatoração do código da área de configurações de conta do usuário final do app:

O código do projeto também pode ser acessado no GitHub dele em: https://github.com/viniciusthiengo/blueshoes-kotlin-android.

Conclusão

Melhorar o código? Remover duplicações?

Sim, são importantes também estes processos, mesmo que inúmeras vezes em um mesmo projeto.

É assim que conseguimos tirar o melhor dele, em termos estruturais, e consequentemente sermos mais eficientes em desenvolvimento e em manutenção.

Quase todo o código antigo da área de configurações de conta do usuário estava problemático, isso, pois havia inúmeros trechos repetidos, até mesmo na parte de layout, XML.

Com está refatoração nós diminuímos consideravelmente os códigos duplicados sem, certamente, perder em eficiência de processamento.

Caso você tenha dúvidas no projeto, ainda sobre está 18ª aula, não deixe de comentar abaixo que logo eu lhe respondo.

Curtiu o conteúdo? Não esqueça de compartilha-lo. E, por fim, de se inscrever na 📩 lista de e-mails, respondo às suas dúvidas também por lá.

Abraço.

Fontes

Kotlin Functions - documentação oficial

Where Should I Keep My Constants in Kotlin?

Multiple types in Generic Extensions

Kotlin Generics - documentação oficial

Understanding Generics and Variance in Kotlin

Initialize array in method argument [duplicate] - Resposta de Sean Patrick Floyd

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes!
Email inválido

Relacionado

Freelancer AndroidFreelancer AndroidAndroid
Como Reter Objetos Utilizando Android-State APIComo Reter Objetos Utilizando Android-State APIAndroid
Lottie API Para Animações no AndroidLottie API Para Animações no AndroidAndroid
Android About Page API Para Construir a Tela SobreAndroid About Page API Para Construir a Tela SobreAndroid

Compartilhar

Comentários Facebook

Comentários Blog

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...