I am making a Flutter app where I navigate from the home screen to other pages. I then want to go back to the home screen by popping the Navigator stack. When I do this I get a black screen and I assume I pop until the stack is empty. I do not however understand where my mistake is.
In page2.dart, if I replace the Navigator.popUntil() call with two calls to Navigator.pop(context) after each other, it pops successfully back to the home screen.
To demonstrate the issue I have I made a stand alone application that has this behavior.
main.dart
import 'package:flutter/cupertino.dart';
import 'package:test/router.dart' as router;
import 'package:flutter/widgets.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return const CupertinoApp(
title: 'Test',
initialRoute: router.home,
onGenerateRoute: router.generateRoute);
}
}
router.dart
import 'package:flutter/cupertino.dart';
import 'package:test/home.dart';
import 'package:test/page1.dart';
import 'package:test/page2.dart';
import 'package:flutter/widgets.dart';
const home = '/';
const page1 = '/page1';
const page2 = '/page2';
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case home:
return CupertinoPageRoute(builder: (context) => const Home());
case page1:
return CupertinoPageRoute(builder: (context) => const Page1());
case page2:
return CupertinoPageRoute(builder: (context) => const Page2());
default:
print('Undefined view');
return CupertinoPageRoute(
builder: (context) => UndefinedView(
name: settings.name ?? 'No Name',
));
}
}
class UndefinedView extends StatelessWidget {
final String name;
const UndefinedView({Key? key, required this.name}) : super(key: key);
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: Center(
child: Text('Route for $name is not defined'),
),
);
}
}
home.dart
import 'package:flutter/cupertino.dart';
import 'package:test/router.dart' as router;
import 'package:flutter/widgets.dart';
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
CupertinoButton(
child: const Text(
'Go to page 1',
style: TextStyle(color: Color.fromRGBO(0xFF, 0xFF, 0xFF, 1)),
),
onPressed: () => {Navigator.pushNamed(context, router.page1)},
),
Container(
height: 50,
)
],
));
}
}
page1.dart
import 'package:flutter/cupertino.dart';
import 'package:test/router.dart' as router;
import 'package:flutter/widgets.dart';
class Page1 extends StatefulWidget {
const Page1({Key? key}) : super(key: key);
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
CupertinoButton(
child: const Text(
'Go to Page 2',
style: TextStyle(color: Color.fromRGBO(0xFF, 0xFF, 0xFF, 1)),
),
onPressed: () => {Navigator.pushNamed(context, router.page2)},
),
Container(
height: 50,
)
],
));
}
}
page2.dart
import 'package:flutter/cupertino.dart';
import 'package:test/router.dart' as router;
import 'package:flutter/widgets.dart';
class Page2 extends StatefulWidget {
const Page2({Key? key}) : super(key: key);
#override
_Page2State createState() => _Page2State();
}
class _Page2State extends State<Page2> {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
CupertinoButton(
child: const Text(
'Go home',
style: TextStyle(color: Color.fromRGBO(0xFF, 0xFF, 0xFF, 1)),
),
onPressed: () =>
{Navigator.popUntil(context, ModalRoute.withName(router.home))},
),
Container(
height: 50,
)
],
));
}
}
When returning CupertinoPageRoute inside generateRoute, try passing settings too, i.e.
return CupertinoPageRoute(
settings: settings,
builder: (context) => const Home()
);
or
return CupertinoPageRoute(
settings: const RouteSettings(name: home),
builder: (context) => const Home()
);
I believe otherwise the Navigator is not aware of the route names, which causes popUntil to pop until there are no more routes (thus the black screen).
Related
I've some trouble when I try to save the reorderable grid after I've changed it.
It seem to work good but when i open again the app is only a grey screen.I've added some snackbar that print the list before and after been saved with GetStorage but they are in the right order as you can see in this screenshot
imagePaths before being saved
imagePaths loaded from GetStorage
grey screen when I reopening the app
import 'package:flutter/material.dart';
import 'package:reorderable_grid_view/reorderable_grid_view.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
void main() async {
await GetStorage.init();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final box = GetStorage();
List<String> imagePaths = GetStorage().read('imagePaths') ?? [
'remote',
'timelapse',
'video',
'hdr',
'star',
'lightning',
'drop',
'vibrate',
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: ReorderableGridView.count(
crossAxisCount: 2,
childAspectRatio: 1,
children: imagePaths
.map((String path) => Card(
key: ValueKey(path),
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(path),
SvgPicture.asset(
'assets/$path.svg',
color: Colors.red,
),
],
),
),
))
.toList(),
onReorder: (oldIndex, newIndex) async {
String path = imagePaths.removeAt(oldIndex);
imagePaths.insert(newIndex, path);
setState(() {
Get.snackbar(
'before GetStorage:',
'${imagePaths.toString()}',
snackPosition: SnackPosition.BOTTOM,
);
box.remove('imagePaths');
box.write('imagePaths', imagePaths);
Get.snackbar(
'from GetStorage:',
'${box.read('imagePaths').toString()}',
snackPosition: SnackPosition.BOTTOM,
);
});
},
),
);
}
}
I solved your problem like here:
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List imagePaths = [
'remote',
'timelapse',
'video',
'hdr',
'star',
'lightning',
'drop',
'vibrate'
];
final box = GetStorage();
final String key = 'imagePaths';
#override
void initState() {
imagePaths = box.read(key);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ReorderableGridView.count(
crossAxisCount: 2,
childAspectRatio: 1,
children: imagePaths
.map((path) => Card(
key: ValueKey(path),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(path),
SvgPicture.asset(
'assets/$path.svg',
color: Colors.red,
),
],
),
))
.toList(),
onReorder: (oldIndex, newIndex) async {
String path = imagePaths.removeAt(oldIndex);
imagePaths.insert(newIndex, path);
setState(() {
box.remove(key);
box.write(key, imagePaths);
});
},
),
);
}
}
I am watching a tutorial on youtube (https://youtu.be/Evu19gTKaFo) to build a menu for my mobile application. I followed the steps but I had a problem when I ran the application:
-how it's supposed to look: goodLook
-how it's actually looking:
badLook
import 'package:flutter/material.dart';
import 'package:flutter_zoom_drawer/config.dart';
import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart';
import 'main_screen.dart';
import 'menu_page.dart';
class Dashboard extends StatefulWidget {
const Dashboard({Key? key}) : super(key: key);
#override
State<Dashboard> createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
#override
Widget build(BuildContext context) {
return const ZoomDrawer(
style: DrawerStyle.style1,
mainScreen: MainScreen(),
menuScreen: MenuPage(),
);
}
}
import 'package:dashboard/menu_widget.dart';
import 'package:flutter/material.dart';
import 'package:authentication/authentication.dart';
import 'package:firebase_auth/firebase_auth.dart';
class MainScreen extends StatefulWidget {
const MainScreen({Key? key}) : super(key: key);
#override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: const Text('Main page'),
leading: const MenuWidget(),
),
body: ElevatedButton(
onPressed: _signOut,
child: const Text('Logout'),
),
);
}
Future<void> _signOut() async {
await FirebaseAuth.instance.signOut();
if (!mounted) {
return;
}
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => const Authentication()),
(route) => false);
}
}
import 'package:flutter/material.dart';
class MenuPage extends StatelessWidget {
const MenuPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) => const Scaffold(
backgroundColor: Colors.indigo,
);
}
import 'package:flutter/material.dart';
import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart';
class MenuWidget extends StatelessWidget {
const MenuWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.menu),
onPressed: () => ZoomDrawer.of(context)!.toggle(),
);
}
}
I've been searching for a solution but I couldn't find it. If you know how to solve this problem, please help me.
Thank you!
Problem solved:
ZoomDrawer(
controller: z,
borderRadius: 24,
style: DrawerStyle.defaultStyle,
openCurve: Curves.fastOutSlowIn,
slideWidth: MediaQuery.of(context).size.width * 0.65,
duration: const Duration(milliseconds: 500),
menuBackgroundColor: Colors.indigo,
mainScreen: MainScreen(),
menuScreen: MenuPage(),
)
https://github.com/medyas/flutter_zoom_drawer/issues/105
I don't want to pass data to text widget if the counterValue number is less than 0. This the code:
main.dart :
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_bloc_concepts/cubit/cubit/counter_cubit.dart';
void main() {
runApp(const MyApp());
}
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 BlocProvider<CounterCubit>(
create: (context) => CounterCubit(),
child: const MaterialApp(
title: 'flutter_bloc Demo',
debugShowCheckedModeBanner: false,
home: HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Flutter BLoC Concepts DEMO")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text("You have pushed the button this many times:"),
// Bloc Builder
BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
if (state.counterValue < 0) {
return NOTHING;
} else {
return Text(
state.counterValue.toString(),
style: Theme.of(context).textTheme.headline4,
);
}
},
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FloatingActionButton(
onPressed: () {
BlocProvider.of<CounterCubit>(context).decrement();
},
tooltip: "Decrement",
child: const Icon(Icons.remove),
),
FloatingActionButton(
onPressed: () {
BlocProvider.of<CounterCubit>(context).increment();
},
tooltip: "Increment",
child: const Icon(Icons.add),
),
],
),
],
),
),
);
}
}
counter_cubit.dart :
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
part 'counter_state.dart';
class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(CounterState(counterValue: 0));
void increment() => emit(CounterState(counterValue: state.counterValue + 1));
void decrement() => emit(CounterState(counterValue: state.counterValue - 1));
}
counter_state.dart :
part of 'counter_cubit.dart';
class CounterState {
int counterValue;
CounterState({
required this.counterValue,
});
}
Can i pass nothing to text widget when counterValue number is less than 0. I don't want to show negative numbers, so if I press the decrement button when counterValue = 0 the number displayed is not negative -1,-2... / I want stay at 0. Can i do that
adding if else statement in counter_cubit.dart :
void decrement() {
if (state.counterValue <= 0) {
state.counterValue = 0;
} else {
emit(CounterState(counterValue: state.counterValue - 1));
}
}
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'),
),
),
);
}
}
Context
I have two stateless widgets (pages): HomePage and DetailsPage. Obviously the application starts and launches the HomePage. There is a button the user can press to navigate to the DetailsPage with a Navigator.pop() button to navigate back to the HomePage.
I know when the DetailsPage is done being used with the .whenComplete() method. It is at this point I want to rebuild the HomePage widget.
Code
This is the minimum reproduction of my behavior.
main.dart
import 'package:example/home.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: HomePage());
}
}
home.dart
import 'package:example/details.dart';
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
static const name = 'Home Page';
const HomePage() : super();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: Text(name),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: DetailsPage.builder),
).whenComplete(() => print('Rebuild now.'));
},
),
),
);
}
}
details.dart
import 'package:flutter/material.dart';
class DetailsPage extends StatelessWidget {
static const name = 'Details Page';
static WidgetBuilder builder = (BuildContext _) => DetailsPage();
const DetailsPage();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(name),
MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: Text('Go Back'),
onPressed: () => Navigator.pop(context),
),
],
),
),
);
}
}
Question
How can I invoke a rebuild of this stateless widget (HomePage) at the .whenComplete() method callback?
You can force rebuild the widget tree as follows:
class RebuildController {
final GlobalKey rebuildKey = GlobalKey();
void rebuild() {
void rebuild(Element el) {
el.markNeedsBuild();
el.visitChildren(rebuild);
}
(rebuildKey.currentContext as Element).visitChildren(rebuild);
}
}
class RebuildWrapper extends StatelessWidget {
final RebuildController controller;
final Widget child;
const RebuildWrapper({Key? key, required this.controller, required this.child}) : super(key: key);
#override
Widget build(BuildContext context) => Container(
key: controller.rebuildKey,
child: child,
);
}
In your case,
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final RebuildController controller = RebuildController();
MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: RebuildWrapper(
controller: controller,
child: HomePage(
rebuildController: controller,
),
),
);
}
}
class HomePage extends StatelessWidget {
static const name = 'Home Page';
final RebuildController rebuildController;
const HomePage({Key? key, required this.rebuildController}) : super(key: key);
#override
Widget build(BuildContext context) {
print('Hello there!');
return Scaffold(
body: Center(
child: MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: const Text(name),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: DetailsPage.builder),
).whenComplete(rebuildController.rebuild);
},
),
),
);
}
}
class DetailsPage extends StatelessWidget {
static const name = 'Details Page';
static WidgetBuilder builder = (BuildContext _) => const DetailsPage();
const DetailsPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(name),
MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: const Text('Go Back'),
onPressed: () => Navigator.pop(context),
),
],
),
),
);
}
}
class RebuildController {
final GlobalKey rebuildKey = GlobalKey();
void rebuild() {
void rebuild(Element el) {
el.markNeedsBuild();
el.visitChildren(rebuild);
}
(rebuildKey.currentContext as Element).visitChildren(rebuild);
}
}
class RebuildWrapper extends StatelessWidget {
final RebuildController controller;
final Widget child;
const RebuildWrapper({Key? key, required this.controller, required this.child}) : super(key: key);
#override
Widget build(BuildContext context) => Container(
key: controller.rebuildKey,
child: child,
);
}
But it is unnatural to force rebuild stateless widgets as they are not supposed to be rebuilt. You should use stateful widget or other state management solutions so that your HomePage will only be updated on meaningful state change.
Source - this answer