A biblioteca Dynamic Navigator estende a funcionalidade do componente de navegação do Jetpack para funcionar com destinos definidos em módulos de recursos. Essa biblioteca também oferece a instalação simples de módulos de recursos sob demanda ao navegar até esses destinos.
Configurar
Para oferecer compatibilidade com módulos de recursos, use as seguintes dependências no arquivo build.gradle
do módulo do app:
Groovy
dependencies { def nav_version = "2.8.0" api "androidx.navigation:navigation-fragment-ktx:$nav_version" api "androidx.navigation:navigation-ui-ktx:$nav_version" api "androidx.navigation:navigation-dynamic-features-fragment:$nav_version" }
Kotlin
dependencies { val nav_version = "2.8.0" api("androidx.navigation:navigation-fragment-ktx:$nav_version") api("androidx.navigation:navigation-ui-ktx:$nav_version") api("androidx.navigation:navigation-dynamic-features-fragment:$nav_version") }
As outras dependências de navegação precisam usar configurações de API. para que estejam disponíveis nos módulos de recursos.
Uso básico
Para oferecer suporte a módulos de recursos, primeiro mude todas as instâncias de
NavHostFragment
no app para
androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment
:
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment"
app:navGraph="@navigation/nav_graph"
... />
Em seguida, adicione um atributo app:moduleName
para qualquer destino <activity>
, <fragment>
ou
<navigation>
nos gráficos
de navegação de um módulo com.android.dynamic-feature
associados a um DynamicNavHostFragment
.
Esse atributo informa à biblioteca Dynamic Navigator que o destino
pertence a um módulo de recurso com o nome especificado por você.
<fragment
app:moduleName="myDynamicFeature"
android:id="@+id/featureFragment"
android:name="com.google.android.samples.feature.FeatureFragment"
... />
Quando você navega para um desses destinos, a biblioteca Dynamic Navigator confere primeiro se o módulo de recursos está instalado. Se o módulo de recursos já estiver presente, o app navegará para o destino conforme o esperado. Se o módulo não estiver presente, o app mostrará um destino de fragmento de progresso intermediário à medida que o módulo é instalado. A implementação padrão do fragmento de progresso mostra uma IU básica com uma barra de progresso e processa todos os erros de instalação.
Para personalizar essa IU ou processar manualmente o progresso da instalação na tela do seu app, consulte as seções Personalizar o fragmento de progresso e Monitorar o estado da solicitação neste tópico.
Os destinos que não especificam app:moduleName
continuam funcionando sem
alterações e se comportam como se o app estivesse usando um NavHostFragment
normal.
Personalizar o fragmento de progresso
É possível substituir a implementação do fragmento de progresso para cada gráfico de navegação
definindo o atributo app:progressDestination
como o ID do destino
que você quer usar para processar o progresso da instalação. Seu destino de progresso personalizado
precisa ser um
Fragment
derivado de
AbstractProgressFragment
.
É preciso modificar os métodos abstratos para notificações sobre o progresso da instalação,
erros e outros eventos. É possível mostrar o progresso da instalação em uma
IU de sua escolha.
A classe
DefaultProgressFragment
da implementação padrão usa essa API para mostrar o progresso da instalação.
Monitorar o estado da solicitação
A biblioteca do Dynamic Navigator permite implementar um fluxo de UX semelhante ao das Práticas recomendadas de UX para entrega on demand, em que o usuário continua no contexto de uma tela anterior enquanto aguarda a conclusão da instalação. Isso significa que você não precisa exibir uma IU intermediária ou um fragmento de progresso.
Nesse cenário, você é responsável por monitorar e processar todos os estados de instalação, mudanças de progresso, erros e assim por diante.
Para iniciar esse fluxo de navegação sem bloqueio, transmita um objeto
DynamicExtras
que contenha um
DynamicInstallMonitor
para
NavController.navigate()
,
como mostrado no exemplo a seguir:
Kotlin
val navController = ... val installMonitor = DynamicInstallMonitor() navController.navigate( destinationId, null, null, DynamicExtras(installMonitor) )
Java
NavController navController = ... DynamicInstallMonitor installMonitor = new DynamicInstallMonitor(); navController.navigate( destinationId, null, null, new DynamicExtras(installMonitor); )
Imediatamente após chamar navigate()
, verifique o valor de
installMonitor.isInstallRequired
para ver se a tentativa de navegação resultou
em uma instalação do módulo de recurso.
- Se o valor for
false
, você estará navegando para um destino normal e não precisará fazer mais nada. Se o valor for
true
, comece a observar o objetoLiveData
que agora está eminstallMonitor.status
. Esse objetoLiveData
emite atualizaçõesSplitInstallSessionState
da biblioteca Play Core. Essas atualizações contêm eventos de progresso de instalação que podem ser usados para atualizar a IU. Não esqueça de processar todos os status relevantes conforme descrito no Guia da Play Core, incluindo a solicitação de confirmação do usuário se necessário.Kotlin
val navController = ... val installMonitor = DynamicInstallMonitor() navController.navigate( destinationId, null, null, DynamicExtras(installMonitor) ) if (installMonitor.isInstallRequired) { installMonitor.status.observe(this, object : Observer<SplitInstallSessionState> { override fun onChanged(sessionState: SplitInstallSessionState) { when (sessionState.status()) { SplitInstallSessionStatus.INSTALLED -> { // Call navigate again here or after user taps again in the UI: // navController.navigate(destinationId, destinationArgs, null, null) } SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION -> { SplitInstallManager.startConfirmationDialogForResult(...) } // Handle all remaining states: SplitInstallSessionStatus.FAILED -> {} SplitInstallSessionStatus.CANCELED -> {} } if (sessionState.hasTerminalStatus()) { installMonitor.status.removeObserver(this); } } }); }
Java
NavController navController = ... DynamicInstallMonitor installMonitor = new DynamicInstallMonitor(); navController.navigate( destinationId, null, null, new DynamicExtras(installMonitor); ) if (installMonitor.isInstallRequired()) { installMonitor.getStatus().observe(this, new Observer<SplitInstallSessionState>() { @Override public void onChanged(SplitInstallSessionState sessionState) { switch (sessionState.status()) { case SplitInstallSessionStatus.INSTALLED: // Call navigate again here or after user taps again in the UI: // navController.navigate(mDestinationId, mDestinationArgs, null, null); break; case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION: SplitInstallManager.startConfirmationDialogForResult(...) break; // Handle all remaining states: case SplitInstallSessionStatus.FAILED: break; case SplitInstallSessionStatus.CANCELED: break; } if (sessionState.hasTerminalStatus()) { installMonitor.getStatus().removeObserver(this); } } }); }
Quando a instalação for concluída, o objeto LiveData
emite um status
SplitInstallSessionStatus.INSTALLED
. Em seguida, chame
NavController.navigate()
novamente. Como agora o módulo está instalado, a chamada
será bem-sucedida e o app navegará para o destino conforme o esperado.
Depois de alcançar um estado terminal, como quando a instalação é concluída ou quando
a instalação falha, remova o observador LiveData
para evitar vazamentos
de memória. É possível verificar se o status representa um estado de terminal usando
SplitInstallSessionStatus.hasTerminalStatus()
.
Consulte AbstractProgressFragment
para ver um exemplo de implementação desse observador.
Gráficos incluídos
A biblioteca Dynamic Navigator é compatível com a inclusão de gráficos definidos em módulos de recursos. Para incluir um gráfico definido em um módulo de recursos, faça o seguinte:
Use
<include-dynamic/>
em vez de<include/>
, como mostrado no exemplo a seguir:<include-dynamic android:id="@+id/includedGraph" app:moduleName="includedgraphfeature" app:graphResName="included_feature_nav" app:graphPackage="com.google.android.samples.dynamic_navigator.included_graph_feature" />
Dentro de
<include-dynamic ... />
, você precisa especificar os seguintes atributos:app:graphResName
: o nome do arquivo de recursos do gráfico de navegação. O nome é derivado do nome do arquivo do gráfico. Por exemplo, se o gráfico estiver emres/navigation/nav_graph.xml
, o nome do recurso seránav_graph
.android:id
: ID do gráfico de destino. A biblioteca Dynamic Navigator ignora todos os valoresandroid:id
encontrados no elemento raiz do gráfico incluído.app:moduleName
: nome do pacote do módulo.
Usar o graphPackage correto
Se o app:graphPackage
correto não for usado, o componente de
navegação não poderá incluir a navGraph
especificada no módulo do
recurso.
O nome do pacote para um módulo de recurso dinâmico é composto pelo
nome do módulo unido ao applicationId
do módulo do app base. Assim, se o
módulo do app base tiver com.example.dynamicfeatureapp
como a propriedade applicationId
, e
o módulo do recurso dinâmico for chamado de DynamicFeatureModule
, o nome do
pacote para o módulo dinâmico será
com.example.dynamicfeatureapp.DynamicFeatureModule
. Esse nome de pacote
diferencia maiúsculas de minúsculas.
Em caso de dúvida, confira o AndroidManifest.xml
gerado para confirmar o
nome do pacote para o módulo do recurso. Depois de criar o projeto, acesse
<DynamicFeatureModule>/build/intermediates/merged_manifest/debug/AndroidManifest.xml
,
que terá a seguinte aparência:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:dist="http://schemas.android.com/apk/distribution" featureSplit="DynamicFeatureModule" package="com.example.dynamicfeatureapp" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" /> <dist:module dist:instant="false" dist:title="@string/title_dynamicfeaturemodule" > <dist:delivery> <dist:install-time /> </dist:delivery> <dist:fusing dist:include="true" /> </dist:module> <application /> </manifest>
O valor de featureSplit
precisa ser igual ao nome do módulo do recurso dinâmico, e o pacote corresponderá ao applicationId
do módulo do app base. O app:graphPackage
é a combinação destes dois: com.example.dynamicfeatureapp.DynamicFeatureModule
.
Como acessar um gráfico de navegação com inclusão dinâmica
Só é possível acessar o startDestination
de um
gráfico de navegação include-dynamic
. O módulo dinâmico é responsável pelo
próprio gráfico de navegação, e o app base não tem conhecimento disso.
O mecanismo de inclusão dinâmica permite que o módulo do app base inclua um
gráfico de navegação aninhado
definido no módulo dinâmico. Esse gráfico se comporta
como qualquer outro gráfico de navegação aninhado. O gráfico de navegação raiz (ou seja, o gráfico
do gráfico aninhado) só podem definir o próprio gráfico de navegação aninhado como um
e não os filhos dele. Assim, o startDestination
é usado quando
o gráfico include-dynamicnavigation é o destino.
Limitações
- Os gráficos incluídos dinamicamente não são compatíveis com links diretos.
- Gráficos aninhados carregados dinamicamente (ou seja, um elemento
<navigation>
com umapp:moduleName
) não são compatíveis com links diretos atualmente.