Vinculando Telas ao Menu Principal - YouTuber Android App - Parte 9

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 /Vinculando Telas ao Menu Principal - YouTuber Android App - Parte 9

Vinculando Telas ao Menu Principal - YouTuber Android App - Parte 9

Vinícius Thiengo
(195) (6)
Go-ahead
"É preciso encarar todo retrocesso, fracasso ou dificuldade como provações ao longo do caminho, como sementes plantadas para a colheita futura. Nenhum momento é desperdiçado se você presta atenção nas lições contidas em cada experiência."
Robert Greene
Kotlin Android
Capa do livro Mapas Android de Alta Qualidade - Masterização Android
TítuloMapas Android de Alta Qualidade - Masterização Android
CategoriasAndroid, Kotlin, Masterização, Especialização
AutorVinícius Thiengo
Edição
Ano2020
Capítulos11
Páginas166
Acessar Livro
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
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 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:

Animação do menu principal do app Android em ação

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:

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:

Bottom menu principal do aplicativo Android para YouTubers

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:

Diagrama do layout activity_main.xml

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:

  • 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":

Animação apresentando o problema de tela vazia no app Android

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.

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

Facilitando o Desenvolvimento de Apps Android Com a Biblioteca AndroidUtilCodeFacilitando o Desenvolvimento de Apps Android Com a Biblioteca AndroidUtilCodeAndroid
Chips Android, Quando e Como UtilizarChips Android, Quando e Como UtilizarAndroid
Android About Page API Para Construir a Tela SobreAndroid About Page API Para Construir a Tela SobreAndroid
Porque e Como Utilizar Vetores no AndroidPorque e Como Utilizar Vetores no AndroidAndroid

Compartilhar

Comentários Facebook

Comentários Blog (6)

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...
John vanderson (1) (0)
19/08/2020
Ao executar o build está aparecendo o seguinte erro:

> Task :app:processDebugManifest FAILED
/home/john/projetos/android/CanalVinciusThiengo/app/src/main/AndroidManifest.xml Error:
Attribute meta-data#onesignal_app_id@value at AndroidManifest.xml requires a placeholder substitution but no value for <onesignal_app_id> is provided.
/home/john/projetos/android/CanalVinciusThiengo/app/src/main/AndroidManifest.xml Error:
Attribute meta-data#onesignal_google_project_number@value at AndroidManifest.xml requires a placeholder substitution but no value for <onesignal_google_project_number> is provided.

See http://g.co/androidstudio/manifest-merger for more information about the manifest merger.


> Task :app:mergeDebugResources

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:processDebugManifest'.
> Manifest merger failed with multiple errors, see logs

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 5s
8 actionable tasks: 7 executed, 1 up-to-date
Responder
Vinícius Thiengo (0) (0)
19/08/2020
John, tudo bem?

Esse problema não deveria estar ocorrendo neste ponto do projeto, pois você está na parte 9.

E o OneSignal é configurado em projeto somente na parte 10.

De qualquer forma, caso você já tenha configurado o OneSignal em projeto, certifique-se de que no Gradle Nível de Aplicativo, ou build.gradle (Module: app), tenha somente uma configuração de manifestPlaceholders.

Exatamente como a seguir (porém utilizando o ID de app OneSignal que você criou na plataforma):

manifestPlaceholders = [
        onesignal_app_id: 'ID de app OneSignal',
        onesignal_google_project_number: 'REMOTE'
]

John, se mesmo assim nada.

Vou pedir que você coloque o código de seu Gradle Nível de Aplicativo no PasteBin ( https://pastebin.com/ ) e então compartilhe o link aqui.

Isso para que eu dê uma olhada nele e assim consiga uma solução.

É isso.

Surgindo mais dúvidas ou problemas, pode enviar.

Obs. : o problema que você está enfrentando também está reportado na documentação GitHub do OneSignal:

https://github.com/OneSignal/OneSignal-Android-SDK/issues/696

Abraço.
Responder
John vanderson (1) (0)
19/08/2020
Eu conseguir entender o que aconteceu, é o seguinte em uma parte do projeto foi usado uma lib que tinha sido definida em outra parte do artigo eu até cheguei a comenta sobre isso, sendo assim eu fui até esses cara e configurei os Gradle de acordo com que estáva no git, ai apareceu esse BO, lendo realmente a issue comentada acimada tinha essa solução.
Responder
Vinícius Thiengo (0) (0)
19/08/2020
John,

Excelente.

Espero que essa configuração adiantada que você tinha feito não seja uma que já esteja em artigos anteriores à parte 10 de todo o projeto de aplicativo.

De qualquer forma, neste exato momento eu estou formatando a versão em PDF do conteúdo completo.

Ou seja: é uma nova revisão que estou aplicando ao conteúdo.

Se eu encontrar problemas, vou corrigir também nos artigos aqui no Blog.

É isso.

Surgindo dúvidas ou problemas, pode enviar.

Abraço.
Responder
john vanderson (1) (0)
19/08/2020
Teve um drawable que teve um problema ao informa o valor Hex da cor diretamente, porem vi que você acabou corrigindo no GIT, mais já estou no ultimo artigo já \o.
Responder
Vinícius Thiengo (0) (0)
19/08/2020
John,

Excelente.

Na verdade o último conteúdo eu ainda vou liberar.

Amanhã estarei liberando a 14º artigo (penúltimo conteúdo), onde mostro passo a passo como mudar o canal do aplicativo.

É nesse conteúdo que vamos provar que o que temos em mãos na verdade é um framework.

O PDF, infelizmente, vou liberar somente na próxima semana. Pois ele ainda não está totalmente finalizado.

É isso.

Surgindo dúvidas, pode enviar.

Abraço.
Responder