I'm using go_router to build an application that
does "dummy" pushes of routes (a bit like Twitter). It is possible to start with a page /a ,push /b, then /c, ... and then push again /a.
When a page is pushed twice, an assert fails: assert(!keyReservation.contains(key)); which ensures a key is used only once in the Navigator.
Is there a way to be able to push twice the same page with go_router?
Here is a small code snippet:
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
final router = GoRouter(
initialLocation: '/a',
routes: [
GoRoute(
path: '/a',
builder: (_, __) => const MyWidget(path: '/a'),
),
GoRoute(
path: '/b',
builder: (_, __) => const MyWidget(path: '/b'),
),
],
);
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
);
}
}
class MyWidget extends StatelessWidget {
const MyWidget({
required this.path,
Key? key,
}) : super(key: key);
final String path;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(path),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () {
context.push('/a');
},
child: const Text('/a'),
),
TextButton(
onPressed: () {
context.push('/b');
},
child: const Text('/b'),
),
],
)),
);
}
}
The issue can be triggered by clicking on the TextButton with "/a".
It was a bug in go_router (see this issue) which has been solved in the version 4.2.3.
So now you should be able to push the same page twice.
Related
I have a simple flutter app with two screens. On the first screen, i have an alert dialog that pops up every time a user visits the screen.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() => runApp(const MyApp());
/// The route configuration.
final GoRouter _router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return const HomeScreen();
},
routes: <RouteBase>[
GoRoute(
path: 'details',
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen();
},
),
],
),
],
);
/// The main app.
class MyApp extends StatelessWidget {
/// Constructs a [MyApp]
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationProvider: _router.routeInformationProvider,
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate);
}
}
/// The home screen
class HomeScreen extends StatefulWidget {
/// Constructs a [HomeScreen]
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
void initState() {
Timer(const Duration(seconds : 1), (() {
showDialog(
context: context,
builder: (context) {
return someDialogy();
});
print('i have been called forth');
}));
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Screen')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/details'),
child: const Text('Go to the Details screen'),
),
],
),
),
);
}
}
/// The details screen
class DetailsScreen extends StatelessWidget {
/// Constructs a [DetailsScreen]
const DetailsScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details Screen')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <ElevatedButton>[
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go back to the Home screen'),
),
],
),
),
);
}
}
Widget someDialogy () {
return AlertDialog(
content: Center(
child: Text('data'),
),
);
}
When i try to navigate to my second screen using a hyperlink on web say http://localhost/secondscreen, the popup from my first screen shows up.
My guess is that in constructing the route stack, flutter calls the initstate in my first page which does show my popup. What is the best way to go around this while maintaining the popup that shows when my first page is called?
it would be helpful to show some mode code on this, like the somedialogy() method
anyways I suspect the issue might be with your routing solution,
try
onPressed: () {
// Navigate to the second route when the button is pressed
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
I think its all about Timer
try this:
Future.delayed(Duration(seconds: 1), () {
showDialog(
context: context,
builder: (context) {
return someDialogy();
});});
read this for more about Timer Understanding Flutter’s Timer class and Timer.periodic
and this 2 Types of Flutter Delay Widgets
Even with a simple app, when I switch from one page to another, the screen flickers. Initially, I was testing this on flutter 3.0.2 and then moved to 3.3.2. On both versions I observe the flickering. Searching on google I see that others have observed this with ad_mob, but in my case I don't have ad_mob.
Here is main.dart
import 'package:flutter/material.dart';
import 'package:navigate3/page_1.dart';
import 'package:navigate3/page_2.dart';
import 'package:navigate3/page_3.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Main app',
initialRoute: '/Page1',
routes: {
'/Page1': (context) => Page_1(),
'/Page2': (context) => Page_2(),
'/Page3': (context) => Page_3(),
},
);
}
}
And here is page_1.dart. page_2.dart and page_3.dart are similar to page_1.dart
import 'package:flutter/material.dart';
class Page_1 extends StatelessWidget {
const Page_1({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text('Page 1'),
),
body: Center(
child: Column(
children: [
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/Page2');
},
child: const Text("Goto page 2"))
],
),
),
));
}
}
I'm writing a web flutter application with go_router.
Below is a code sample to illustrate how it is architectured (you can also check out this repo).
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
final router = GoRouter(
routes: [
GoRoute(
path: '/',
redirect: (_) => '/persons',
),
GoRoute(
path: '/persons',
builder: (_, __) => const PersonsScreen(),
routes: [
GoRoute(
path: ':id',
pageBuilder: (_, __) => const DrawerPage(
child: PersonScreen(),
),
),
],
),
],
);
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
routeInformationProvider: router.routeInformationProvider,
);
}
}
class PersonsScreen extends StatelessWidget {
const PersonsScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Persons'),
ElevatedButton(
onPressed: () => GoRouter.of(context).push('/persons/1'),
child: const Text('Push'),
)
],
),
),
);
}
}
class PersonScreen extends StatelessWidget {
const PersonScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.deepPurple,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Person'),
ElevatedButton(
onPressed: GoRouter.of(context).pop,
child: const Text('Pop'),
)
],
),
),
);
}
}
class DrawerPage extends Page {
const DrawerPage({
required this.child,
});
final Widget child;
#override
Route createRoute(BuildContext context) {
return _DrawerPageRoute(
settings: this,
);
}
}
class _DrawerPageRoute extends TransitionRoute {
_DrawerPageRoute({
required DrawerPage settings,
}) : super(
settings: settings,
);
#override
Iterable<OverlayEntry> createOverlayEntries() {
return [
OverlayEntry(
builder: (context) {
return Row(
children: [
const Spacer(flex: 2),
Expanded(
child: SlideTransition(
position: animation!.drive(
Tween(
begin: const Offset(1, 0),
end: const Offset(0, 0),
),
),
child: (settings as DrawerPage).child,
)),
],
);
},
),
];
}
#override
bool get opaque => false;
#override
Duration get transitionDuration => const Duration(milliseconds: 200);
}
It has 2 pages:
/persons
/persons/:id
/persons is just a grey screen:
When I click on "Push", it pushed /persons/1 which is a transparent route that takes a 3 of the screen:
This works well and I can still see the previous screen related to /persons behind the DrawerPage.
And when I click on "Pop", it does remove the purple screen (as it should).
But what I don't understand is when I paste myself the URL in the browser's address bar (for example /persons/3), it also ends up with the purple page that takes 1/3 of the screen (this is fine), but for some reason, the grey screen (linked to /persons) is behind and I can pop to go to the previous page (/persons) when I click on "Pop".
How and why is this page built?
How does flutter/GoRouter decide that when going to /persons/:id it should insert persons/ as the first page?
This is how it works.
According to the documentation (https://gorouter.dev/sub-routes):
go_router will match the routes all the way down the tree of sub-routes to build up a stack of pages
The route /persons/id includes the screen /persons in the stack and that is why it is shown.
If you want a screen showing /persons/id without the stack, you could define a different route where /persons/id is the root. Like:
final router = GoRouter(
routes: [
GoRoute(
path: '/',
redirect: (_) => '/persons',
),
GoRoute(
path: '/persons',
builder: (_, __) => const PersonsScreen(),
routes: [
GoRoute(
path: ':id',
pageBuilder: (_, __) => const DrawerPage(
child: PersonScreen(),
),
),
],
),
GoRoute(
path: '/person/:id',
pageBuilder: (_, __) => const DrawerPage(
child: PersonScreen(),
),
),
],
);
I have created a flutter package (say weight_calculator) and using it from the parent flutter app.
Now I want to navigate from screen A to screen B in weight_calculator package, but I am not able to use the Named routes as this package doesn't have a Material App.
What will be the correct approach to do that?
Currently, I am creating a new MaterialApp inside the package.
This actually works if you use go_route.
Here is an example where u have a app showing a screen from a package, and that package screen is navigating to a screen from the main app.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:home_page/home_page.dart';
void main() => runApp(const MyApp());
/// The route configuration.
final GoRouter _router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return const HomeScreen();
},
routes: <RouteBase>[
GoRoute(
path: 'details',
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen();
},
),
],
),
],
);
/// The main app.
class MyApp extends StatelessWidget {
/// Constructs a [MyApp]
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}
/// The details screen
class DetailsScreen extends StatelessWidget {
/// Constructs a [DetailsScreen]
const DetailsScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details Screen')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <ElevatedButton>[
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go back to the Home screen'),
),
],
),
),
);
}
}
and you also just need to make a local package with the HomeScreen widget.
library home_page;
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
/// The home screen
class HomeScreen extends StatelessWidget {
/// Constructs a [HomeScreen]
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Screen')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/details'),
child: const Text('Go to the Details screen'),
),
const Text("hello"),
],
),
),
);
}
}
Worked out of the box for me.
Also this can be improved by abstraction the routes, and injecting them in to the package from the main app.
I made a web app and deployed it to firebase. Let's say that the link is https://firebasewebapp. When I open the link the Sign In form shows up but when I open https://firebasewebapp/#/home I get redirected to the home page even if the user is not logged in. Is there a way to redirect the user if it's not logged in? I use Flutter 2.10.3 Stable Channel
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: options),
);
runApp(MultiProvider(providers: [
], child: const MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Portalul e-Radauti',
theme: ThemeData(
primarySwatch: Colors.orange,
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
backgroundColor:
MaterialStateColor.resolveWith((states) => Colors.orange),
foregroundColor:
MaterialStateColor.resolveWith((states) => Colors.white),
),
),
),
initialRoute: '/login',
routes: {
'/home': (_) => const MyHomePage(),
'/addcouncilmember': (_) => const LocalCouncilLayout(),
'/signup': (_) => const SignUp(),
'/login': (_) => const LogIn(),
'/addevent': (_) => const AddEventLayout(),
'/profile': (_) => const Profile(),
},
// home: const MyApp(),
);
}
}
The easiest way to do this would be to use Flutter's Navigator 2.0 or a package which wrap Navigator 2.0's API such as go_router or beamer.
Here's an example of how you can implement this behavior by using go_router:
router.dart
import 'package:go_router/go_router.dart';
import 'app.dart';
import 'redirection/log_in.dart';
import 'redirection/my_home_page.dart';
import 'redirection/sign_up.dart';
GoRouter routerGenerator(LoggedInStateInfo loggedInState) {
return GoRouter(
initialLocation: Routes.login,
refreshListenable: loggedInState,
redirect: (state) {
final isOnLogin = state.location == Routes.login;
final isOnSignUp = state.location == Routes.signup;
final isLoggedIn = loggedInState.isLoggedIn;
if (!isOnLogin && !isOnSignUp && !isLoggedIn) return Routes.login;
if ((isOnLogin || isOnSignUp) && isLoggedIn) return Routes.home;
return null;
},
routes: [
GoRoute(
path: Routes.home,
builder: (_, __) => const MyHomePage(),
),
GoRoute(
path: Routes.login,
builder: (_, __) => LogIn(loggedInState),
),
GoRoute(
path: Routes.signup,
builder: (_, __) => SignUp(loggedInState),
),
],
);
}
abstract class Routes {
static const home = '/home';
static const signup = '/signup';
static const login = '/login';
}
app.dart
import 'package:flutter/material.dart';
import 'router.dart';
class LoggedInStateInfo extends ChangeNotifier {
bool _isLoggedIn = false;
bool get isLoggedIn => _isLoggedIn;
void login() {
_isLoggedIn = true;
notifyListeners();
}
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _loggedInStateInfo = LoggedInStateInfo();
late final _router = routerGenerator(_loggedInStateInfo);
#override
void dispose() {
_loggedInStateInfo.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
);
}
}
With this code you only need to update the value of _isLoggedIn inside the LoggedInStateInfo and notify the listeners to access the HomePage, otherwise you will only have access to the LogIn and SignUp pages even when you manually change the URL.
Bonus
LogIn page code
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../app.dart';
import '../router.dart';
class LogIn extends StatelessWidget {
final LoggedInStateInfo loggedInStateInfo;
const LogIn(this.loggedInStateInfo, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('LogIn')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: loggedInStateInfo.login,
child: const Text('Log In'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => context.push(Routes.signup),
child: const Text('Sign Up'),
),
],
),
),
);
}
}
SignUp page code
import 'package:flutter/material.dart';
import '../app.dart';
class SignUp extends StatelessWidget {
final LoggedInStateInfo loggedInStateInfo;
const SignUp(this.loggedInStateInfo, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('SignUp')),
body: Center(
child: ElevatedButton(
onPressed: loggedInStateInfo.login,
child: const Text('Sign Up'),
),
),
);
}
}