1

I found some answers about this [here, here] but none of them completely answer my question.

I'm going to be using the package provider to describe my question because it greatly reduces the boilerplate code.

What I want to do is to inject a dependency when (and only when) a route is called. I can achieve that by doing something like this on onGenerateRoute:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Routes Demo',
        initialRoute: '/',
        onGenerateRoute: (settings) {
          switch (settings.name) {
            case '/':
              return MaterialPageRoute(builder: (_) => HomePage());
            case '/firstPage':
              return MaterialPageRoute(
                builder: (_) => Provider(
                      builder: (_) => MyComplexClass(),
                      child: FirstPage(),
                    ),
              );
            case '/secondPage':
              return MaterialPageRoute(builder: (_) => SecondPage());
            default:
              return MaterialPageRoute(
                builder: (_) => Scaffold(
                      body: Center(
                        child: Text('Route does not exists'),
                      ),
                    ),
              );
          }
        });
  }
}

class MyComplexClass {
  String message = 'UUUH I am so complex';
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          RaisedButton(
              child: Text('Go to first page'),
              onPressed: () {
                Navigator.pushNamed(context, '/firstPage');
              }),
        ],
      ),
    ));
  }
}

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final myComplexClass = Provider.of<MyComplexClass>(context);
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Center(
              child: Text(myComplexClass.message),
            ),
            RaisedButton(
              child: Text('Go to second page'),
              onPressed: () {
                Navigator.pushNamed(context, '/secondPage');
              },
            )
          ],
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final myComplexClass = Provider.of<MyComplexClass>(context);
    return Scaffold(
      body: Center(
        child: Text(myComplexClass.message),
      ),
    );
  }
}

This works fine for '/firstPage', but as soon as I push another route from inside 'firstPage' the context is lost and I loose access to MyComplexClass, since the navigator stays at the top of the tree together with MaterialApp the next route will loose the context where MyComplexClass was injected, I cannot manage to find a elegant solution to this.

This is the navigator stack we end up with: navigation stack

As we can see SecondPage is not a child of Provider, hence the problem.

I don't want to inject all dependencies I have all at once on top of MainApp, I want to inject them as they're needed.
I considered creating new navigators each time I need another "fold", but that seems to become really messy very quickly.

How do I solve this issue?

lgvaz
  • 452
  • 6
  • 17
  • `"I cannot manage to find a elegant solution to this."` - for what exactly? cannot you access your `Inherited`Widget` from the second route or what? – pskink Jun 10 '19 at 12:55
  • Yes, once I push a new route inside '/foo' it loses the context that the `InheritedWidget` was on. – lgvaz Jun 10 '19 at 16:11
  • so you mean that `inheritFromWidgetExactType` method returns null? if so, post your code – pskink Jun 10 '19 at 16:16
  • I edited the question and added the complete code example so my question becomes clearer. – lgvaz Jun 10 '19 at 16:43
  • so make your `Provider` as a parent of `MyApp` for example (or as a parent of `MaterialApp`) – pskink Jun 10 '19 at 17:03
  • This is what I described in the question what I **don't** want to do. Currently I have a bunch of providers in my code and it quickly becomes out of hand to just inject everything from the `MaterialApp`. – lgvaz Jun 10 '19 at 17:07
  • What I want to do is to create `MyComplexClass` only when pushing for `FirstPage`, and I want to be able to access this class in the pages that `FirstPage` is able to push. – lgvaz Jun 10 '19 at 17:30

1 Answers1

1

In the following examples, both the route / and /login can access Provider.of<int>, but the route /other can't.

There are two solutions:

  • A StatefulWidget combined with a Provider.value that wraps each route.

Example:

class Foo extends StatefulWidget {
  @override
  _FooState createState() => _FooState();
}

class _FooState extends State<Foo> {
  int myState = 42;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        '/': (_) => Provider.value(value: myState, child: Home()),
        '/login': (_) => Provider.value(value: myState, child: Login()),
        '/other': (_) => Other(),
      },
    );
  }
}
  • A private placeholder type that wraps MaterialApp combined with ProxyProvider:

Example:

class _Scope<T> {
  _Scope(this.value);

  final T value;
}

// ...

Widget build(BuildContext context) {
  Provider(
    builder: (_) => _Scope(42),
    child: MaterialApp(
      routes: {
        '/': (_) => ProxyProvider<_Scope<int>, int>(
            builder: (_, scope, __) => scope.value, child: Home()),
        '/login': (_) => ProxyProvider<_Scope<int>, int>(
            builder: (_, scope, __) => scope.value, child: Login()),
        '/other': (_) => Other(),
      },
    ),
  );
}
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432