Skip to content

JulioRennan/demo_modular

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flutter Modular

Esse package tem como proposta te auxiliar a ter um projeto escalável e com um baixo nivel de acoplamento entre camadas, independente da sopa de letrinhas que você estiver usando como: MVC, MVVM, VIPER, DDD e assim vai. Para alcançar esse objetivo ele possui duas funcionalidades que auxiliam muito no processo: O Sistema de Injeção de Dependência e a Navegação via Rotas.

Para facilitar a assimilação do conhecimento é desejável que você leia esse documento com o código aberto e rodando, para você conseguir ir visualizando o que cada recurso faz de maneira mais clara.

Tabela de conteúdos

Começando o projeto.

Antes de entrar em detalhes sobre as funcionalidades o modular exige um preset muito simples:

  • Configurar a função main em main.dart para:

    void main() => runApp(
      ModularApp(
        module: AppModule(),
        child: AppWidget(),
      ),
    );
    
  • Configurar o AppWiget:

    class AppWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // É obrigatório usar o Modular.routerDelegate e Modular.routeInformationParser.
        return MaterialApp.router(
          title: 'Modular Demo',
          theme: ThemeData(primarySwatch: Colors.green),
          routerDelegate: Modular.routerDelegate,
          routeInformationParser: Modular.routeInformationParser,
        );
      }
    }
    
  • Criar e configurar o móludo inicial AppModule:

    class AppModule extends Module {
     @override
     final List<Bind> binds = [];
    
     @override
     final List<ModularRoute> routes = [];
    }
    

Simples não é mesmo? A partir de agora, você deve pensar no seu código como um lego de módulos. Cada Módulo tem a sua configuração, page e componentes especificos. E caso ache válido para o seu projeto, as camadas relacionadas a regras de negócio.

Entendendo um Módulo.

O Módulo nada mais é que um agrupamento de features com escopos semelhantes ou iguais. Cada módulo tem as injeções de dependência, configuradas em binds e rotas, configuradas em routes daquele módulo. A estrutura recomendada pelo autor é algo semelhante a isto:

.                  
├── features                                 # All features or Modules 
│   ├─ auth                                  # Auth's MVC       
│   │  ├── auth_model.dart   
│   │  ├── auth_controller.dart  
│   │  └── auth_page.dart                      
│   ├─ home                                  # Home's MVC       
│   │  ├── home_model.dart   
│   │  ├── home_controller.dart  
│   │  └── home_page.dart                        
│   └─ product                               # Product's MVC     
│      ├── product_model.dart   
│      ├── product_controller.dart
│      └── product_page.dart                    
├── core                                     # Tools and utilities
├── app_widget.dart                          # Main Widget containing MaterialApp 
└── main.dart                                # runApp 

Nesse exemplo ele separou cada módulo no padrão MVC, mas não é obrigatório. Adapte de acordo com o seu projeto, mas o fundamental é: Desenvolva baseado em escopos.

Configurando rotas.

A rota inicial costuma ser "/", podendo ser chamada de Modular.initialRoute. No exemplo a seguir a minha rota inicial será um outro módulo o HomeModule.

class AppModule extends Module {
 @override
 final List<Bind> binds = [];

   @override
   final List<ModularRoute> routes = [
     ModuleRoute(
       Modular.initialRoute,
       module: HomeModule(),
       transition: TransitionType.downToUp,
     ),
     WildcardRoute(child: (_, args) => NotFoundPage())
   ];
 }

Note que AppModule não conhece nada sobre HomeModule ele apenas o define como rota inicial.

ChildRoute

Define qual vai ser o Widget atribuído a determinada rota. O primeiro argumento é o nome da rota e o argumento child é uma função que recebe um BuildContext e ModularArguments e deve retornar um Widget definido por você.

Em HomeModule temos esse exemplo: ChildRoute('/unauthorized', child: (_, args) => UnauthorizedPage())

com isso temos que a rota /unauthorized/ retornara UnauthorizedPage.

ModuleRoute

Define qual vai ser o Modulo atribuído a determinada rota. O primeiro argumento é o nome da rota e Module é o módulo que será definido a partir daquela rota.

Em HomeModule temos esse exemplo: ModuleRoute( "/news", module: NewsModule(), ),

ModularArguments

Objeto poderoso em que você pode recuperar os Params, QueyParams e Data passados naquela rota.

  • Params: recuperar valores passados por parametro na rota, por exemplo: /news foi definida como /news/:id

    Modular.to.navigate('/news/12341231');
    // args.params['id'] == 12341231
    
  • QueyParams: Recuperar valores passados por query parametes na rota, por exemplo: /news foi definida como /news/

    Modular.to.navigate('/news/?name=julio&numero=1234567');
    // args.queryParams['name'] == julio e args.queryParams['numero']=1234567
    
  • Data: Recuperar objetos passados no segundo parâmetro da navegação.

    Modular.to.navigate("/rota, ObjetoTeste());
    //args.data == instância de ObjetoTeste
    

Além de estar disponível em toda definição de ChildRoute, também pode ser recuperado com Modular.args, que retornará os argumentos da rota atual.

PS: Esse recurso é extremamente poderoso e útil para Web.

WildcardRoute

Essa classe define qual vai ser o Widget a ser mostrado caso o usuário esteja em uma rota não definida em seu módulo.

Em AppModule temos esse exemplo:

WildcardRoute(child: (_, args) => NotFoundPage())
//Qualquer rota não definida que o usuário tente acessar levará para NotFoundPage()

RouteGuard

Lembra o funcionamento de um middleware, mas ele apenas redireciona o usuário para outra rota desejada caso ele não atenda uma condição. Em HomeModule temos esse exemplo:

  • Definindo o RouteGuard

    class AuthGuard extends RouteGuard {
      AuthGuard() : super(redirectTo: '/unauthorized');
    
      @override
      FutureOr<bool> canActivate(String path, ParallelRoute route) {
        return Modular.args.queryParams['token'] != null;
      }
    }
    // Usuario será direcionado para /unauthorized, caso ele não passe um token via query parameter
    // diferente de nulo.
    
  • Definindo a rota que será protegida pelo RouteGuard

    ModuleRoute("/auth", module: AuthModule(), guards: [
             AuthGuard(),
           ])
    
  • Definição da rota /unauthorized:

     ChildRoute('/unauthorized', child: (_, args) => UnauthorizedPage())
    

Caso sua rota necessite de mais RouteGuard Basta defini-lo e adicionar no parâmetro guards

RouterOutlet

Esse Wiget nos permite implementar Widgets como BottomNavigationBar de maneira simples. E o melhor de tudo, com rotas. Para utilizar ele você deve configurar a rota que retornará o Widget com RouterOutlet e quais serão as rotas filhas que estarão dentro de RouterOutlet:

Em HomeModule temos esse exemplo:

  • Configuração no Módulo:
 ChildRoute(
     "/",
      child: (_, args) => HomePage(), // Rota que retorna um Widget Com RouterOutlet.
      children: [
        ModuleRoute(
          "/news",
          module: NewsModule(),
        ),
        
        ModuleRoute(
          "/search",
          module: SearchModule(),
        ),
        ModuleRoute(
          "/config",
          module: ConfigModule(),
        ),
        ModuleRoute("/auth", module: AuthModule(), guards: [
          AuthGuard(),
        ]),
        ChildRoute('/unauthorized', child: (_, args) => UnauthorizedPage())
      ],
    )
  • Configuração do RouterOutlet, não é necessário implementar NavigationRail para que RouterOutlet funcione. Mas sem o uso de um Widget semelhante, não tem necessidade de utilizar RouterOutlet.
class HomePage extends StatelessWidget {
 const HomePage({Key? key}) : super(key: key);

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     body: Row(
       children: [
         NavigationRailHome(),
         Expanded(child: RouterOutlet()),
       ],
     ),
   );
 }
}
  • Configuração de NavigationRailHome:
class NavigationRailHome extends StatefulWidget {
  const NavigationRailHome({Key? key}) : super(key: key);

  @override
  State<NavigationRailHome> createState() => _NavigationRailHomeState();
}

class _NavigationRailHomeState extends State<NavigationRailHome> {
  final routes = ["/news/", "/search/", "/config/", "/auth/"];

  int _selectedIndex = 0;
  @override
  void initState() {
    super.initState();
    updateIndex();
    SchedulerBinding.instance?.addPersistentFrameCallback((timeStamp) {
      Modular.to.addListener(() {
        // Toda vez que alterar a rota é necessário atualizar o index do NavigationRail, para o index selecionado.
        if (mounted) setState(() => updateIndex()); 
      });
    });
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return NavigationRail(
      selectedIndex: _selectedIndex,
      onDestinationSelected: (int index) {
        Modular.to.navigate(routes[index]);
      },
      backgroundColor: Colors.green.withOpacity(.1),
      labelType: NavigationRailLabelType.selected,
      unselectedIconTheme: IconThemeData(color: Colors.grey.shade900),
      minWidth: 20,
      destinations: const <NavigationRailDestination>[
        NavigationRailDestination(
          icon: Icon(Icons.newspaper_outlined),
          selectedIcon: Icon(Icons.newspaper),
          label: Text('News'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.search_outlined),
          selectedIcon: Icon(Icons.search),
          label: Text('Pesquisa'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.settings_outlined),
          selectedIcon: Icon(Icons.settings),
          label: Text('Configuração'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.security),
          selectedIcon: Icon(Icons.settings),
          label: Text('Rota Autorizada'),
        ),
      ],
    );
  }

  updateIndex() {
    final path = Modular.args.uri.path;
    if (path.contains('unauthorized')) _selectedIndex = 3;

    for (int i = 0; i < routes.length; i++) {
      if (path.contains(routes[i])) _selectedIndex = i;
    }
  }
}

Note que de fato estamos navegando pelas rotas do App. E a cada navegação atualizamos o _selectedIndex para mostrar a opção selecionada corretamente. Dessa maneira podemos navegar tanto direto via URL quanto interagindo com o NavigationRail.

Conclusão

Nessa demo simples podemos aprender recursos que abrangem diversos casos de uso frequentes na Web. No momento a injeção de dependência não foi explorada, mas caso tenha ficado curioso de uma olhadinha na documentção em Flutter Modular.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published