Flutter, show AlertDialog from function - flutter

I have the follwing code with a _checkCreds function. I want to show an alert when that button is pressed.
When I replace the print() statement with an AlertDialog(), I get a "No MaterialLocalizations found".
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
void _checkCreds(bool val) {
if(!val){
print("Warning, show alert here instead of print!");
return;
}
...
// Continue executing
}
Widget build(BuildContext context) {
return MaterialApp(
home: RaisedButton(
child: "Press me to trigger an alert!" ),
onPressed: () => _checkCreds(false),
);
}
}

I've figured it out, coming for a React environment I didn't know where "BuildContext" fits in all of this. After further investigation I passed the current context as an argument to the function that calls the Alert.
Above main():
Future<void> _ackAlert(BuildContext context) {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Error'),
content: const Text('Please enter a valid text'),
actions: <Widget>[
FlatButton(
child: Text('Okay'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
Inside the main widget:
Widget build(BuildContext context) {
return MaterialApp(
home: RaisedButton(
child: "Press me to trigger an alert!" ),
// I've added a parameter that takes the current context
onPressed: () => _checkCreds(false, context),
);
}

I think this is what you want:
void _checkCreds(bool val){//put it inside 'MyAppState' class
if(!val){
showDialog(
barrierDismissible: true,//tapping outside dialog will close the dialog if set 'true'
context: context,
builder: (context){
return Dialog(
//Add code here
);
}
);
}
...
// Continue executing
}
AlertDialog and Dialog has same properties except AlertDialog has content property whereas Dialog has child property. Both does same work.

Related

Flutter Errors: Undefined name "context"

I receive some errors when I try to push my flutter page from neighbourhoodList to individual neighbourhoods (e.g. neighbourhoodlist_admirality).
In my neighbourhoodlist, I would like to navigate to the individual neighbourhood pages when the user has clicked on the relevant neighbourhood. As I have not build the individual neighbourhood pages yet, I have linked them to an example page i.e. NeighbourhoodAdmirality.
This is my code for the neighbourhoodlist page:
import 'package:flutter/material.dart';
import 'indv_neighbourhoods/neighbourhoodlist_admirality.dart';
class NeighbourhoodList extends StatefulWidget {
NeighbourhoodList({ this.name = "name"});
final String name;
#override
_NeighbourhoodListState createState() => _NeighbourhoodListState();
}
class _NeighbourhoodListState extends State<NeighbourhoodList> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Neighbourhoods',
),
),
body: _buildListView(context),
);
}
ListView _buildListView(BuildContext context){
return ListView.builder(
itemCount: allNeighbourhoods.length,
itemBuilder: (BuildContext content, int index) {
NeighbourhoodList neighbourhoodlist = allNeighbourhoods[index];
return NeighbourhoodListTile(neighbourhoodlist);
});
}
}
class NeighbourhoodListTile extends ListTile {
NeighbourhoodListTile(NeighbourhoodList neighbourhoodlist)
: super(
title: Text(neighbourhoodlist.name),
trailing: Icon(Icons.arrow_forward),
onTap: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NeighbourhoodAdmiralty(neigh1)),
);
}
);
}
List<NeighbourhoodList> allNeighbourhoods = [
NeighbourhoodList(name: 'Admiralty'),
NeighbourhoodList(name: 'Aljunied'),
NeighbourhoodList(name: 'Ang Mo Kio'),
];
This is my code for an example page that I want to direct my neighbourhoodlist to when each individual neighbourhood is directed.
import 'package:flutter/material.dart';
class NeighbourhoodAdmiralty extends StatefulWidget {
final String neigh1;
NeighbourhoodAdmiralty(this.neigh1);
#override
_NeighbourhoodAdmiraltyState createState() => _NeighbourhoodAdmiraltyState();
}
class _NeighbourhoodAdmiraltyState extends State<NeighbourhoodAdmiralty> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Admiralty"),
),
body: Center(child: Text('This is the individual neighbourhood page'),
),
);
}
}
As I am still a beginner, I am facing some errors and have a few questions on these:
Error 1 on neighbourhoodlist.dart: "Undefined name 'context'" under Navigator.push --> not sure why this happens as I have already passed the BuildContext in my methods above
Error 2 on neighbourhoodlist.dart: "Undefined name 'neigh1'" under Navigator.push --> I would like to redirect the neighbourhoodlist.dart page to the individual neighbourhood sheets but I'm not sure what I pass here, I have tried 'neigh1' (my variable in neighbourhoodlist_admirality), 'name' - the variable in NeighbourhoodList, and 'index' 0 the variable inNeighbourhoodListState but none of them seem to work so far.
Appreciate all your help in resolving this and thanks in advance !
When you do
class NeighbourhoodListTile extends ListTile {
NeighbourhoodListTile(NeighbourhoodList neighbourhoodlist)
: super(
title: Text(neighbourhoodlist.name),
trailing: Icon(Icons.arrow_forward),
onTap: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => NeighbourhoodAdmiralty(neigh1)));
}
);
}
you can't access context in onTap because Flutter hasn't yet provided one to you in this point. If you need the context, use a StatelessWidget instead, where you can access it in the build method:
class NeighbourhoodListTile extends StatelessWidget {
final NeighbourhoodList neighbourhoodlist;
const NeighbourhoodListTile(this.neighbourhoodlist);
#override
Widget build(BuildContext context) {
// Here's your context ^^^^^^^
return ListTile(
title: Text(neighbourhoodlist.name),
trailing: Icon(Icons.arrow_forward),
onTap: (){
Navigator.push(context, MaterialPageRoute(
builder: (context) => NeighbourhoodAdmiralty(neigh1)));
}
);
}
}
As to your second error, there is also no neigh1 variable at this point. I don't know what your logic is, but I think you want to replace it with neighbourhoodlist.name:
class NeighbourhoodListTile extends StatelessWidget {
final NeighbourhoodList neighbourhoodlist;
const NeighbourhoodListTile(this.neighbourhoodlist);
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(neighbourhoodlist.name),
trailing: Icon(Icons.arrow_forward),
onTap: (){
Navigator.push(context, MaterialPageRoute(
builder: (context) => NeighbourhoodAdmiralty(neighbourhoodlist.name)));
}
);
}
}

How to call an AlertDialog when Navigator.push move to another screen in flutter

What I want to do is to have for the user first time reach the screen that oine alertDialog appears.
I use this code to move to another screen:
Navigator.push(context, MaterialPageRoute(builder: (context) => IfProfilePage()));
in this specific case i switch tyo IfProfilPage but what I want is to get in IfProfilePage for the first time an alertdialog which I will build later, but this dialogf will contains some information that user has to follow.
thanks
you can pass it in initstate method
it has to be a stateful widget
class DestinationPage extends StatefulWidget {
#override
_DestinationPageState createState() => _DestinationPageState();
}
class _DestinationPageState extends State<DestinationPage> {
#override
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
showAlertdialog(context);
});
}
and here is a example of alert dialog that you can use
showAlertDialog(BuildContext context) {
// set up the button
Widget okButton = FlatButton(
child: Text("OK"),
onPressed: () { },
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text("My title"),
content: Text("This is my message."),
actions: [
okButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}

BLOC losing context when navigating to a new page

I'm using the BLOC pattern to authenticate a user in my app. I have a main BlocProvider that wraps my app. And a BlocBuilder to build according to the authentication state.
If the user is unauthenticated i have onboarding / intro screens that will navigate to the login screen.
The login screen is wrapped in another BlocProvider that contains a button that will do the login, and add a logged in event when the login is successful.
Problem is when i navigate from the onboarding screens i loose the main authenticationBloc context. What do i need to to to have access to the authentication bloc after i pushed a new screen.
void main() {
WidgetsFlutterBinding.ensureInitialized();
Bloc.observer = SimpleBlocObserver();
runApp(
MyApp(),
);
}
class AuthenticationWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider<AuthenticationBloc>(
create: (context) => AuthenticationBloc()..add(AppStarted()),
child: MyApp(),
),
);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (state is Authenticated) {
_appUserProfileRepository = AppUserProfileRepository();
}
},
child: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
_authCredentialHelper = state.authCredentialHelper;
if (state is Uninitialized) {
return SplashScreen();
}
if (state is Unauthenticated) {
return OnboardingScreens(authCredentialHelper: _authCredentialHelper);
}
if (state is InvalidRegistration) {
return RegisterProfileScreen(authCredentialHelper: _authCredentialHelper);
}
if (state is Authenticated) {
xxx
}
return Scaffold(body: Center(child: LoadingIndicator()));
},
),
);
}
}
This is the onboarding screen where i loose the authenticationbloc context as soon as i navigate
class OnboardingScreens extends StatelessWidget {
final AuthCredentialHelper authCredentialHelper;
OnboardingScreens({this.authCredentialHelper});
_pages(BuildContext context) {
return [
xxx
];
}
_getStartedClicked(BuildContext context) {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return LoginScreen(authCredentialHelper: authCredentialHelper);
}));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: IntroductionScreen(
pages: _pages(context),
onDone: () => _getStartedClicked(context),
showSkipButton: true,
done: xxx
),
),
);
}
}
When adding a breakpoint at 1. the context is fine with a valid value for BlocProvider.of(context)
Stepping to 2. gives me an error:
BlocProvider.of() called with a context that does not contain a Cubit of type AuthenticationBloc.
_getStartedClicked(BuildContext context) {
1----->Navigator.push(context, MaterialPageRoute(builder: (context) {
2----->return LoginScreen(authCredentialHelper: authCredentialHelper);
}));
}
This is the LoginScreen code
class LoginScreen extends StatelessWidget {
final AuthCredentialHelper authCredentialHelper;
LoginScreen({this.authCredentialHelper});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back, color: darkBlue),
onPressed: () => Navigator.of(context).pop(),
),
backgroundColor: Colors.transparent,
elevation: 0.0,
),
body: SafeArea(
child: Center(
child: BlocProvider<LoginBloc>(
create: (context) => LoginBloc(authCredentialHelper: authCredentialHelper),
child: LoginForm(authCredentialHelper: authCredentialHelper),
),
),
),
);
}
}
Getting this error:
The following assertion was thrown building _InheritedProviderScope<LoginBloc>(value: Instance of 'LoginBloc'):
BlocProvider.of() called with a context that does not contain a Cubit of type AuthenticationBloc.
No ancestor could be found starting from the context that was passed to BlocProvider.of<AuthenticationBloc>().
This can happen if the context you used comes from a widget above the BlocProvider.
Change this :
Navigator.push(context, MaterialPageRoute(builder: (context) {
return LoginScreen(authCredentialHelper: authCredentialHelper);
}));
to
Navigator.push(
context,
MaterialPageRoute(builder: (contextLoginScreen) {
return BlocProvider.value(
value: context.bloc<AuthenticationBloc>(),
child: LoginScreen(authCredentialHelper: authCredentialHelper));
}),
);

Flutter - How catch back button(hardware) event?

I created Flutter app for Android. This my user case:
I'm on the widgetA.
I'm click button and and go to the widgetB. (A->B)
I'm click hardware back button ant return to widgetA.
Now I'm on widgetA and I need any event so that I can update my WidgetA.
Any advices?
You could do something similar to what is called on Android startActivityForResult().
If you are on WidgetA push a new WidgetB and wait for a result.
From WidgetB detect when the widget will pop and pop manually sending the result.
WidgetA:
class WidgetA extends StatefulWidget {
#override
State<StatefulWidget> createState() => WidgetAState();
}
class WidgetAState extends State<WidgetA> {
Future<String> _result;
Future<String> _startWidgetForResult(BuildContext context) async {
String result = await Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => WidgetB(context: context),
),
);
return result;
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.subdirectory_arrow_right),
onPressed: () => _result = _startWidgetForResult(context),
),
body: FutureBuilder(
future: _result,
builder: (context, snapshot) {
return Center(child: Text('${snapshot.data}'));
},
),
);
}
}
WidgetB:
class WidgetB extends StatelessWidget {
final BuildContext context;
WidgetB({this.context});
Future<bool> _onWillPop() async {
Navigator.of(context).pop("New data");
return false;
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.subdirectory_arrow_left),
onPressed: () => Navigator.of(context).pop("New data"),
),
body: Center(child: Text('Press back to send new data')),
),
);
}
}
I guess you can wrap your widgetB in WillPopScope widget. Or you can set a key press handler with SystemChannels.keyEvent.setMessageHandler(...), but this isn't a recommended way to go.
You can use RouteObserver for that
https://api.flutter.dev/flutter/widgets/RouteObserver-class.html

How to save last opened screen in flutter app

I am trying to reopen last opened screen after boot, Is there any simple way to do so ? sample codes are welcome !
So far I tried a code(which I got somewhere) with SharedPreferences, but it's not working.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
String lastRouteKey = 'last_route';
void main() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
String lastRoute = preferences.getString(lastRouteKey);
runApp(MyApp(lastRoute));
}
class MyApp extends StatelessWidget {
final String lastRoute;
MyApp(this.lastRoute);
#override
Widget build(BuildContext context) {
bool hasLastRoute = getWidgetByRouteName(lastRoute) != null;
return MaterialApp(
home: Foo(),
initialRoute: hasLastRoute ? lastRoute : '/',
onGenerateRoute: (RouteSettings route) {
persistLastRoute(route.name);
return MaterialPageRoute(
builder: (context) => getWidgetByRouteName(route.name),
);
},
);
}
Widget getWidgetByRouteName(String routeName) {
switch (routeName) {
case '/':
return MainWidget();
case '/':
return SecondRoute();
// Put all your routes here.
default:
return null;
}
}
void persistLastRoute(String routeName) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
preferences.setString(lastRouteKey, routeName);
}
}
class Foo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Foo'),
),
body: Column(
children: <Widget>[
RaisedButton(
child: Text('Open route second'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
),
RaisedButton(
child: Text('Open route main'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MainWidget()),
);
},
),
],
),
);
}
}
class SecondRoute 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!'),
),
),
);
}
}
class MainWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("MainWidget"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
should I use SQLite or JSON instead of SharedPreferences to make the code simple? thanks.
Demo
A. Navigation
when we are navigating through different screens within app, actually, the route stacks are changing.
So, firstly, we need to figure out how to listen to this changes e.g Push screen, Pop back to users screen.
1. Attaching saving method in each action button
we can actually put this on every navigation-related button.
a. on drawer items
ListTile(
title: Text("Beta"),
onTap: () {
saveLastScreen(); // saving to SharedPref here
Navigator.of(context).pushNamed('/beta'); // then push
},
),
b. on Titlebar back buttons
appBar: AppBar(
title: Text("Screen"),
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {
saveLastScreen(); // saving to SharedPref here
Navigator.pop(context); // then pop
},
),
),
c. and also capturing event of Phone Back button on Android devices
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: (){ // will triggered as we click back button
saveLastScreen(); // saving to SharedPref here
return Future.value(true);
},
child: Scaffold(
appBar: AppBar(
title: Text("Base Screen"),
),
Therefore, we will have more code and it will be harder to manage.
2. Listening on Route Changes using Route observer
Nonetheless, Flutter provides on MaterialApp, that we can have some "middleware" to capture those changes on route stacks.
We may have this on our MyApp widget :
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Save Last Route',
navigatorObservers: <NavigatorObserver>[
MyRouteObserver(), // this will listen all changes
],
routes: {
'/': (context) {
return BaseScreen();
},
'/alpha': (context) {
return ScreenAlpha();
},
We can define MyRouteObserver class as below :
class MyRouteObserver extends RouteObserver {
void saveLastRoute(Route lastRoute) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('last_route', lastRoute.settings.name);
}
#override
void didPop(Route route, Route previousRoute) {
saveLastRoute(previousRoute); // note : take route name in stacks below
super.didPop(route, previousRoute);
}
#override
void didPush(Route route, Route previousRoute) {
saveLastRoute(route); // note : take new route name that just pushed
super.didPush(route, previousRoute);
}
#override
void didRemove(Route route, Route previousRoute) {
saveLastRoute(route);
super.didRemove(route, previousRoute);
}
#override
void didReplace({Route newRoute, Route oldRoute}) {
saveLastRoute(newRoute);
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
}
}
B. How to Start the App
As users interacting through the screens, the Shared Preferences will always store last route name. To make the app navigate correspondingly, we need to make our BaseScreen statefull and override its initState method as below :
return MaterialApp(
routes: {
'/': (context) {
return BaseScreen(); // define it as Main Route
},
class BaseScreen extends StatefulWidget {
#override
_BaseScreenState createState() => _BaseScreenState();
}
class _BaseScreenState extends State<BaseScreen> {
#override
void initState() {
super.initState();
navigateToLastPage();
}
void navigateToLastPage() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
String lastRoute = prefs.getString('last_route');
// No need to push to another screen, if the last route was root
if (lastRoute.isNotEmpty && lastRoute != '/') {
Navigator.of(context).pushNamed(lastRoute);
}
}
C. Working Repo
You may look at this repository that overrides RouteObserver as explained in second option above
Saving and Opening Screen Beta and Screen Delta in different starts
D. Shared Preferences / JSON / SQLite
I suggest to use Shared preferences for simplicity. As we only record simple String for route name, we can only write two lines of code to Save and two lines of code to Load.
If we use JSON file, we need to manually set Path for it using path_provider package.
Moreover, if we use SQLite, we need to setup DB (may consist > 8 more lines), and setup table and also inserting table method.