Why Flutter Navigator 2.0 API not work with flutter_bloc? - flutter

I am trying to make navigation by serving pages via BLoC (flutter_bloc 6.1.1).
main.dart:
class MyApp extends StatelessWidget {
List<MaterialPage> _pages = [
MaterialPage( key: ValueKey('Page1'), child: Page1() ),
];
// 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: BlocProvider(
create: (BuildContext context) => NavCubit(),
// WHY PAGES NOT UPDATES HERE FROM BLoC?????
child: BlocListener<NavCubit, NavState>(
listener: (context, state) {
_pages = state.pages;
},
child: Navigator(
pages: _pages,
onPopPage: (route, result) {
if ( !route.didPop(result) ) {
return false;
}
return true;
},
)
)
),
);
}
}
The first page (Page 1) has a button, clicking on which should navigate to the second page (Page2)
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => NavCubit(),
child: Scaffold(
appBar: AppBar( title: Text('Page 1') ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("page 11111111111"),
RaisedButton(
child: Text('Go to: Page 2', style: TextStyle(fontSize: 20)),
onPressed: () {
print("goto page2 btn");
context.read<NavCubit>().navigateTo();
},
),
],
)
),
),
);
}
}
class NavCubit extends Cubit<NavState> {
...
void navigateTo() {
final navState = NavState([MaterialPage( key: ValueKey('Page2'), child: Page2() )]);
debugPrint(navState.toString());
emit(navState);
}
}
But it doesn't happen!
As far as I can see, the BLoC State are updated good. But no screen changes...
SEE PLS:
Here my full test project: https://github.com/morfair/flutter_test_app/tree/master/lib

In Page1 you are creating another NavCubit in the BlocProvider. Therefore you have 2 NavCubits in total. So when you then call context.read<NavCubit>.navigateTo(), you are calling this method on the wrong NavCubit. Try to remove the creation of a second cubit in the BlocProvider in the Page1 build method.

Related

flutter int got to zero

I have a screen with a one button and anotherone with a Container to show a number. I declared a variable in the StatlessWidget class. The button adds 1 to the variable , however after leaving the Class with the container und return to it, I noticed the widgets get updated and my variable loses its value. I have tried initializing it in initState() but it still loses it's value.
import 'package:flutter/material.dart';
import 'package:generator/route_generator.dart';
import 'package:generator/main.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/menu',
onGenerateRoute: RouteGenerator.generateRoute,
);
}
}
class Menu extends StatelessWidget {
int data = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Menu'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed('/second', arguments: data);
},
child: Text('go to the second'),
),
],
),
));
}
}
class FirstPage extends StatelessWidget {
int data = 0;
void eins() {
data = data + 25;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Page'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
data.toString(),
),
RaisedButton(
onPressed: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/second', arguments: data);
},
child: Text('go to the second'),
),
RaisedButton(
child: Text('25'),
onPressed: eins,
)
],
),
));
}
}
class SecondPage extends StatelessWidget {
int data = 0;
SecondPage({Key key, #required this.data}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
data.toString(),
style: TextStyle(fontSize: 20),
),
RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed('/first');
},
child: Text('go to the first'),
),
],
),
));
}
}
another class
import 'package:flutter/material.dart';
import 'package:generator/main.dart';
import './main.dart';
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case '/first':
return MaterialPageRoute(
builder: (_) => FirstPage(),
);
case '/third':
return MaterialPageRoute(
builder: (_) => FirstPage(),
);
case '/menu':
return MaterialPageRoute(
builder: (_) => Menu(),
);
case '/second':
// if (args is int) {
return MaterialPageRoute(
builder: (_) => SecondPage(
data: args,
),
);
//}
// return _errorRoute();
//default:
//return _errorRoute();
}
}
static Route<dynamic> _errorRoute() {
return MaterialPageRoute(builder: (_) {
return Scaffold(
appBar: AppBar(
title: Text('Error'),
),
body: Center(
child: Text('ERROR'),
),
);
});
}
}
The first thing that is weird about your program is that you want to preserve state, in your case a counter variable, but to do that, you select a StatelessWidget. At the very least you will need a StatefulWidget. It's in the name already.
That said, it's not that easy, you may want to look up the different approaches to state management in Flutter: https://flutter.dev/docs/development/data-and-backend/state-mgmt/options
To expand on what #nvoigt said, pick a state management solution instead of passing around arguments from page to page. This way you can keep your widgets stateless, which is preferred but not possible to do what you want to do without a state management solution.
Here's a quick way using GetX state management. This can be done using Provider, RiverPod, Bloc/Cubit...pick your poison.
Here's a new controller class with your data and logic.
class DataController extends GetxController {
int data = 0;
void eins() {
data += 25;
update();
}
}
Then a couple small changes to the rest of your good and you're good to go.
void main() {
Get.put(DataController()); // initializing your controller
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/menu',
onGenerateRoute: RouteGenerator.generateRoute,
);
}
}
class Menu extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Menu'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed('/second');
},
child: Text('go to the second'),
),
],
),
));
}
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller = Get.find<DataController>(); // finding controller
return Scaffold(
appBar: AppBar(
title: Text('First Page'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
GetBuilder<DataController>( // wrap your text in GetBuilder to display variabe
builder: (_) {
return Text(
controller.data.toString(), // accessing variable via controller
);
},
),
RaisedButton(
onPressed: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/second');
},
child: Text('go to the second'),
),
RaisedButton(
child: Text('25'),
onPressed: () {
controller.eins(); // accessing function via controller
}),
],
),
));
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller = Get.find<DataController>(); // finding same instance of controller on new page
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
GetBuilder<DataController>(
builder: (_) {
return Text(
controller.data.toString(),
);
},
),
RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed('/first');
},
child: Text('go to the first'),
),
],
),
));
}
}
// no longer need to pass anything in your router below
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case '/first':
return MaterialPageRoute(
builder: (_) => FirstPage(),
);
case '/third':
return MaterialPageRoute(
builder: (_) => FirstPage(),
);
case '/menu':
return MaterialPageRoute(
builder: (_) => Menu(),
);
case '/second':
// if (args is int) {
return MaterialPageRoute(
builder: (_) => SecondPage(),
);
//}
// return _errorRoute();
//default:
//return _errorRoute();
}
}

How to expose provider to screen. (Provider to expose only available on previous screen)

I have 2 providers, Product and Products.
3 screens and 1 widget, a ProductsScreen that list all products with a ListView.builder with the widget as its child, a ProductDetailScreen that shows a single product details and a ProductEditScreen to edit a product.
I expose the Products provider on main, and the Product provider to each item in the ListView.
The user can go to ProductEditScreen on ProductsScreen and on ProductDetailScreen.
With the code I have now if user goes to edit on the ProductsScreen, edits the product and saves, the app goes back to ProductsScreen and the edited product shows the correct data.
I'm having a difficult time figuring out how to expose the Product provider to ProductDetailScreen. I need the product provider also here because if user goes to product edit and saves, the app goes back I need to show the correct product data.
main.dart
...
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (ctx) => Products(),
),
],
child: MaterialApp(
title: 'My app',
theme: ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.deepOrange,
),
initialRoute: ProductsScreen.routeName,
routes: {
ProductsScreen.routeName: (ctx) => ProductsScreen(),
ProductDetailScreen.routeName: (ctx) => ProductDetailScreen(),
ProductEditScreen.routeName: (ctx) => ProductEditScreen(),
},
),
);
}
}
products_screen.dart
...
class ProductsScreen extends StatelessWidget {
static const routeName = '/products-screen';
#override
Widget build(BuildContext context) {
final products = Provider.of<Products>(context, listen: true);
return Scaffold(
appBar: AppBar(
title: Text('Products'),
),
body: ListView.builder(
itemCount: products.items.length,
itemBuilder: (ctx, idx) => ChangeNotifierProvider.value(
value: products.items[idx],
child: ProductItemWidget(),
),
),
);
}
}
product_item_widget.dart
...
class ProductItemWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final product = Provider.of<Product>(context);
return Card(
child: ListTile(
leading: product.image.isEmpty
? Icon(Icons.image)
: Image.asset(product.image),
title: Text(product.name),
subtitle: Text(product.info),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
Navigator.of(context).pushNamed(
ProductEditScreen.routeName,
);
},
),
],
),
onTap: () {
Navigator.of(context).pushNamed(
ProductDetailScreen.routeName,
);
},
),
);
}
}

Flutter Transition Animation Issue while pushing new screen on Bottom Navigation Bar

I have a MaterialApp in which it has an IndexedStack as its homePage, to use it for BottomBarNavigation. Now, in one of the "Tab"s, I want page transitions to be done like it is done in iOS.
Part of the trick can be done using CupertinoPageRoute in Navigator.push as follows:
Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute<bool>(
//fullscreenDialog: true,
builder: (BuildContext context) => new DescriptionPage(),
));
this results the new page to slide from right as an iOS app. But, I also want the first page to be shifted to right with parallax, as it says in the CupertinoPageRoute's documentation:
The page also shifts to the left in parallax when another page enters to cover it.
this will be done if the first page itself is created via "Navigator.push(CupertinoPageRoute ...", but as I mentioned, my first page is one of the main pages of the application's home.
current transition style:
the new page slides in from right, but the current page does not shift to left
as you can see, the current page does not shift to left. There might be a way to make the current page's widget to act as a widget built by a CupertinoPageRoute, so that the current page itself slides to left as the new page comes in.
In the theme: parameter of MaterialApp, try this:
MaterialApp(
theme: ThemeData( //or replace with your custom ThemeData.copywith()
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder()
}
),
),
)
If you run the code below, you will see that First Tab is doing what you are seeing and second Tab is what you are expecting.
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,
),
home: CupertinoStoreHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class CupertinoStoreHomePage extends StatelessWidget {
const CupertinoStoreHomePage({title});
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.bell),
title: Text('TAB1'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.bell_solid),
title: Text('TAB2'),
),
],
),
tabBuilder: (context, index) {
switch (index) {
case 0:
return CupertinoTabView(builder: (context) {
return CupertinoPageScaffold(
child: TAB1(),
);
});
case 1:
return CupertinoTabView(
builder: (context) {
return CupertinoPageScaffold(
child: TAB2(),
);
},
routes: {
'/screen': (ctx) => NextScreen(),
},
);
default:
return Container();
}
},
);
}
}
class TAB1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("TAB1"),
),
body: Container(
child: FlatButton(
child: Text("Go To Next Screen"),
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => NextScreen())),
),
),
);
}
}
class TAB2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("TAB2"),
),
body: Container(
child: FlatButton(
child: Text("Go To Next Screen"),
onPressed: () => Navigator.of(context).pushNamed("/screen"),
)),
);
}
}
class NextScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: Scaffold(
appBar: AppBar(
title: Text("NextScreen"),
),
body: Container(
child: Text("NextScreen"),
),
),
);
}
}
When using Navigator.of(ctx).push the app tries to push new screen on root navigator, which in your case is tab bar but it fails to replace the tab bar, hence you see incomplete animation. But with second approach where you have defined route in relevant tab and use Push Named, the app uses navigator assigned to that tab and hence expected result.
Happy Coding!

Flutter navigation by route name in statefull widget

i am trying to go on another page using navigation, but i am getting error;
Navigator operation requested with a context that does not include a
Navigator.
i am just trying to move on next page, i followed flutter documentations for this stateless widget but how to do with state full widget.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State createState() => new MyApp1();
}
class MyApp1 extends State<MyApp> {
List<Widget> _listSection = [];
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Share IDEASS',
initialRoute: '/',
routes: {
'/second': (context) => SecondScreen(),
},
home: Scaffold(
appBar: AppBar(
title: Text('IDEAS'),
),
body: Container(
child: Stack(
children: [
floatingButton(),
],
),
),
),
);
}
Widget floatingButton() {
return Container(
padding: const EdgeInsets.all(30),
alignment: Alignment.bottomRight,
child: FloatingActionButton(
onPressed: () {
Navigator.pushNamed(context, "/SecondScreen");
},
child: Text("+"),
backgroundColor: Colors.blue,
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
You should use the named route you created.
Widget floatingButton(BuildContext context) { // added context as a parameter
return Container(
padding: const EdgeInsets.all(30),
alignment: Alignment.bottomRight,
child: FloatingActionButton(
onPressed: () {
Navigator.pushNamed(context, "/second"); // Changed this to use the named route
},
child: Text("+"),
backgroundColor: Colors.blue,
),
);
}
}
then use the following
body: Container(
child: Stack(
children: [
floatingButton(context),
],
),
),
The situation here is that the floatingButton() uses a context with the navigator to push the given page route. But the context used is provided in the parent Widget(MaterialApp) it self, which doesn't include a Navigator, hence the error.
So, Try this approach:
Separate the Home widget from the MaterialApp, like below:
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Share IDEASS',
initialRoute: '/',
routes: {
'/second': (context) => SecondScreen(),
},
home: HomePage(),
);
Create a stateless widget containing the Scaffold:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('IDEAS'),
),
body: Container(
child: Stack(
children: [
floatingButton(),
],
),
),
);
}
}
Hope it helps. Let me know if this doesn't work.
You have made two mistakes because of which your code is not working:
You have used wrong route name. Replace /SecondScreen with /second
You have used wrong context. You can get Navigator only if your widget has MaterialApp as it's parent and here you are using context of MyApp1 so it is not working.
Following is a working code for your reference.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State createState() => new MyApp1();
}
class MyApp1 extends State<MyApp> {
List<Widget> _listSection = [];
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Share IDEASS',
initialRoute: '/',
routes: {
'/second': (context) => SecondScreen(),
},
home: AppContent(),
);
}
}
class AppContent extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('IDEAS'),
),
body: Container(
child: Stack(
children: [
floatingButton(context),
],
),
),
);
}
Widget floatingButton(BuildContext context) {
return Container(
padding: const EdgeInsets.all(30),
alignment: Alignment.bottomRight,
child: FloatingActionButton(
onPressed: () {
Navigator.pushNamed(context, "/second");
},
child: Text("+"),
backgroundColor: Colors.blue,
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}

Flutter Breadcrumbs?

I need a dynamic "back" button on every screen/page that also shows the title of the previous screen.
Navigation is done via global navigatorKey, pushing new routes is not done from a specific screen.
Is this possible with built-in navigator or it needs to be built from scratch?
Just pass the string you want to display to the constructor of the widget which will be your next page.
Example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
Widget build(context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FlatButton(
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return Sec('title1');
}));
},
child: Text('Press here'))));
}
}
Widget backButton(context, t) {
return Row(children: [
Expanded(
child: IconButton(
icon: Icon(Icons.backspace),
onPressed: () => Navigator.pop(context),
),
),
Text(t,style:TextStyle(fontSize:16))
]);
}
class Sec extends StatelessWidget {
String t;
Sec(String x) {
t = x;
}
Widget build(context) {
return Scaffold(
appBar: AppBar(leading: backButton(context, t)),
body: Center(
child: FlatButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Press here to go back'))));
}
}
Output