I am starting to learn Flutter and am working on a Calculator app. When I want to prevent the user from some action (let's say divide by zero), I want to display a Dialog showing an error message. This requires a context, but when I pass context, this results in an error.
The examples that I have seen that do display an alert dialog all appear to be the result of a button being pressed, and this uses the context that is present when the app Widget is created. My situation is that the dialog is displayed outside the creation of the widget, and it appears that the context is not valid there.
How can I display a dialog as a result of an action taken by the user rather than the clicking a button within the Widget that has been created for the app? An example would be great.
The error that I am getting is as follows:
I/flutter ( 6990): The getter 'modalBarrierDismissLabel' was called on null.
While I presume from what I have read that I need to restructure the code and reposition the Alert Dialog, I have no idea how to do that. The examples that I have seen that work result from a Widget created on construction that consequently uses the context available at that point. In my case, I'm attempting to create the alert dialog as a result of an outcome from the result of what a user has done, not from the pressing of a widget button.
Some of my relevant code is as follows:
} else if (pendingOperator == "/") {
if (secondValue != 0) {
setNewValue(Decimal.parse(firstValue.toString()) /
Decimal.parse(resultString));
} else {
_showAlert(context, "Divide by zero is invalid");
}
}
class MyAppState extends State<MyApp> {
Decimal firstValue;
String pendingOperator;
bool clearCurrentValue = true;
String resultString = "0";
void _showAlert(BuildContext context, String text) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("Error"),
content: Text(text),
));
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new SafeArea(
child: new Material(
color: Colors.black,
child: Column(
The need to show the alert is indirectly the result of a button being pressed. When that button is created, pass the context to the function that is called and use that context in the call to _showAlert.
Related
I have 2 dart file. I want close the alertdialog from another dart file in specific conditions.
How can I ?
for example the function i want.(I wrote to express myself better.)
void example(){
if(control==false){
alertDialog.close();
}
Alert dialog file
class AlertForm extends StatefulWidget {
#override
_AlertFormState createState() => _AlertFormState();
}
class _AlertFormState extends State<AlertForm> {
void _showDialog() {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Alert Dialog title"),
content: new Text("Alert Dialog body"),
actions: <Widget>[
// usually buttons at the bottom of the dialog
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
So what I want is to use the pop action in a function in another flutter file.
Call Navigator.of(context).pop(); inside the if block (of the void example function of the other Dart file) and the alert should close.
But here is the problem, the context above might not be part of or aware of the dialog and the above may not work.
More info about your setup would suggest a solution to this problem. Like how exactly does the other Dart file exist relative to the file where _showDialog is declared? If it's something you can easily provide context from AlertForm Dart file to the closingDialog Dart file, then you should know what to do. If that's not the case then a navigator key should help.
You can keep a navigator key for the entire application. Then instead of closing the dialog with Navigator.of(context).pop();, you use navigatorKey.currentState!.pop();.
You should have properly setup the navigator key yourself. Refer to this Stackoverflow answer for how to do that: https://stackoverflow.com/a/72945522/13644299
It is not compulsory to use get_it package. You can simply export the NavigationService (that will keep the navigatorKey) from any Dart file or with any method you deem fit, depending on your current state management architecture.
I'm building an application that uses lazy internationalization, this way there will be no translation files in the application and all translations will be fetched from the internet when a new page is opened. For that I am using a localization cubit.
Each screen of my application is divided into a "view" that receives the translated messages as a parameter, a "cubit" that contains the cubit screen and its states, and a "container" that contains the BlocProvider for the cubit and the screen.
For now my app starts in the presentation screen, after that it goes to the login screen and finally goes to the home screen.
So in the main file, instead of using the presentation screen directly, I use the localization container and the presentation container comes as its child:
return MaterialApp(
title: 'My App',
theme: myTheme(context),
debugShowCheckedModeBanner: false,
home: LocalizationContainer(
child: PresentationContainer(),
),
);
The PresentationContainer is composed this way:
class PresentationContainer extends BlocContainer {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => PresentationCubit(),
child: I18NLoadingContainer(
language: BlocProvider.of<CurrentLocaleCubit>(context).state,
viewKey : "Presentation",
creator: (messages) => PresentationView(PresentationViewLazyI18N(messages)),
),
);
}
}
So in the container I have a BlocProvider with PresentationCubit and I18NLoadingContainer as a child.
I18NLoadingContainer just obtains the transalted messages according to the language provided and the screen name, that is "Presentation" in this case. The translated messages are returned in the variable messages, so this messages are passed as parameter to the screen.
If I use this only for my presentation screen everything works fine, but the issue comes when I need to open a new page.
After the presentation screen I need to open the login screen. So in the PresentationView I have the following function when the user clicks the button to open the login screen:
void _goToLogin(BuildContext blocContext) {
Navigator.of(blocContext).pushReplacement(
MaterialPageRoute(
builder: (context) => BlocProvider.value(
value: BlocProvider.of<CurrentLocaleCubit>(blocContext),
child: LoginContainer(),
),
),
);
}
And the LoginContainer works exaclty as the PresentationContainer:
class LoginContainer extends BlocContainer {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => LoginCubit(),
child: I18NLoadingContainer(
language: BlocProvider.of<CurrentLocaleCubit>(context).state,
viewKey : "Login",
creator: (messages) => LoginView(LoginViewLazyI18N(messages)),
),
);
}
}
If I keep in the presentation screen and use the hot reload everything works fine, but if I open a new screen using this method, I got the following error when try to use hot reload:
The following _CastError was thrown building Builder(dirty): Null
check operator used on a null value
I'm not sure your LoginContainer is still wrapped by the LocalizationContainer when you change the route. I would suggest you to provide a CurrentLocaleCubit above the MaterialApp widget and check whether it's working or not. I think you're loosing a CurrentLocaleCubit instance
When calling Navigator.pop() a result can be passed to the previous screen. Is there a way to set the result so that if the user navigates back on their own the result is still returned to the previous page?
I could pass an object to the second page and then modify it so that the first page can check it when the second page returns, but I'd rather use the result returned from the Navigator as it's more readable.
Overriding the back button's tap detector as I've seen suggested elsewhere is not an acceptable solution because the user may navigate back in some other way such as swiping or pressing the Android back button.
Yes, you can pass data between screens both ways.
While popping back, send the data you wish to send like this from the second screen
RaisedButton(
onPressed: () {
// The Nope button returns "data" as the result.
Navigator.pop(context, 'data');
},
child: Text('Nope!'),
);
And catch the result in the your first screen like this
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
source
For the other concern where the user is able to go back to the previous screen by pressing the back button, you can wrap your second screen with WillPopScope widget and provide the onWillPop callback. This will override the popping of the second screen and execute the callback where you can define the value you wish to return from the second screen.
#override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(), // or your widget
onWillPop: () {
return Future.delayed(Duration(microseconds: 0), () {
return Navigator.pop(context, "return-data");
});
},
);
}
Yes, this is possible, when navigating to Screen B from Screen A, make the onTap() functionasynchronous and await the result from Screen B.
Check the code below: It works perfectly:
On Screen A, put this code:
onTap: () async {
// receive the data you are sending from screen B here
final result = await Navigator.push( context);
},
On Screen B, put this code:
onTap: (){
// pass the data you want to use in screen A as the second paramter
Navigator.pop(context,number);
},
I hope this helps
I am playing around with a simple flutter countdown app. It consists of 2 pages, the clock and a settings page to set minutes and seconds to be counted down.
On the clock page (HomeWidget) the user clicks a button to navigate to the settings page. After editing the values the user presses the back hardware key or the button in the app bar to navigate back to the clock page.
class _HomeWidgetState extends State<HomeWidget> {
#override
Widget build(BuildContext context) {
TimeService _timeService = ScopedModel.of<TimeService>(context);
SettingsModel _settingsModel = ScopedModel.of<SettingsModel>(context);
_timeService.setTime(_settingsModel.minutes, _settingsModel.seconds);
return Scaffold( ... display the clock, navigation buttons, etc ... )}
My problem to understand is that when navigating back I am setting the new values in the time service class that handles counting down. But in the code sample the time service is updated every time the clock gets redrawn (every second). The countdown doesn't work, the value remains the same. Instead of displaying "10:29", it sticks with "10:30". I don't know how to handle the dependency between my TimeService class and my SettingsModel class.
How can I handle the assignment of the settings values in the time service class properly when the user navigates back? The build method is obviously the wrong place. Can anyone give me a hint?
Ok, I found a solution for my problem. It is described in detail (with some other content) here.
Basically when navigating between pages you can pass objects along. So now I just pass the edited SettingsModel on the settings page via a Navigator.of(context).pop({'newSetting': _settingsModel}); command and the clock page then handles the result. I wasn't aware that navigation works like this.
ControlButtonWidget(
icon: Icons.settings,
iconSize: 72.0,
onPressedHandler: () async {
Map results = await Navigator.of(context).push(
new MaterialPageRoute(
builder: (context) => SettingsWidget(model)));
if (results != null && results.containsKey("newSetting")){
SettingsModel model = results["newSetting"];
ScopedModel.of<TimeService>(context).setTime(model.minutes, model.seconds);
}
})
Make sure to wrap your page in a WillPopScope Widget.
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: _backRequestedHandler,
child: LayoutBuilder(builder:
(BuildContext context, BoxConstraints viewportConstraints) {
return Scaffold(
appBar: new AppBar(title: Text("Settings")),
body: ...
}));
}
Future<bool> _backRequestedHandler() {
Navigator.of(context).pop({'newSetting': _settingsModel});
return new Future.value(true);
}
I want to explain something in my app and add a widget which looks like a notification or chat. I want this widget to be visible for some time and then get dismissed. I tried using tooltip but it is visible only when I click it.
Which widget can I use?
The Dart package intro_views_flutter is what you need, but one of its main limitations is that it is displayed on full screen, if that is not an issue to you, then you should take a look at it. Or you can use a showDialog method inside a Future function this way :
Future showNotification() async {
showDialog<String>(
context: context,
child: new AlertDialog(
title: Text('Note!') ,
contentPadding: const EdgeInsets.all(16.0),
content: //any widget you want to display here
),
);
await new Future.delayed(const Duration(seconds: 5), () {
Navigator.of(context).pop(); // this will dismiss the dialog automatically after five seconds
}
}
then when you need it call:
showNotificaion();