How to dispose of a Stream controller Flutter - flutter

I was testing on how to use streams and stream controllers, using a basic Bloc pattern but I still don't understand on how to dispose of them properly whether using it to update the UI or triggering other Blocs.
I noticed other peoples code that they dispose of it on the stateless or stateful widget, but I was wondering if it can also be done on the Bloc class
Below is a snippet of a basic Bloc and a stream builder for the stateless widget
CounterBloc.dart
import 'dart:async';
class CounterBloc {
int _counter = 10;
StreamController<int> _countController = StreamController<int>();
Stream<int> get counterStream => _countController.stream;
StreamSink<int> get counterSink => _countController.sink;
void incrementCounter() {
_counter++;
counterSink.add(_counter);
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:test_bloc/counter_bloc.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
final counterClass = CounterBloc();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home Page"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
StreamBuilder(
stream: counterClass.counterStream,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData) {
print(snapshot.data.toString() + ' this is test');
return Text(snapshot.data.toString());
} else {
return CircularProgressIndicator();
}
},
)
],
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () {
counterClass.incrementCounter();
},
tooltip: 'Increment',
child: Icon(Icons.add),
)
],
));
}
}

You may want to create a method say closeStream in your Bloc class.
Future<void> closeStream() => _streamController?.close();
Convert your HomePage to StatefulWidget, override dispose() method, and use
#override
void dispose() {
counterClass?.closeStream();
super.dispose();
}

Related

Close screen when a value changes in Riverpod

How do I close a screen (call Navigator.pop) when a certain value changes?
I am using riverpod to watch for the values.
Use the WidgetRef.listen to listen to state changes. From the docs:
Listen to a provider and call listener whenever its value changes.
This is useful for showing modals or other imperative logic.
The code is going to be something like this:
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
#override
Widget build(context, ref) {
ref.listen(theProvider, (previous, next) {
if (isTheEnd(next)) {
Navigator.of(context).pop();
}
});
...
Here's a complete example:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
#override
Widget build(context, ref) {
ref.listen(counterProvider, (previous, next) {
if (next % 2 == 0) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('Value is even: $next'),
),
);
}
});
final counter = ref.watch(counterProvider);
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'You have pushed the button this many times:',
),
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
final counterProvider = StateNotifierProvider<Counter, int>((ref) {
return Counter();
});
class Counter extends StateNotifier<int> {
Counter() : super(0);
void increment() => state++;
}
You need to create some kind of Function that handle that. The function required a build context to get the Navigator and you could call the Navigator.of(context).pop or Navigator.pop(context) when you want.
onChangeValue(BuildContext context, dynamic value){
if(value!=null){
Navigator.of(context).pop();
}
}

Flutter using a changenotifier with named routes

I've followed some online tutorials and managed to implement ChangeNotifier when the app has a single route however none of these explain how to implement this when the app has more than one route (screen) which is rather the point!
I made an attempt to figure this out myself but when the app runs in the emulator I get a blank screen.
/* main.dart */
import 'dart:collection'; // used in test.dart
import 'package:flutter/foundation.dart'; // used in test.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Person extends ChangeNotifier {
Person({this.firstName});
String firstName = '';
void updateName(String n) {
this.firstName = n;
notifyListeners();
}
}
void main() {
runApp(
Provider(
create: (_) => Person(firstName: 'Mark'),
child: MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeRoute(),
'/second': (context) => SecondRoute(),
'/third': (context) => ThirdRoute(),
},
),
)
);
}
class HomeRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Provider.of<Person>(context).firstName),
),
body: Center(
child: new IconButton(
icon: new Icon(Icons.favorite, color: Colors.redAccent),
iconSize: 70.0,
onPressed: () {
Navigator.of(context).pushNamed("/second");
}
),
),
);
}
}
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Page'),
),
body: Center(
child: Text(Provider.of<Person>(context).firstName),
),
);
}
}
class ThirdRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Third Page'),
),
body: Center(
child: Text('Third Route'),
),
);
}
}
The project builds and runs with no error messages which has stumped me.
Any thoughts?
Code developed in FlutLab

Issue with StreamBuilder and streams in Flutter (receiving duplicated data)

today I have faced a problem with streams and StreamBuilder.
The problem is the following:
If you have multiple StreamBuilder widgets listening to the same stream, and you add data into its sink, this data will go out through the stream the amount of StreamBuilder listeners that you have, in other words:
If you have one StreamController (or BehaviorSubject) , k amount of widgets of type StreamBuilder, and you try to do StreamController.sink.add(event), this event will go out k times trough the stream, one per StreamBuilder.
Is that an expected behavior (expected behavoir = input an event and listening just once from the other side independentlly of the amount of listeners) ? I was able to "fix" this encapsulating almost all the widget tree into one StreamBuilder, but this isnt as optimal as the first approach because of you are rendering the whole tree instead of some little node widgets.
Here I left some code to test it if you want (This code is a modification of flutter create project_name project).
Thank you!
(P.D: This works well if you just listen the streams without StreamBuilder, i.e: streamController.stream.listen..)
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:rxdart/subjects.dart';
class MyAppBloc with ChangeNotifier {
int _currentIndex;
BehaviorSubject<bool> _controller;
MyAppBloc() {
_currentIndex = 0;
_controller = BehaviorSubject<bool>();
}
Stream<int> get currentIndex => _controller.stream.map<int>((event) {
print('[event: $event]');
_currentIndex++;
return _currentIndex;
});
StreamSink<bool> get increment => _controller.sink;
void close() {
_controller.close();
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test_project/bloc/my_app_bloc.dart';
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
Widget leadingBuilder(MyAppBloc bloc) {
return StreamBuilder<int>(
initialData: 0,
stream: bloc.currentIndex,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
print('[leadingBuilderSnapshot: $snapshot]');
return Text(snapshot.data.toString());
},
);
}
StreamBuilder<int> counterBuilder(MyAppBloc bloc) {
return StreamBuilder<int>(
initialData: 0,
stream: bloc.currentIndex,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
print('[counterBuilderSnapshot: $snapshot]');
return Text(
snapshot.data.toString(),
style: Theme.of(context).textTheme.headline4,
);
},
);
}
#override
Widget build(BuildContext context) {
print('[build]');
final _bloc = Provider.of<MyAppBloc>(context);
return Scaffold(
appBar: AppBar(
leading: Container(
width: 30,
height: 30,
alignment: Alignment.center,
child: leadingBuilder(_bloc),
),
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
StreamBuilder<int>(
initialData: 0,
stream: _bloc.currentIndex,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return Text('${snapshot.data}');
},
),
Text(
'You have pushed the button this many times:',
),
counterBuilder(_bloc),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _bloc.increment.add(true),
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Because currentIndex is a getter and you are using map(), a new stream will be created every time
you call bloc.currentIndex and StreamBuilder will listen to it.
So in original code , there are actually 1 StreamControlller, and k Streams. (k: number of StreamBuilder)
To solve your problem, you can create an eventController, and listen to it inside a bloc to execute your logic. (eventStream is listened from only bloc itself, it will be created just once)
for example:
class MyAppBloc {
MyAppBloc() {
_eventController.listen((event) {
print('[event: $event]');
_indexController.add(currentIndex.value + 1);
});
}
final _indexController = BehaviorSubject<int>.seeded(0);
final _eventController = PublishSubject<bool>();
ValueStream<int> get currentIndex => _indexController.stream;
StreamSink<bool> get increment => _eventController.sink;
void close() {
_indexController?.close();
_eventController?.close();
}
}
You have to use 2 streams/sink and put the increment outside the get stream.
import 'dart:async';
import 'package:rxdart/subjects.dart';
class Bloc {
int _counter = 0;
Bloc() {
_controller.stream.listen(_incrementStream);
}
final _counterStream = BehaviorSubject<int>.seeded(0);
Stream get presentCounter => _counterStream.stream;
Sink get _addValue => _counterStream.sink;
StreamController _controller = BehaviorSubject<bool>();
StreamSink<bool> get incrementCounter => _controller.sink;
void _incrementStream(data) {
_counter += 1;
_addValue.add(_counter);
}
void dispose() {
_counterStream.close();
_controller.close();
}
}
import 'package:flutter/material.dart';
import 'package:increment/bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Bloc _bloc = Bloc();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
StreamBuilder<int>(
stream: _bloc.presentCounter,
builder: (context, snapshot) {
return Text(
'${snapshot.data}',
style: Theme.of(context).textTheme.headline4,
);
}),
SizedBox(
height: 60,
),
StreamBuilder<int>(
stream: _bloc.presentCounter,
builder: (context, snapshot) {
return Text(
'${snapshot.data}',
style: Theme.of(context).textTheme.headline4,
);
}),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_bloc.incrementCounter.add(true);
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}

No able to get the latest state of an object using provider

I am trying get the latest state of the name variable from the Home class, but I am getting a null instead of the value Tenzin Nyidon. I initialize the name variable inside the build method of the MyHomePage as you can see, and set the notifyListeners() method (which notify all listeners) inside the onPress of Navigate button.
main.dart:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<MyHomePage>(builder: (_) => MyHomePage(),)
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatelessWidget with ChangeNotifier {
String name;
#override
Widget build(BuildContext context) {
//* Here I intialized the name variabel
name = "Tenzin Nyidon";
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(name),
RaisedButton(
child: Text("Navigate"),
onPressed: (){
//* notify all listener
notifyListeners();
Navigator.of(context).push(
MaterialPageRoute(
builder: (_){
return Home();
}
)
);
},
)
],
),
),
);
}
}
home.dart:
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
var provider = Provider.of<MyHomePage>(context);
return Scaffold(
body: Center(
//! I am getting a null value here
child: Text("name from the main.dart: ${provider.name}"),
),
);
}
}
try to not create a provider with a stateless widget because it can't update its state.
and also you have to create a method to handle changes and then call changeNotifier();
such as:
class MyProvider extends ChangeNotifier {
String name;
void changeName(String newValue){
name = newValue;
}
}
and then call in provider:
To change value:
Provider.of<MyProvider>(context).changeName(newValue);
To read it:
Provider.of<MyProvider>(context).name;

Navigator operation requested with a context that does not include a Navigator

I'm trying to start a new screen within an onTap but I get the following error:
Navigator operation requested with a context that does not include a
Navigator.
The code I am using to navigate is:
onTap: () { Navigator.of(context).pushNamed('/settings'); },
I have set up a route in my app as follows:
routes: <String, WidgetBuilder>{
'/settings': (BuildContext context) => new SettingsPage(),
},
I've tried to copy the code using the stocks sample application. I've looked at the Navigator and Route documentation and can't figure out how the context can be made to include a Navigator. The context being used in the onTap is referenced from the parameter passed into the build method:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
SettingsPage is a class as follows:
class SettingsPage extends Navigator {
Widget buildAppBar(BuildContext context) {
return new AppBar(
title: const Text('Settings')
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: buildAppBar(context),
);
}
}
TLDR: Wrap the widget which needs to access to Navigator into a Builder or extract that sub-tree into a class. And use the new BuildContext to access Navigator.
This error is unrelated to the destination. It happens because you used a context that doesn't contain a Navigator instance as parent.
How do I create a Navigator instance then ?
This is usually done by inserting in your widget tree a MaterialApp or WidgetsApp. Although you can do it manually by using Navigator directly but less recommended. Then, all children of such widget can access NavigatorState using Navigator.of(context).
Wait, I already have a MaterialApp/WidgetsApp !
That's most likely the case. But this error can still happens when you use a context that is a parent of MaterialApp/WidgetsApp.
This happens because when you do Navigator.of(context), it will start from the widget associated to the context used. And then go upward in the widget tree until it either find a Navigator or there's no more widget.
In the first case, everything is fine. In the second, it throws a
Navigator operation requested with a context that does not include a Navigator.
So, how do I fix it ?
First, let's reproduce this error :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
),
);
}
}
This example creates a button that attempts to go to '/' on click but will instead throw an exception.
Notice here that in the
onPressed: () => Navigator.pushNamed(context, "/"),
we used context passed by to build of MyApp.
The problem is, MyApp is actually a parent of MaterialApp. As it's the widget who instantiate MaterialApp! Therefore MyApp's BuildContext doesn't have a MaterialApp as parent!
To solve this problem, we need to use a different context.
In this situation, the easiest solution is to introduce a new widget as child of MaterialApp. And then use that widget's context to do the Navigator call.
There are a few ways to achieve this. You can extract home into a custom class :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHome()
);
}
}
class MyHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
);
}
}
Or you can use Builder :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) => Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
),
),
);
}
}
Hy guys, i have the same problem. This is occur for me. The solution what i found is very simple. Only what i did is in a simple code:
void main() {
runApp(MaterialApp(
home: YOURAPP() ,
),
);
}
I hope was useful.
Make sure your current parent widget not with same level with MaterialApp
Wrong Way
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Title'),
),
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: RaisedButton(
onPressed: () {
//wrong way: use context in same level tree with MaterialApp
Navigator.push(context,
MaterialPageRoute(builder: (context) => ScanScreen()));
},
child: const Text('SCAN')),
)),
),
);
}
}
Right way
void main() => runApp(MaterialApp(
title: "App",
home: HomeScreen(),
));
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Title'),
),
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: RaisedButton(
onPressed: () {
//right way: use context in below level tree with MaterialApp
Navigator.push(context,
MaterialPageRoute(builder: (context) => ScanScreen()));
},
child: const Text('SCAN')),
)),
);
}
}
Just like with a Scaffold you can use a GlobalKey. It doesn't need context.
final _navKey = GlobalKey<NavigatorState>();
void _navigateToLogin() {
_navKey.currentState.popUntil((r) => r.isFirst);
_navKey.currentState.pushReplacementNamed(LoginRoute.name);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: _navKey,
...
);
}
I set up this simple example for routing in a flutter app:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyHomePage(),
routes: <String, WidgetBuilder>{
'/settings': (BuildContext context) => new SettingsPage(),
},
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('TestProject'),
),
body: new Center(
child: new FlatButton(
child: const Text('Go to Settings'),
onPressed: () => Navigator.of(context).pushNamed('/settings')
)
)
);
}
}
class SettingsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('SettingsPage'),
),
body: new Center(
child: new Text('Settings')
)
);
}
}
Note, that the SettingsPage extends StatelessWidget and not Navigator. I'm not able to reproduce your error.
Does this example help you in building your app? Let me know if I can help you with anything else.
You should rewrite your code in main.dart
FROM:
void main() => runApp(MyApp());
TO
void main() {
runApp(MaterialApp(
title: 'Your title',
home: MyApp(),));}
The point is to have the home property to be your first page
this worked for me, I hope it will help someone in the future
A complete and tested solution:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:my-app/view/main-view.dart';
class SplashView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: Builder(
builder: (context) => new _SplashContent(),
),
routes: <String, WidgetBuilder>{
'/main': (BuildContext context) => new MainView()}
);
}
}
class _SplashContent extends StatefulWidget{
#override
_SplashContentState createState() => new _SplashContentState();
}
class _SplashContentState extends State<_SplashContent>
with SingleTickerProviderStateMixin {
var _iconAnimationController;
var _iconAnimation;
startTimeout() async {
var duration = const Duration(seconds: 3);
return new Timer(duration, handleTimeout);
}
void handleTimeout() {
Navigator.pushReplacementNamed(context, "/main");
}
#override
void initState() {
super.initState();
_iconAnimationController = new AnimationController(
vsync: this, duration: new Duration(milliseconds: 2000));
_iconAnimation = new CurvedAnimation(
parent: _iconAnimationController, curve: Curves.easeIn);
_iconAnimation.addListener(() => this.setState(() {}));
_iconAnimationController.forward();
startTimeout();
}
#override
Widget build(BuildContext context) {
return new Center(
child: new Image(
image: new AssetImage("images/logo.png"),
width: _iconAnimation.value * 100,
height: _iconAnimation.value * 100,
)
);
}
}
As per this comment If your navigator is inside Material context navigator push will give this error. if you create a new widget and assign it to the material app home navigator will work.
This won't work
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Title"),
),
body: new Center(child: new Text("Click Me")),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
backgroundColor: Colors.orange,
onPressed: () {
print("Clicked");
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new AddTaskScreen()),
);
},
),
),
);
}
}
This will work
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomeScreen());
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Title"),
),
body: new Center(child: new Text("Click Me")),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
backgroundColor: Colors.orange,
onPressed: () {
print("Clicked");
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new AddTaskScreen()),
);
},
),
);
}
}
I was facing the same problem and solved by removing home from MaterialApp and use initialRoute instead.
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: '/',
routes: {
'/': (context) => MyApp(),
'/settings': (context) => SettingsPage(),
},
);
And
onTap: () => {
Navigator.pushNamed(context, "/settings")
},
It is Simple
instead using this normal code
`runApp(BasicBankingSystem());`
wrap it with MaterialApp
runApp(MaterialApp(home: BasicBankingSystem()));
It happens because the context on the widget that tries to navigate is still using the material widget.
The short answer for the solution is to :
extract your widget
that has navigation to new class so it has a different context when calling the navigation
When your screen is not navigated from other screen,you don't initially have access to the navigator,Because it is not instantiated yet.So in that case wrap your widget with builder and extract context from there.This worked for me.
builder: (context) => Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
You ca use this plugin
https://pub.dev/packages/get/versions/2.0.2
in The MaterialApp assign property navigatorKey: Get.key,
MaterialApp(
navigatorKey: Get.key,
initialRoute: "/",
);
you can access Get.toNamed("Your route name");
Change your main function example:
void main() {
runApp(
MaterialApp(
title: 'Your title',
home: MyApp(),
)
);
}
use this
void main() {
runApp(MaterialApp(debugShowCheckedModeBanner: false, home: MyApp()),);
}
instead of this
void main() {runApp(MyApp());}
Wrap with materialapp
reproduce code
import 'dart:convert';
import 'package:flutter/material.dart';
void main() {
// reproduce code
runApp(MyApp());
// working switch //
// runApp(
//
// MaterialApp(debugShowCheckedModeBanner: false, home: MyApp()),);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body:
Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 100,
width: 100,
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => IntroPage(Isscar4: true)),
);
},
child: RichText(
text: TextSpan(
text: 'CAR',
style: TextStyle(
letterSpacing: 3,
color: Colors.white,
fontWeight: FontWeight.w400),
children: [
TextSpan(
text: '4',
style: TextStyle(
fontSize: 25,
color: Colors.red,
fontWeight: FontWeight.bold))
],
)),
),
),
],
),
SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 100,
width: 100,
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => IntroPage(Isscar4: false)),
);
},
child: RichText(
text: TextSpan(
text: 'BIKE',
style: TextStyle(
letterSpacing: 3,
color: Colors.white,
fontWeight: FontWeight.w400),
children: [
TextSpan(
text: '2',
style: TextStyle(
fontSize: 25,
color: Colors.red,
fontWeight: FontWeight.bold))
],
)),
),
),
],
)
])));
}
MaterialApp Swithwidget(istrue) {
return MaterialApp(
home: Scaffold(
body: IntroPage(
Isscar4: istrue,
),
),
);
}
}
class Hi extends StatelessWidget {
const Hi({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Text("df"),
);
}
}
class IntroPage extends StatelessWidget {
final Isscar4;
IntroPage({
Key? key,
required this.Isscar4,
}) : super(key: key);
List<Widget> listPagesViewModel = [];
List<IntroModel> models = [];
#override
Widget build(BuildContext context) {
List<dynamic> intro = fetchIntroApi(Isscar4);
intro.forEach((element) {
var element2 = element as Map<String, dynamic>;
var cd = IntroModel.fromJson(element2);
models.add(cd);
});
models.forEach((element) {
listPagesViewModel.add(Text(""));
});
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Container(),
));
}
List fetchIntroApi(bool bool) {
var four = bool;
if (four) {
var data =
'[ {"name_Title": "title name1","description": "description1"}, {"name_Title": "title name2","description": "description2"}, {"name_Title": "title name3","description": "description3"}, {"name_Title": "title name4","description": "description4"} ]';
return json.decode(data);
} else {
var data =
'[ {"name_Title": "title name","description": "description1"}, {"name_Title": "title name2","description": "description2"}, {"name_Title": "title name3","description": "description3"} ]';
return json.decode(data);
}
}
}
class IntroModel {
String? nameTitle;
String? description;
IntroModel({this.nameTitle, this.description});
IntroModel.fromJson(Map<String, dynamic> json) {
nameTitle = json['name_Title'];
description = json['description'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name_Title'] = this.nameTitle;
data['description'] = this.description;
return data;
}
}
class Splash extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Splash Screen',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: MyState(),
debugShowCheckedModeBanner: false,
);
}
void main() {
runApp(Splash());
}
class MyState extends StatefulWidget{
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyState> {
#override
void initState() {
super.initState();
Timer(Duration(seconds: 3),
()=>Navigator.pushReplacement(context,
MaterialPageRoute(builder:
(context) =>
Login()
)
)
);
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center ,
children: [
Container(
child:
Image.asset("assets/images/herosplash.png"),
),
],
),
);
}
}
Builder(
builder: (context) {
return TextButton(
child: const Text('Bearbeiten'),
onPressed:(){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const gotothesiteyouwant()),
);
});
}
),
Here, all you need is to make MaterialApp the parent of your Build. This is because the context that you've used to navigate to a different screen is finding a MaterialApp or a WidgetApp as a parent of the build.
And Since in your case, the situation is the opposite, therefore you need to modify it by either calling a new Stateless widget the parent of is the MaterialApp or by simply using a Builder as home: Builder in MaterialApp.
Hope this would help!