Question
J'essaie de récupérer la référence d'un BLoC, d'un Modèle... via BlocProvider.of(context) ou ScopedModel.of(context) et il renvoie null. Pourquoi cela ?
Réponse
La plupart du temps, cela vient du fait que vous êtes passé d'une Page (= Route) à une autre, en utilisant le Navigator.
Cas d'utilisation typique :
class HomePage extends StatefulWidget {
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late MyBloc bloc;
void initState(){
super.initState();
bloc = MyBloc();
}
void dispose(){
bloc.dispose();
super.dispose();
}
Widget build(BuildContext context){
return BlocProvider<MyBloc>(
bloc: bloc,
child: Scaffold(
appBar: AppBar(
title: Text('Home Page'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPress:(){
Navigator.of(context).pushNamed('/searchPage');
}
),
]
),
body: Container(),
),
);
}
}
class SearchPage extends StatelessWidget {
Widget build(BuildContext context){
MyBloc? bloc = BlocProvider.of<MyBloc>(context);
return Container();
}
}
Ainsi, lorsque vous cliquez sur l'icône Recherche sur la page d'accueil, vous êtes redirigé vers la page de recherche et la ligne : "MyBloc bloc = BlocProvider.of<MyBloc>(context)" renvoie null...
Pourquoi cela ?
Pour comprendre, vous devez garder à l'esprit la façon dont Flutter gère les Pages (= Route)...
Routes = Stack of OverlayEntry()
Lorsque vous instanciez une MaterialApp, en fait, Flutter crée une série de Widgets dont vous n'avez généralement pas à vous soucier et l'un d'entre eux est un Overlay.
Ce Overlay a un Stack comme descendant. Ce " stack " sera utilisé pour "empiler" les pages (quelque chose comme les mettre les unes sur les autres).
Le schéma suivant montre une vue très simplifiée de la hiérarchie, qui résulte du code ci-dessus :
Le PLUS important ici est de se rappeler que les deux Pages empilées NE PARTAGENT RIEN. La seule chose qu'elles aient en commun est fait d'avoir les mêmes ancêtres : la Stack et tout ce qu'il y a au-dessus.
En d'autres termes, les Widgets qui sont descendants d'une page N'ONT AUCUNE VISIBILITÉ SUR AUTRE PAGE (sans utiliser d'astuce...).
Lorsque, depuis la page de recherche, vous utilisez BlocProvider.of<MyBloc>(context), le système recherche un Widget d'un certain type dans la liste de tous ses ANCESTORS (= ancêtres du Widget de la page de recherche).
Dans notre cas (voir schéma simplifié), il ne trouvera jamais le Widget demandé car ce dernier ne figure pas dans la liste de ses ancêtres.
Comment résoudre ce problème ?
Il y a plusieurs possibilités, voyons 3 d'entre elles :
1. Mettre en place un "Top of Everything"
Si le BLoC est global et unique pour toute l'application, rien n'empêche de l'injecter comme ancêtre de la MaterialApp, comme ceci :
void main(){
runApp(Application());
}
class Application extends StatelessWidget {
Widget build(BuildContext context){
return BlocProvider<MyBloc>(
bloc: MyBloc(),
child: MaterialApp(
home: HomePage(),
),
);
}
}
De cette façon, le BLoC sera disponible à partir de PARTOUT dans l'application puisqu'il sera un ancêtre de presque tous les widgets.
2. Faites-en un "Singleton"
Si le BLoC est global et unique pour toute l'application, rien n'empêche d'utiliser un "Singleton", comme suit :
class MyBloc implements BlocBase {
void dispose(){
//...
}
// ------- SINGLETON ------
static final MyBloc _instance = MyBloc._internal();
factory MyBloc() => _instance;
MyBloc._internal();
}
MyBloc myBloc = MyBloc();
Pour utiliser le BLoC, cela devient très facile : il suffit de faire référence à la variable globale "myBloc".
3. Passez le en argument
Une autre possibilité est de passer l'instance du BLoC en argument à une autre page.
Personnellement, je ne suis pas favorable à cette solution car elle crée un couplage serré mais parfois, il est très difficile de l'éviter.
Conclusions
Ce problème est très courant et chaque nouveau développeur de Flutter y est confronté, un jour ou l'autre.
J'espère vraiment que l'on sait maintenant un peu mieux pourquoi cela se produit.