Dmitry Bubnenkov
Dmitry Bubnenkov

Reputation: 9869

How to use Navigator from class that do not have context?

I wrote small app that print 1, 2, 3, 42 on splash screen, then wait 2 seconds and complete switching to HomePage. The problem that I can't figure out how to use Navigator outside of Widget, because it's require context.

Here is my code:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

Stream<int> stream;

StreamController<int> myStreamController = StreamController();

void main() {
  MyClass myClass = MyClass();

  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft])
      .then((_) {
    runApp(new MyApp());
  });
}

class MyClass {
  MyClass() {
    stream = Stream<int>.periodic(Duration(seconds: 1), (t) => t + 1).take(3);
    myStreamController.addStream(stream).then(
      (done) {
      Future.delayed(Duration(seconds: 2), () { // wait 2 seconds before Showing HomePage
            myStreamController.addStream( Stream.value(42) ); 

           // Navigator.push(context, route)  // But we do not have context in this class!!

          });
      }
    );
    // stream.pipe(myStreamController.sink);

  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(title: "Hello", routes: {
      '/': (context) => SplashScreen(),
      '/home': (context) => HomePage(),
    });
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context)
  {
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        child: Text("Hello World, Home Page"),
      ),
    );
  }
}

class SplashScreen extends StatefulWidget {
  @override
  SplashScreenState createState() => SplashScreenState();
}

class SplashScreenState extends State<SplashScreen> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: Center(
          child: Container(
            child: DefaultTextStyle(
              style: TextStyle(
                fontSize: 24,
                color: Colors.black54
              ),
              child: StreamBuilder(
                  stream: myStreamController.stream,
                  builder: (context, snapshot) {
                    if (snapshot.hasData) {
                      switch (snapshot.data) {
                        case 1:
                          return Text("${snapshot.data}");
                        case 2:
                          return Text("${snapshot.data}");
                        case 3:
                          return Text("${snapshot.data}");
                        default:
                          return Text("${snapshot.data}");
                      }
                    } else
                      return CircularProgressIndicator();
                  })
            )
                  ),
        ));

    // return
  }
}


class DigitProgressIndicator extends StatelessWidget
{
  String text;
  DigitProgressIndicator(this.text);

  @override
  Widget build(BuildContext context)
  {
    return Text(text);
  }
}

Upvotes: 1

Views: 1508

Answers (2)

Dmitry Bubnenkov
Dmitry Bubnenkov

Reputation: 9869

I found two ways to do to do switching screen from business logic. The first one (I think best).

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

Stream<int> stream;

StreamController<int> myStreamController = StreamController.broadcast();

void main() {
  MyClass myClass = MyClass();

  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft])
      .then((_) {
    runApp(new MyApp());
  });
}


class MyClass {
  MyClass() {
    stream = Stream<int>.periodic(Duration(seconds: 1), (t) => t + 1).take(3);
    myStreamController.addStream(stream).then(
      (done) {
      Future.delayed(Duration(seconds: 2), () { // wait 2 seconds before Showing HomePage
            myStreamController.addStream( Stream.value(42) ); 

          });
      }
    );


  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(title: "Hello", 
    routes: 
      {
        '/': (context) => SplashScreen(),
        '/home': (context) => HomePage(),
      },

    );

  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context)
  {
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        child: Text("Hello World, Home Page"),
      ),
    );
  }
}

class SplashScreen extends StatefulWidget {
  @override
  SplashScreenState createState() => SplashScreenState();
}

class SplashScreenState extends State<SplashScreen> {

  @override
  void initState() {
    myStreamController.stream.listen((data) {
      if (data == 42)
      {
        Navigator.pushNamed(context, '/home');
      }
    });

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: Center(
          child: Container(
            child: DefaultTextStyle(
              style: TextStyle(
                fontSize: 24,
                color: Colors.black54
              ),
              child: StreamBuilder(
                  stream: myStreamController.stream,
                  builder: (context, snapshot) {
                    if (snapshot.hasData) {
                      switch (snapshot.data) {
                        case 1:
                          return Text("${snapshot.data}");
                        case 2:
                          return Text("${snapshot.data}");
                        case 3:
                          return Text("${snapshot.data}");
                        default:
                          return Text("${snapshot.data}");                          
                      }
                    } else
                      return CircularProgressIndicator();
                  })
            )
                  ),
        ));

  }
}

The second way is described here http://stacksecrets.com/flutter/navigation-when-there-is-no-context

Upvotes: 1

Ali Qanbari
Ali Qanbari

Reputation: 3111

Just move it to where you display your 42:

class SplashScreenState extends State<SplashScreen> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: Center(
          child: Container(
              child: DefaultTextStyle(
                  style: TextStyle(
                      fontSize: 24,
                      color: Colors.black54
                  ),
                  child: StreamBuilder(
                      stream: myStreamController.stream,
                      builder: (context, snapshot) {
                        if (snapshot.hasData) {
                          switch (snapshot.data) {
                            case 1:
                              return Text("${snapshot.data}");
                            case 2:
                              return Text("${snapshot.data}");
                            case 3:
                              return Text("${snapshot.data}");
                            default:
                              Navigator.push(context, route);  // But now we have context in this class!!
                              return Text("${snapshot.data}");

                          }
                        } else
                          return CircularProgressIndicator();
                      })
              )
          ),
        ));

    // return
  }
}

you can also wrap the Navigator with Future.delayed so it will display the 42 for a while.

also a tip:

Navigator is a widget, not a Management class, Navigator.of(context) will get you the nearest ancestor Navigator widget. pushing a scaffold on a Navigator widget means that you are adding a widget on top of it's Stack widget with Scaffold children.

Upvotes: 0

Related Questions