Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Navigation problem with FutureBuilder and MaterialApp

My app has a state which is computed as a Future. For example it includes a theme color, because I want to change the color when I navigate. I try to display a progress indicator while waiting for the data.

But I can't make it work. Either Navigator.push is not working and the app bar is missing, or I have no progress indicator and a route error...

Here is a code snippet.

import 'package:flutter/material.dart';

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

class Test extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _TestState();
}

class _TestState extends State<Test> {
  Future<Color> color = Model.getColor();

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Color>(
      future: color,
      builder: (context, snapshot) {
        if (snapshot.hasError) throw snapshot.error;
        if (snapshot.connectionState != ConnectionState.done) {
          if (false) {
            // Navigation not working. App bar missing.
            return Material(child: Center(child: CircularProgressIndicator()));
          } else {
            // Progress not working. Screen flickering.
            return MaterialApp(home: _buildWait());
          }
        }
        var app = MaterialApp(
          theme: ThemeData(primaryColor: snapshot.data),
          home: _buildPage(),
          // ERROR: The builder for route "/" returned null.
          // routes: {'/': (_) => _buildPage()},
        );
        return app;
      },
    );
  }

  Widget _buildPage() {
    return Builder(
      builder: (context) {
        return Scaffold(
          appBar: AppBar(),
          body: Center(
            child: RaisedButton(
              child: Text('Push'),
              onPressed: () {
                setState(() {
                  color = Model.getColor();
                });
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return Scaffold(appBar: AppBar());
                }));
              },
            ),
          ),
        );
      },
    );
  }
}

Widget _buildWait() {
  return Scaffold(
    appBar: AppBar(title: Text('Wait...')),
    body: Center(child: CircularProgressIndicator()),
  );
}

class Model {
  static final _colors = [Colors.red, Colors.green, Colors.amber];
  static int _index = 0;
  static Future<Color> getColor() {
    return Future.delayed(Duration(seconds: 2), () => _colors[_index++ % _colors.length]);
  }
}

Expected result: when I push the button to navigate to the new route, it should display a progress indicator, and then the new screen with a different theme color.

like image 546
Patrick Avatar asked Oct 29 '25 21:10

Patrick


1 Answers

Now try the following. Try to make a root widget separately, because root widget is always there. you don't want a complete UI route to persist in the memory. Also make next route as a separate widget.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Test(),
    );
  }
}

class Test extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _TestState();
}

class _TestState extends State<Test> {
  Future<Color> color = Model.getColor();

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Color>(
      future: color,
      builder: (context, snapshot) {
        if (snapshot.hasError) return Center(child: Text("An Error Occurred"));
        if (snapshot.connectionState == ConnectionState.waiting) {
          return _buildWait();
        }
        var app = Theme(
          data: ThemeData(primaryColor: snapshot.data),
          child: _buildPage(),
        );
        return app;
      },
    );
  }

  Widget _buildPage() {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: RaisedButton(
          child: Text('Push'),
          onPressed: () {
            Navigator.push(context, MaterialPageRoute(builder: (context) {
              return NextRoute();
            }));
          },
        ),
      ),
    );
  }
}

Widget _buildWait() {
  return Scaffold(
    appBar: AppBar(title: Text('Wait...')),
    body: Center(child: CircularProgressIndicator()),
  );
}

class Model {
  static final _colors = [Colors.red, Colors.green, Colors.amber];
  static int _index = 0;
  static Future<Color> getColor() {
    return Future.delayed(
        Duration(seconds: 2), () => _colors[_index++ % _colors.length]);
  }
}

class NextRoute extends StatefulWidget {
  NextRoute({Key key}) : super(key: key);

  @override
  _NextRouteState createState() => _NextRouteState();
}

class _NextRouteState extends State<NextRoute> {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Color>(
        future: Model.getColor(),
        builder: (context, snapshot) {
          if (snapshot.hasError) {
            return Center(
              child: Text("An Error Occurred"),
            );
          }

          if (snapshot.connectionState == ConnectionState.waiting) {
            return _buildWait();
          }

          return Theme(
            data: ThemeData(primaryColor: snapshot.data),
            child: Scaffold(
              appBar: AppBar(),
            ),
          );
        });
  }
}
like image 154
Taha Malik Avatar answered Oct 31 '25 12:10

Taha Malik