Bottom Tab Bar et router

Hello tout le monde,

Je compte m’attaquer à l’intégration de la bottom tab bar sur Flutter mais ca me pose un problème de périmètre avec le router.

Si j’ai bien compris, la redirection vers la bonne vue après le tap de l’utilisateur sur un icône est géré par la tap bar et je n’ai pas envie de gérer une navigation autre part que dans le router.

Est-ce que quelqu’un a déjà implémenté le router et la bottom tab bar sur un même projet ? Je serai preneur de quelques tuyaux :slight_smile:

Hello,
Utilises-tu les routes nommées ou bien la navigation 2.0 ?

Hello @Mathis,
La navigation 2.0 :slight_smile:

Hello,
Personnellement j’ai fait comme ceci dans le BottomNavBarWidget :

Widget _buildBottomNavBar(BuildContext context) {
    return BottomNavigationBar(
      backgroundColor: Colors.black,
      unselectedItemColor: Colors.white,

      onTap: (index) {
        switch (index) {
          case 0:
            viewModel.UserTouchedHome();
            break;
          case 2:
            viewModel.UserTouchedFavorites();
            break;
        }
      },
      type: BottomNavigationBarType.fixed,
      currentIndex: indexSelected,
      items: const <BottomNavigationBarItem>[
...
//ici  se trouve la liste de mes items (icons)

Pour la suite, pour ramener la gestion de la navigation à qui de droit, je declare viewModel.UserTouchedHome();
de cette manière

void UserTouchedHome() {
   _router.displayHome();
 }

pour l’implémentation de displayHome() , je le fais dans le navigationDelegate.
J’ai rapidement fais un petit diagramme, j’espère être pardonné pour les potentielles erreurs de syntaxe :smiley:

En espérant avoir pu t’aider,
Bonne journée,

Mathis

1 « J'aime »

J’aurais fait exactement comme @Mathis à sa place pour faire remonter les infos.

Cependant il faut aussi se poser la question ensuite de comment faire au niveau du routeur lui même.
Si la TabBar doit rester tout le temps visible et chacun des onglets doit avoir son propre historique alors tu devras probablement utiliser des routeurs enfants pour chacun des onglets

Dans la doc officielle du Router ils disent :

A particularly elaborate application might have multiple Router widgets, in a tree configuration, with the first handling the entire route parsing and making the result available for routers in the subtree. The routers in the subtree do not participate in route information parsing but merely take the result from the first router to build their sub routes.

Donc ton routeur principal gère la conversion des URL via ton parser habituel, puis tu utilises juste des Widget de type Router dans chaque onglet qui ne connaîtront que leurs écrans.
C’est comme ça que je comprends la doc mais je n’ai encore jamais essayé de le faire

1 « J'aime »

Whaou … Et moi qui croyait m’attaquer à quelque chose de simple :sweat_smile::sweat_smile::sweat_smile:
En tout cas merci pour les conseils à tous les deux. Je vais tenter de mettre ca en place :grinning:

Hello @Mathis ,
Je viens de commencer cette fonctionnalité et j’ai réussi à mettre en place la bottomNavBar dans un écran test et ca me pose des questions sur la suite de mes développements :slight_smile:

J’ai deux entrées sur ma bottomNavBar, ma vue A et ma vue B. Mon widget bottomNavBar est également en place et je pensais l’ajouter sur ma vue A et sur ma vue B mais j’ai l’impression d’après ton diagramme que tu as fait l’inverse …
Est-ce que tu as créé une seule vue avec une bottomNavBar et tu switch le contenu en fonction du tap utilisateur ?

Hello @Tazooou,
En fait, j ai fait un widget bottomNavBar et son viewModel qui ne gère que ça. Dans mes différentes vue autres, dans lesquelles je veux ce bottomNavBar, j’ai juste à appeler le widget en question.
Je pourrais t’envoyer un exemple des que je rentre du travail si tu veux.
Bonne journée :smiley:

1 « J'aime »

Ok je comprends mieux, je n’avais jamais envisagé de créer un viewmodel pour un widget :slight_smile:

J’ai essayer de coder cette façon de faire mais je me retrouve devant un problème.

Comment tu renseignes cette ligne de code dans l’une de tes vues ? Il m’indique une erreur puisque mon widget attend en paramètre son viewmodel. Tu crées une interface ?

bottomNavigationBar: BottomNavigationBarWidget(),

Hello,
Bon je cale sur cette architecture … :smiley:
Si je mets directement mon BottomNavigationBarWidget au niveau de ma vue, je suis obligé de lui mettre le viewmodel et le router associé. Et c’est un peu contraire à ce qu’on fait dans les cours de @mbritto depuis le début.
J’aurai voulu créer un faux widget, une espèce d’interface qui me permettent d’isoler ma vue principale mais je ne sais pas comment faire …
@Mathis, dès que tu as du temps, je suis toujours preneur de ton bout de code pour l’implémentation du widget !!
Très bonne journée.

Salut @Tazooou ,
Je comprend ce qui te pose problème, je n’ai pas été aussi loin dans la réflexion.
Pour ma part, j’ai fait comme cela dans chacune des vues dans lesquelles je retrouve ma BottomNavBar :

bottomNavigationBar:
BottomNavigationBarWidget(0,BottomNavBarViewModel(widget._bottomNavBarRouter)),

Je suis donc obligé de faire final BottomNavBarRouter _bottomNavBarRouter; dans chacune des vues.
Si tu trouves quelque chose de plus joli je suis prenneur :smiley:
Bonne journée

Dans ma vue principale, j’ai tenté de créer une interface de widget et je peux donc remplir mon option bottomNavigationBar de cette manière :

abstract class IBottomNavigationBarWidget extends StatefulWidget {
  const IBottomNavigationBarWidget({Key? key}) : super(key: key);
}

class DiscoverView extends StatefulWidget {
  final IDiscoverViewModel _viewModel;
  final IBottomNavigationBarWidget _bottomNavigationBarWidget;
  const DiscoverView(this._viewModel, this._bottomNavigationBarWidget,
      {Key? key})
      : super(key: key);

...

bottomNavigationBar: widget._bottomNavigationBarWidget,

Du coup, dans mon router, je peux placer mon vrai widget mais j’obtiens une erreur bizarre :

final discoverView = DiscoverView(
            DiscoverViewModel(this, _discoverFungusList),
            BottomNavigationBarWidget(BottomNavigationBarViewModel(this)));

J’en suis là … :smiley:

Je me suis replongé dans mon code et en fait je suis juste pas très rigoureux.
En réalité, lorsque je fait

bottomNavigationBar:
BottomNavigationBarWidget(0,BottomNavBarViewModel(widget._bottomNavBarRouter)),

Mon BottomNavBarViewModel et mon _bottomNavBarRouter sont des interfaces (mal nommées je vais corriger :slight_smile: )
Du coup j’appelle mon widget bottomNavBar et son constructeur prend des abstractions. Je n’ai cependant pas d’interface pour le widget en lui même mais je ne suis pas sur que ce soit vraiment nécessaire. @mbritto je crois que nous avons besoin d’un expert :smiley:
Merci d’avance
Bonne journée

Si je comprends bien, tu as quand même un routeur par vue et un dernier que tu crées au démarrage ?

Pour compléter mon code, je déclare bien que mon widget respecte le protocole énoncé :

class BottomNavigationBarWidget extends IBottomNavigationBarWidget

Du coup, je me demande si c’est bien un extends que je dois positionner … J’ai toujours du mal avec les extends, with et implements :slight_smile:

Pour une raison que j’ignore, VSCode me joue parfois des tours … J’ai juste commenté et décommenté certaines lignes de code pour revenir à la dernière version sans modification et je n’ai plus de problème de compilation oO
Je pensais avoir enfin résolu mon problème et :
Capture d’écran 2022-12-21 à 17.23.01

Je continue mes investigations :smiley:

Bon, finalement j’avais une petite coquille dans mon navigation_delegate et je ne créais aucune pageList.

Donc le code que j’avais posté était bien correcte :slight_smile:

@mbritto, ça mériterait presque une ou deux vidéos d’exemple pour implémenter cette BottomNavigationBar !!

Mais d’un autre côté, ça m’a bien remué les ménages. Merci à tous les deux pour votre aide, je vais pouvoir passer à autre chose.

Bonnes fêtes de fin d’année !!

Hello world ! Et bonne année tout le monde :slight_smile:

j’essaye également de mettre en place une navigation ‹ bottom bar › avec le router delegate (2.0).
L’idée serait d’avoir une barre de navigation disponible et évolutive (si l’utilisateur est connecté, il aurait la possibilité d’accéder à des actions supplémentaires).

Pour le moment, en suivant le cours ‹ architecture › et vos indications (dans ce fil), j’ai réussi à mettre en place une navigation avec un ‹ bottom bar › identique dans chaque vue.
Ce qui veut dire que je duplique ce widget et sa méthode onTap à chaque fois :frowning:

Ca fonctionne mais ce n’est évidemment pas très propre.

Quand j’ai voulu extraire ce code dans un widget séparé, je ne suis, malheureusement, pas arrivé à relier son viewModel avec le routeur.

J’y suis presque mais j’aurais besoin d’un coup de pouce :slight_smile:

Merci beaucoup :slight_smile:

Hello @morey,

Tu pourrais nous pousser ton code, qu’on y jette un coup d’oeil ?

Avec le temps que j’y ai passé, je devrais pouvoir trouver là où tu bloques !

Merci pour ta proposition @Tazooou :slight_smile:

Du coup … en me creusant juste un peu plus la tête je suis arrivé à mes fins :slight_smile:

J’ai déclaré une classe BottomMenu :

import 'package:flutter/material.dart';

abstract class BottomRouter {
  accueil();
  displaySettings();
}

class BottomMenu extends StatelessWidget {
  final BottomRouter router;
  const BottomMenu({Key? key, required this.router}) : super(key: key);

  _onClicked(int value) {
    if (value == 0) {
      router.accueil();
    }
    if (value == 2) {
      router.displaySettings();
    }
  }

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      items: const [
        BottomNavigationBarItem(
          icon: Icon(Icons.home),
          label: 'Accueil',
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.new_releases),
          label: 'test',
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.question_answer),
          label: 'paramètres',
        )
      ],
      onTap: _onClicked,
      selectedItemColor: Colors.red[800],
      backgroundColor: Colors.black,
      unselectedItemColor: Colors.white
    );
  }
}

J’implémente la classe BottomRouter dans mon NavigationDelegate :

class NavigationDelegate extends RouterDelegate<NavigationPath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<NavigationPath>
    implements UserHomeRouter, LoginRouter, SettingsRouter, BottomRouter

Et dans les appels de mes vues, je rajoute le router (this) juste avant le viewmodel :

      final loginScreen = LoginScreen(
        this,
        LoginViewModel(LoginUseCases(), this),
      );
//
        final settingsScreen =
            SettingsScreen(this, SettingsViewModel(user!, this));

Et voici ma vue Settings par exemple :
(

import 'package:flutter/material.dart';

import '../navigation/bottom_menu.dart';
import 'settings_viewmodel.dart';

class SettingsScreen extends StatelessWidget {
  final SettingsViewModel _viewModel;
  final BottomRouter _router;
  const SettingsScreen(this._router, this._viewModel, {Key? key})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Center(child: Text("Pub & Moi")),
      ),
      body: Text(_viewModel.email),
      bottomNavigationBar: BottomMenu(
        router: _router),
    );
  }
}

Je ne sais pas si c’est fait dans les règles de l’art mais ca à l’air de tourner :slight_smile:

Et après ca, rien de plus simple que :

  • d’extraire le code du BottomMenu dans un viewModel
  • d’afficher ou cacher certains boutons dans le bottom Nav Bar
  • de rajouter le currentIndex pour savoir où l’on se trouve

:slight_smile:

Merci beaucoup pour ce post qui m’a permis d’y arriver :slight_smile:

1 « J'aime »

C’est exactement ça !

Par contre, j’ai une seule fonction qui ramène le clic sur la NavBar au viewmodel et au router. Je préfère masquer toute la logique data de la vue.
Je remonte mon currentIndex jusqu’au router et je le sauvegarde pour reconstruire mes vues et mon état à la volée.
Bravo et happy coding :wink:

2 « J'aime »