Open GuilhermeVVeiga opened 1 year ago
Eu encontrei esse problema tbm, quando você entra no módulo principal do RouterOutlet sem navegar entre os módulos/rotas filhos e sai para o módulo login por exemplo o dispose do módulo principal do RouterOutlet é feito, porém se navegar internamente nos módulos filho, só esses são disposed e o do módulo principal do RouterOutlet não. Senhores, @jacobaraujo7, @Bwolfs2, tem alguém olhando isso? Obrigado
Mesmo problema aqui com a versão 3.13.6 do Flutter
Mesmo problema aqui. Quando saio do módulo e volto as rotas filhas ainda estão ativas porquê o módulo não foi desativado.
Bom dia @jacobaraujo7, alguma novidade?
Olá @GuilhermeVVeiga @migdev-br @Dansp @Zeca-dev Eu fiz um um pull request #911 para resolver esse problema e está aguardando análise. Por favor, se puderem testar a solução e colocar um comentário no pull request, eu agradeceria.
Para testar, como a alteração é referente ao modular_core, acrescente isso ao seu pubspec (não precisa alterar a dependência flutter_modular
):
dependency_overrides:
modular_core:
git:
url: https://github.com/eduardoflorence/modular.git
ref: module_not_dispose
path: modular_core
@eduardoflorence , boa tarde. Eu verifiquei que o dispose ocorre com a sua alteração, porém o mesmo só ocorre se estiver usando Modular.to.navigate. Se utilizar, por exemplo, Navigator.of(context, rootNavigator: true).pop(), o dispose não ocorre. A sua solução não resolve o meu caso, pois todas as rotas são eliminadas ao utilizar o Modular.to.navigate. Meu cenário exije que as rotas sejam eliminadas só do módulo que quero dar o dispose. Observe o seguinte cenário:
AppModule [HomeModule] -> navega para o módulo Home. HomeModule [HomePage] -> Navega para HomeBank. HomeBankModule [HomeBankPage] -> Navega para contaCorrente ou Pix. ContaCorrenteModule [Page1, Page2, Page3] -> Ao fehcar volta para HomeBank. PixModule [PixPage] -> Ao fechar volta para HomeBank.
Veja que os módulos de conta corrente e pix, ao serem fechados devem navegar de volta para HomeBankPage, que pertencem ao HomeBankModule. Se eu utilizar Modular.to.navigate('/homeBank') todas as rotas iniciais serão perdidas (nesse caso HomeModule).
Eu preciso navegar de volta mantendo as rotas de HomeModule e HomeBakn ativas, fechando e dando dispose apenas no módulo acima, que nesse caso poderia ser ContaCorrenteModule ou PixModule.
Boa tarde @Zeca-dev,
Pelo que entendi do exemplo do seu cenário, basta utilizar o Modular.to.pushNamed
ao invés do navigate. No PR que coloquei de correção (#911), tem um exemplo com os dois tipos de navegação (navigate e pushNamed).
Verifica e me diz se resolveu.
@eduardoflorence , boa tarde.
Nesse caso eu estaria empilhando uma nova página/módulo acima do que já tenho. O que preciso é fechar/sair do módulo atual, dando dispose no mesmo e portanto "matando" todas as suas rotas. Ao fazer isso eu já estaria no módulo abaixo que o chamou, dessa forma:
AppModule [HomeModule] -> navega para o módulo Home. HomeModule [HomePage] -> Navega para HomeBank. HomeBankModule [HomeBankPage] -> Navega para contaCorrente ou Pix. ContaCorrenteModule [Page1, Page2, Page3] -> Ao fehcar volta para HomeBank. PixModule [PixPage] -> Ao fechar volta para HomeBank.
Suponha que estando em HomeBank e chame ContaCorrenteModule, em seguida navego até a conclusão de um fluxo dentro deste módulo (page3). Terminando esse fluxo eu dou um pop na page. Eu espero que esse pop feche todo o módulo ContaCorrenteModule, dando dispose em tudo, fazendo com que eu continue com todas as demais rotas abaixo desse módulo, ou seja, a página dentro do HomeBankModule que chamou o ContaCorrenteModule.
Boa tarde @Zeca-dev,
Como você quer que continue as rotas iniciais, tem que ser com o pushNamed
mesmo. Em relação ao módulo que tem que ser fechado após navegar pelas páginas filhas dele, é só utilizar o popUntil
.
Eu fiz o exemplo abaixo para você testar (atenção para o forRoot
nas rotas children e para o popUnitl
):
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
void main() {
runApp(ModularApp(module: AppModule(), child: const AppWidget()));
}
class AppWidget extends StatelessWidget {
const AppWidget({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: Modular.routerConfig,
);
}
}
class AppModule extends Module {
@override
void routes(r) {
r.module('/', module: HomeModule());
}
}
class HomeModule extends Module {
@override
void routes(r) {
r.child('/', child: (_) => const HomePage());
r.module('/homebank', module: HomeBankModule());
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('HomePage')),
body: Center(
child: ElevatedButton(
onPressed: () => Modular.to.pushNamed('/homebank/'),
child: const Text('HomeBank'),
),
),
);
}
}
class HomeBankModule extends Module {
@override
void routes(r) {
r.child('/', child: (_) => const HomeBankPage());
r.module('/contacorrente', module: ContaCorrenteModule());
//r.module('/pix', module: PixModule());
}
}
class HomeBankPage extends StatelessWidget {
const HomeBankPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('HomeBank')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => Modular.to.pushNamed('/homebank/contacorrente/'),
child: const Text('Conta Corrente'),
),
ElevatedButton(
onPressed: () => Modular.to.pushNamed('/homebank/pix/'),
child: const Text('Pix'),
),
],
),
),
);
}
}
class ContaCorrenteModule extends Module {
@override
void routes(r) {
r.child('/', child: (_) => const ContaCorrentePage(), children: [
ChildRoute('/page1', child: (_) => const Page1()),
ChildRoute('/page2', child: (_) => const Page2()),
]);
//r.module('/pix', module: PixModule());
}
}
class ContaCorrentePage extends StatelessWidget {
const ContaCorrentePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Conta Corrente')),
body: Center(
child: ElevatedButton(
onPressed: () => Modular.to.pushNamed('/homebank/contacorrente/page1', forRoot: true),
child: const Text('Go Page 1'),
),
),
);
}
}
class Page1 extends StatelessWidget {
const Page1({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 1')),
body: Center(
child: ElevatedButton(
onPressed: () => Modular.to.pushNamed('/homebank/contacorrente/page2', forRoot: true),
child: const Text('Go Page 2'),
),
),
);
}
}
class Page2 extends StatelessWidget {
const Page2({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 2')),
body: Center(
child: ElevatedButton(
onPressed: () => Modular.to.popUntil(ModalRoute.withName('/homebank/')),
child: const Text('Return HomeBank'),
),
),
);
}
}
@eduardoflorence , boa noite. Dessa forma vai funcionar porquê a navegação está apontando para o navegador root (forRoot) e assim ocorre o dispose, mas se colocar essa navegação de page1 e page2 dentro de um RouterOutlet irá quebrar e não fará o dispose. Se usar o root faz o dispose mas a navegação quebra. Se navegar normal, sem forRoot, não faz o dispose.
@Zeca-dev, você consegue alterar o exemplo que eu fiz para representar o problema e colocar aqui?
Farei isso hoje à noite. Obrigado pela atenção. As features aqui na empresa são construídas baseadas em fluxo como esse citado, então ter isso funcionando no modular ajudaria muito.O modular é a minha primeira opção quando penso em injeção de dependências e navegação.
@eduardoflorence , boa noite. Segue o exemplo solicitado:
No fluxo representando o usuário entra no módulo de conta corrente e tem uma navegação interna, por exemplo um cadastro, e no final sai do módulo. O problema é que o dispose não ocorre, e portanto, ao entrar novamente no módulo de conta corrente todas as rotas estão ativas, o que acaba quebrando o fluxo original.
class AppWidget extends StatelessWidget {
const AppWidget({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routerConfig: Modular.routerConfig,
);
}
}
class AppModule extends Module {
@override
void routes(r) {
r.module('/', module: HomeModule());
}
}
class HomeModule extends Module {
@override
void routes(r) {
r.child('/', child: (_) => const HomePage());
r.module('/homebank', module: HomeBankModule());
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('HomePage')),
body: Center(
child: ElevatedButton(
onPressed: () => Modular.to.pushNamed('/homebank/'),
child: const Text('HomeBank'),
),
),
);
}
}
class HomeBankModule extends Module {
@override
void routes(r) {
r.child('/', child: (_) => const HomeBankPage());
r.module('/contacorrente', module: ContaCorrenteModule());
// r.module('/pix', module: PixModule());
}
}
class HomeBankPage extends StatelessWidget {
const HomeBankPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('HomeBank')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 110,
child: ElevatedButton(
onPressed: () => Modular.to.pushNamed('/homebank/contacorrente/'),
child: const Text('Account'),
),
),
// ElevatedButton(
// onPressed: () => Modular.to.pushNamed('/homebank/pix/'),
// child: const Text('Pix Payment'),
// ),
],
),
),
);
}
}
class ContaCorrenteModule extends Module {
@override
void routes(r) {
r.child('/',
child: (_) => const ContaCorrentePage(),
transition: TransitionType.downToUp,
children: [
ChildRoute('/page1',
child: (_) => const Page1(),
transition: TransitionType.rightToLeft,
isFullscreenDialog: true),
ChildRoute('/page2', child: (_) => const Page2(), transition: TransitionType.rightToLeft),
ChildRoute('/page3', child: (_) => const Page3(), transition: TransitionType.rightToLeft),
]);
//r.module('/pix', module: PixModule());
}
}
///A conta corrente é um RouterOutlet que contem um fluxo interno
///que navega da Page1 até a Page3, sendo que nesta é feita a finalização
///do fluxo. Porém o dispose não ocorre e ao entrar novamente em conta corrente
///todas as Pages que foram colocadas na pilha ainda estão lá, quando deveria
///reiniciar o fluxo e os empilhamentos.
///
///Ao colocar o forRoot o dispose ocorre, porém quebra o fluxo da navegação do
///RouterOutlet, surgindo inclusive algumas telas pretas entre a navegação, por
///estarem em um Navigator diferente do interno ao RouterOutlet.
///
///Se utilizar Modular.to.navigate todas as rotas anteriores serão perdidas e
///o usuário não poderá voltar a home, por exemplo, mantendo o estado que havia
///antes de iniciar as navegações.
class ContaCorrentePage extends StatelessWidget {
const ContaCorrentePage({super.key});
@override
Widget build(BuildContext context) {
Modular.to.pushNamed('./page1');
return const Scaffold(
// appBar: AppBar(title: const Text('Conta Corrente - RouterOutlet')),
body: Center(
child: Expanded(
child: RouterOutlet(),
),
),
);
}
}
class Page1 extends StatelessWidget {
const Page1({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Account - Page 1'),
actions: [IconButton(onPressed: () => Modular.to.pop(), icon: const Icon(Icons.close))],
),
body: Center(
child: ElevatedButton(
onPressed: () => Modular.to.pushNamed('./page2'),
child: const Text('Go Page 2'),
),
),
);
}
}
class Page2 extends StatelessWidget {
const Page2({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Account - Page 2')),
body: Center(
child: ElevatedButton(
onPressed: () => Modular.to.pushNamed('./page3'),
child: const Text('Go Page 3'),
),
),
);
}
}
class Page3 extends StatelessWidget {
const Page3({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Account - Page 3'),
),
body: Center(
child: ElevatedButton(
onPressed: () => Modular.to.popUntil(ModalRoute.withName('/homebank/')),
child: const Text('Return HomeBank'),
),
),
);
}
}
Boa noite @Zeca-dev,
Agora com seu exemplo ficou mais fácil ver o problema. Ele não tem relação com a correção que eu estou propondo no PR.
Após uma pesquisa vi que esse problema, que está no seu exemplo, tem relação com o fato de tanto o Modular.to.pop
quanto o Modular.to.popUntil
não conseguirem agir no navegador do RouterOutlet
. Eles só agem no Navegador principal. Por isso que as páginas do módulo de conta corrente ainda continuam na pilha e consequentemente o módulo não é disposado.
Para te ajudar, eu fiz as duas funções abaixo para usar em rotas filhas (children). No seu exemplo acima, para dar o dispose no módulo de forma correta, bastaria alterar :
onPressed: () => Modular.to.popUntil(ModalRoute.withName('/homebank/')),
Por:
onPressed: () => modularChildrenPopUntil('/homebank/'), // Se for módulo a string tem que terminar com /
Seguem as funções:
Future<void> modularChildrenPop() async {
final currentConfiguration = Modular.routerDelegate.currentConfiguration;
final currentRoutes = [...currentConfiguration!.routes];
final newCurrentRoutes = currentRoutes..removeLast();
await Modular.routerDelegate.setNewRoutePath(currentConfiguration.copyWith(routes: newCurrentRoutes));
}
Future<void> modularChildrenPopUntil(String routeName) async {
final currentConfiguration = Modular.routerDelegate.currentConfiguration;
final currentRoutes = [...currentConfiguration!.routes];
final indexRoute = currentRoutes.lastIndexWhere((element) => element.name == routeName);
final newCurrentRoutes = indexRoute > -1 ? currentRoutes.getRange(0, indexRoute + 1).toList() : [currentRoutes.first];
await Modular.routerDelegate.setNewRoutePath(currentConfiguration.copyWith(routes: newCurrentRoutes));
}
@eduardoflorence, boa noite. Muito obrigado. Vou testar assim que puder. Teria como fazer algo semelhante para integrar ao Modular em outra PR? Esse cenário é bem comum em alguns fluxos dentro de projetos diversos, então seria algo que agregaria bastante valor ao package.
@jacobaraujo7 , boa tarde.
Poderia avaliar junto com o @eduardoflorence a possibilidade de adicionar esse tratamento de dispose e encerramento de rotas filhas, quando estamos usando o RouterOutlet?
Obrigado.
@eduardoflorence , boa tarde. Fiz o teste e o dispose realmente funciona, porém seria necessário sobrescrever o WillPopScope para adicionar esse comportamento de pop das páginas filhas. Além disso a animação da rota-mãe é perdida, o que não é interessante, pois ao entrarmos na rota principal com um tipo de animação queremos sair da mesma utilizando o mesmo tipo. Com essa função "modularChildrenPopUntil" as animações são perdidas.
Diante do exposto, e considerando que essa correção inicial seja aceita, precisaria criar uma nova PR considerando esses cenários, de modo a manter o compportamento das animações e saídas de rota pelo botão físico ou seta de voltar da AppBar.
Describe the bug Referente a issue https://github.com/Flutterando/modular/issues/899. O problema ocorrido não era intrínseco ao RouterOutlet Qualquer navegação em rotas filhas ocorre esse problema.