Vinculando Telas ao Menu Principal - YouTuber Android App - Parte 9
(2236) (6)
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
CategoriaDesenvolvimento Web
Autor(es)Robert C. Martin
EditoraAlta Books
Edição1ª
Ano2023
Páginas416
Tudo bem?
Neste artigo vamos continuar com o nosso projeto de aplicativo Android para YouTubers.
Nesta parte nove do projeto vamos enfim "dar vida" ao menu principal, o bottom menu na atividade:
Antes de prosseguir, saiba que:
A versão e-book (PDF 📙) do projeto completo está disponível somente para os inscritos da lista de e-mails 📧 do Blog.
Se você se inscrever ainda hoje é possível que o link do e-book esteja disponível na página de confirmação de sua inscrição na lista de e-mails do Blog.
A seguir os tópicos que estaremos abordando neste nono conteúdo do projeto de aplicativo Android para YouTubers:
O que já temos até aqui
Se você chegou no projeto somente agora, saiba que este não é o primeiro e nem mesmo o último artigo já publicado sobre essa proposta de aplicativo Android.
Todo o roteiro de construção do projeto está na listagem a seguir:
- Construa Um Aplicativo Android Completo Para YouTubers - Parte 1;
- Início do Lado Tático e Barra de Topo Personalizada - Parte 2;
- Criando e Configurando o Menu Principal - Parte 3;
- Criando a Estrutura Base Das Telas Com Lista - Parte 4;
- Construindo os Fragmentos de Conteúdo Local - Parte 5;
- Banco de Dados Local Com a Room API - Parte 6;
- Construindo a Tela e a Lógica de Último Vídeo - Parte 7;
- Desenvolvendo a Tela e a Lógica de PlayLists - Parte 8;
- Vinculando Telas ao Menu Principal - Parte 9 (você está aqui);
- Configurando Por Completo o Sistema de Notificação Push - Parte 10;
- Configurando a YouTube Data API Com a Biblioteca Retrofit - Parte 11;
- Configurando o WorkManager Para Requisições em Background - Parte 12;
- Testes e Resultados no Projeto Finalizado - Parte 13;
- Nós Temos Um Framework Em Mãos - Parte 14;
- Como e Onde Monetizar o Aplicativo Framework - Parte 15.
Para tirar o máximo proveito do projeto de aplicativo que estaremos desenvolvendo...
... para isso é inteligente seguir cada um dos conteúdos na ordem apresentada na lista anterior.
Repositório
Para ter acesso a todos os códigos fontes do projeto já finalizado, entre no repositório GitHub dele em:
➙ GitHub do projeto de aplicativo Android para YouTuber.
Fragmentos vinculados ao menu principal
Neste ponto do projeto temos os seguintes fragmentos de telas já finalizados:
- AboutFragment;
- BooksFragment;
- BusinessContactFragment;
- CoursesFragment;
- GroupsFragment;
- LastVideoFragment;
- PlayListsFragment;
- SocialNetworksFragment.
Com isso nós podemos sem receio algum já vincular os fragmentos às suas respectivas opções no menu principal:
Todo o algoritmo de vinculo estará na atividade principal, incluindo um hack code para quando o usuário quiser deixar o app.
É isso. Vamos aos códigos, iniciando com os estáticos de interface.
Layout container
Primeiro vamos atualizar o layout /res/layout/activity_main.xml. Pois é nele que teremos o ViewGroup que será o container dos fragmentos.
Antes de irmos ao XML do layout, vamos primeiro ver como ficará a nova estrutura dele:
Com isso, em activity_main.xml, temos a seguinte nova versão de código XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorScreenBackground"
android:orientation="vertical"
tools:context=".ui.MainActivity">
<include layout="@layout/top_signature" />
<LinearLayout
android:id="@+id/ll_content_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="8dp"
android:background="@color/colorMenuSeparatorLine" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_menu"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="@color/colorMenuBackground"
android:padding="0dp" />
</LinearLayout>
Houve duas adições:
- O LinearLayout (container dos fragmentos) de ID ll_content_container;
- E a cor colorScreenBackground como cor de background do LinearLayout container de todo o layout da atividade.
Logo, no arquivo /res/values/colors.xml adicione a seguinte nova cor:
...
<color name="colorScreenBackground">#FFFFFF</color>
...
Done.
Com isso podemos prosseguir com os novos códigos dinâmicos em projeto.
Algoritmo para troca de fragmento
Precisamos de um algoritmo que troque de fragmento em tela, porém que mantenha ele em memória.
Isso, pois não estamos utilizando ViewPager e também porque não queremos criar toda uma nova estrutura de fragmento quando o usuário voltar a uma tela que ele já tinha acessado anteriormente enquanto o app ainda esta aberto.
Para manter o fragmento em memória e depois conseguir acessa-lo será preciso um identificador único dele.
Com isso podemos relembrar que em cada fragmento de conteúdo nós tínhamos uma constante do tipo String com um "identificador único".
Veja na classe LastVideoFragmento, por exemplo, o identificador único dela:
...
companion object {
/**
* Constante com o identificador único do
* fragmento [LastVideoFragment] para que
* ele seja encontrado na pilha de fragmentos
* e assim não seja necessária a construção
* de mais de um objeto deste fragmento em
* memória enquanto o aplicativo estiver em
* execução.
*/
const val KEY = "LastVideoFragment_key"
}
...
Ok. Necessidade de identificador único já resolvida.
Pois como sabemos, todos os outros fragmentos que representam alguma tela também têm seus próprios identificadores único.
Um outro ponto importante é que a troca de fragmentos seja suave, sem uma mudança bruta de um para o outro.
Humm... para isso podemos utilizar a API FragmentTransaction com a animação de fade in e fade out.
Excelente. Outra necessidade já vai ser resolvida.
Agora precisamos do método com todo esse algoritmo:
- Troca de fragmento, tela, em foreground;
- Mudança de um fragmento para o outro de maneira suave.
Na MainActivity adicione o método changeFragment() com o seguinte código:
...
/**
* Muda o fragmento que deve ficar em foreground
* (primeiro plano).
*
* @param fragment fragmento que deverá ficar
* em foreground.
* @param fragKey identificador único do
* fragmento que vai ficar em foreground.
*/
private fun changeFragment(
fragment: Fragment,
fragKey: String ){
/**
* Animação na transição entre fragmentos.
*/
val fragTransaction = supportFragmentManager.beginTransaction()
fragTransaction.setCustomAnimations(
android.R.anim.fade_in,
android.R.anim.fade_out
)
/**
* Mantendo o fragmento em memória com um
* "localizador" único dele.
*/
fragTransaction.replace(
R.id.ll_content_container,
fragment,
fragKey
)
fragTransaction.addToBackStack( FRAG_STACK_ID )
fragTransaction.commit()
}
...
Note que em changeFragment() colocamos o tipo Fragment ao invés de FrameworkListFragment, pois há três fragmentos em projeto que não herdam de FrameworkListFragment.
São eles:
- AboutChannelFragment;
- LastVideoFragment;
- e PlayListsFragment.
Ops! Quem é FRAG_STACK_ID?
É a String que identifica de maneira única a pilha de fragmentos.
addToBackStack() exige um identificador único para poder funcionar de maneira consistente em projeto.
Note que o identificador único do fragmento é o terceiro argumento em replace(). Todos os fragmentos ficarão na mesma pilha.
Vamos agora adicionar essa constante FRAG_STACK_ID na MainActivity:
...
class MainActivity : AppCompatActivity() {
companion object{
/**
* Constante com o identificador único da
* pilha de fragmentos em memória.
*/
const val FRAG_STACK_ID = "frag_stack_id"
}
...
}
Nice.
Algoritmo de transição suave entre fragmentos em tela já definido e, mesmo que de maneira parcial, algoritmo de troca de fragmentos também já definido.
Obtenção do fragmento
Agora precisamos de:
- O algoritmo que obtém a KEY correta de fragmento de acordo com o ID do item de menu selecionado;
- O algoritmo que retorna o fragmento correto de acordo com o item de menu selecionado;
- Um código que junta ambos os algoritmos dos dois itens anteriores para poder invocar de maneira segura o método changeFragment().
Antes de continuar lembre que cada item de menu tem o seu identificador único. Identificadores definidos no arquivo /res/values/ids.xml de projeto.
Sendo assim, podemos iniciar com o algoritmo mais simples, o de retorno de KEY.
Na MainActivity adicione o método getFragmentInKey() como a seguir:
...
/**
* Retorna o identificador único (id) de
* fragmento em memória de acordo com o
* identificador único de item de menu
* informado como parâmetro.
*
* Com o id de fragmento é possível verificar
* se ele já está em pilha de fragmentos ou
* não.
*
* @param itemId identificador único de item de
* menu.
* @return identificador único do fragmento.
*/
private fun getFragmentInKey(
itemId: Int = R.id.last_video )
= when( itemId ){
R.id.social_networks -> SocialNetworksFragment.KEY
R.id.play_lists -> PlayListsFragment.KEY
R.id.exclusive_groups -> GroupsFragment.KEY
R.id.about_channel -> AboutChannelFragment.KEY
R.id.books -> BooksFragment.KEY
R.id.courses -> CoursesFragment.KEY
R.id.business -> BusinessContactsFragment.KEY
else -> LastVideoFragment.KEY
}
...
O valor padrão de itemId será R.id.last_video. Pois o fragmento de último vídeo disponível em canal sempre será o principal e o primeiro a ser carregado em nosso domínio de problema.
Agora podemos ir ao algoritmo de obtenção do fragmento correto de acordo com o item de menu selecionado.
Neste algoritmo também deverá ter a verificação de fragmento em memória, assim será possível saber se há ou não a necessidade de criar o objeto Fragment.
Na MainActivity adicione o método getFragment() como a seguir:
...
/**
* Retorna o fragmento correto de acordo com o
* identificador único de item de menu informado
* como parâmetro.
*
* Se o fragmento estiver em pilha de fragmentos,
* memória, então ele é obtido da pilha ao invés
* de ser criado um novo. Diminuindo assim a
* possibilidade de vazamento de memória.
*
* @param itemId identificador único de item de
* menu.
* @return objeto fragmento correto.
*/
private fun getFragment(
itemId: Int = R.id.last_video ) : Fragment {
val key = getFragmentInKey( itemId = itemId )
var fragment = supportFragmentManager
.findFragmentByTag( key )
if( fragment == null ){
fragment = when( itemId ){
R.id.social_networks -> SocialNetworksFragment()
R.id.play_lists -> PlayListsFragment()
R.id.exclusive_groups -> GroupsFragment()
R.id.about_channel -> AboutChannelFragment()
R.id.books -> BooksFragment()
R.id.courses -> CoursesFragment()
R.id.business -> BusinessContactsFragment()
else -> LastVideoFragment()
}
}
return fragment
}
...
Done.
Outro método pequeno e intuitivo.
Por fim precisamos do método que invoca em seu corpo de código os dois novos algoritmos criados anteriormente.
Na MainActivity adicione o método fragmentOnScreen() a seguir:
...
/**
* Coloca em tela o fragmento correto de acordo
* com o item de menu informado como parâmetro.
*
* @param item item de menu.
*/
private fun fragmentOnScreen( item: MenuItem ){
val fragment = getFragment( itemId = item.id )
val fragKey = getFragmentInKey( itemId = item.id )
changeFragment(
fragment = fragment,
fragKey = fragKey
)
}
...
Agora precisamos encaixar a invocação dos fragmentos em dois pontos:
- Na abertura da atividade principal;
- e Na mudança de itens de menu.
Linkando nos lugares corretos
Para a abertura da atividade, vamos colocar o código de changeFragment() no onCreate().
Se o retorno de findFragmentByTag() for null, então:
- Ou o usuário está abrindo o aplicativo;
- ou Na reconstrução da atividade (por causa de uma chamada telefônica, por exemplo) nenhum estado de UI foi mantido em memória.
Nestes casos podemos então iniciar a tela com o nosso fragmento principal, LastVideoFragment.
No método onCreate() da MainActivity adicione o bloco condicional a seguir:
...
override fun onCreate( savedInstanceState: Bundle? ) {
setTheme( R.style.AppTheme )
super.onCreate( savedInstanceState )
setContentView( R.layout.activity_main )
if( supportFragmentManager.findFragmentByTag( FRAG_STACK_ID ) == null ){
changeFragment(
fragment = LastVideoFragment(),
fragKey = getFragmentInKey()
)
}
initBottomMenu()
}
...
Agora, como argumento do parâmetro changeFragmentCallback da instância de MenuAdapter, no método initBottomMenu()...
... neste ponto adicione a invocação a fragmentOnScreen() como a seguir:
...
private fun initBottomMenu(){
...
rv_menu.setHasFixedSize( true )
rv_menu.adapter = MenuAdapter(
context = this,
items = MenuItemsData.getItems( res = resources ),
changeFragmentCallback = {
item -> fragmentOnScreen( item = item )
}
)
}
...
Quase tudo pronto. Ainda tem um problema.
O problema da retenção em memória
Até este ponto o projeto já deve estar funcionando sem problemas.
Obviamente que ainda sem acesso a uma base remota de dados do YouTube.
De qualquer forma, executando o aplicativo e logo depois tentando sair dele... chegamos ao problema de "tela vazia":
Isso, pois a ação "deixar o app" somente será possível depois que a pilha de fragmentos estiver vazia.
Para o nosso aplicativo esse problema de "tela vazia" é um bug.
E uma maneira de contornar isso é sobrescrevendo o método onBackPressed() da atividade principal e nele limpando toda a pilha de fragmentos.
É isso.
Na MainActivity adicione o método onBackPressed() com o código a seguir:
...
override fun onBackPressed(){
/**
* O código abaixo limpa a pilha de
* fragmentos do app para que seja
* possível deixar (fechar) o aplicativo
* sem a necessidade de voltar para cada
* um dos fragmentos já navegados pelo
* usuário.
*/
supportFragmentManager
.popBackStack(
null,
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
super.onBackPressed()
}
...
Esse é o roteiro de execução dentro de onBackPressed():
A invocação a super.onBackPressed() deve vir somente depois da pilha de fragmentos ter sido esvaziada.
Então é isso.
Nenhuma outra entidade (classe ou arquivo estático) foi adicionada em projeto.
Sendo assim a nossa configuração física de aplicativo no Android Studio continua sendo a mesma.
Próximo conteúdo
Agora podemos ir a um dos pontos mais importantes para o domínio de problema do nosso aplicativo: geração de notificações push.
Segue o link para acesso ao próximo conteúdo:
➙ Configurando Por Completo o Sistema de Notificação Push - Parte 10.
Então é isso.
Depois de mais este conteúdo, descanse um pouco 🎯. Tome um café ☕ 🍇 🥮. E...
... te vejo na Parte 10 do projeto.
Se houverem dúvidas ou dicas deste nono conteúdo do aplicativo, então deixe nos comentários que logo eu lhe respondo.
Não esqueça de conhecer também o meu canal no YouTube (caso você ainda não conheça) e...
... não deixe de se inscrever na 📩 lista de e-mails para também garantir a versão em PDF não somente deste projeto de aplicativo Android, mas também de cada novo "conteúdo mini-curso".
Abraço.
Comentários Facebook