I use the following function to check and display the content either in Dialog or Bottom Sheet, but when executing it does not work properly, as it displays both together, what is the reason and how can the problem be solved?
Is it possible to suggest a better name for the function?
Content function:
content(BuildContext context, dynamic dialog, dynamic bottomSheet) {
(MediaQuery.of(context).orientation == Orientation.landscape) ? dialog : bottomSheet;
}
Implementation:
ElevatedButton(
child: Text('Button'),
onPressed: () {
content(context, dialog(context), bottomSheet(context));
},
),
How can this be solved?
In order to determine the Orientation of the screen, we can use the OrientationBuilder Widget. The OrientationBuilder will determine the current Orientation and rebuild when the Orientation changes.
void main() async {
runApp(const Home(
));
}
class Home extends StatefulWidget {
const Home({Key key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return MaterialApp(home: Scaffold(
body: Center(
child: OrientationBuilder(
builder: (context, orientation) {
return ElevatedButton(
child: Text('Button'),
onPressed: () {
revealContent(orientation,context);
},
);
},
)
),
));
}
revealContent(Orientation orientation, BuildContext context) {
orientation == Orientation.landscape ? dialog(context) : bottomSheet(context);
}
dialog(BuildContext context){
showDialog(
context: context,
builder: (context) => const Dialog(
child: Padding(
padding: EdgeInsets.all(20.0),
child: Text('test'),
),
)
);
}
bottomSheet(final BuildContext context) {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (builder) => const Padding(
padding: EdgeInsets.all(20.0),
child: Text('test'),
),
);
}
}
here are screenshots:
happy coding...
The reason the function is not working properly is because you're not actually showing the dialog or bottom sheet. To show the dialog or bottom sheet, you need to call showDialog or showModalBottomSheet, respectively, and pass them the result of calling dialog or bottomSheet.
try this
void revealContent(BuildContext context, Widget dialog, Widget bottomSheet) {
(MediaQuery.of(context).orientation == Orientation.landscape)
? showDialog(context: context, builder: (context) => dialog)
: showModalBottomSheet(context: context, builder: (context) => bottomSheet);
}
You have a fundamental misunderstanding as to what your code is doing.
Take your "Implementation" and revealContent code, for example:
ElevatedButton(
child: Text('Button'),
onPressed: () {
revealContent(context, dialog(context), bottomSheet(context));
},
),
revealContent(BuildContext context, dynamic dialog, dynamic bottomSheet) {
(MediaQuery.of(context).orientation == Orientation.landscape) ? dialog : bottomSheet;
}
You think that revealContent will invoke either dialog or bottomSheet based on the orientation of the screen. What you are actually doing, however, is you are invoking both of them and then passing the result of the invocations to revealContent, which isn't actually doing anything with them.
What you need to be doing is passing the functions as callbacks to revealContent and then invoking the callbacks within the function:
ElevatedButton(
child: Text('Button'),
onPressed: () {
revealContent(context, () => dialog(context), () => bottomSheet(context));
},
),
revealContent(BuildContext context, void Function() dialog, void Function() bottomSheet) {
if (MediaQuery.of(context).orientation == Orientation.landscape) {
dialog()
} else {
bottomSheet();
}
}
You should be calling showDialog and showModalBottomSheet inside revealContent.
Dialog
dialog(BuildContext context){
return Dialog( //.. );
}
BottomSheet
bottomSheet(final BuildContext context) {
return Widget( /.. );
}
Reveal Content
void revealContent(BuildContext context, Widget dialog, Widget bottomSheet) {
if (MediaQuery.of(context).orientation == Orientation.landscape) {
return showDialog(context: context, builder: (context) => dialog);
} else {
return showModalBottomSheet(context: context, builder: (context) => bottomSheet);
}
}
Related
enter code hereI want to open a screen to add extra information if it is not set yet. So after the user is logged in I check if the extra info is set. If not I want it to go to a screen to fill in the info. If the user is done it should go to a "Homescreen". If the user info is already set it should immediately go to the home screen.
I already tried to just go to the extra info form and then Navigator.push to the home screen but then it has difficulties with logging out. I searched for a long time but can not find anything.
class CampPage extends StatelessWidget {
final String email;
final String uid;
const CampPage({super.key, required this.email, required this.uid});
#override
Widget build(BuildContext context) {
return FutureBuilder(
// ignore: unrelated_type_equality_checks
future: context.read<UserProvider>().exists(uid) == true
? null
: Future.delayed(Duration.zero, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewUserPage(email: email, userId: uid),
),
);
}),
builder: (context, snapshot) => Scaffold(
drawer: const DrawerHomePage(),
appBar: AppBar(
title: const Text("Camp Page"),
),
body: Column(
children: const [
Text("nieuwe features"),
],
),
),
);
}
}
this is one of the things I try but the NewUserPage always pops up and I only want it to pop up if context.read<UserProvider>().exists(uid) == false
also the solution mentioned does not work for me. I think because there is a screen in between the login and logout (The form screen) the logout function does not work properly.
`
class UserPage extends StatelessWidget {
const UserPage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
child: const Text("Submit"),
onPressed: () {
//Log out of Firestore Authentication
},
),
);
}
}
class NewForm extends StatelessWidget {
const NewForm({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
child: const Text("Submit"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const UserPage()),
);
},
),
);
}
}
Widget build(BuildContext context) {
return FutureBuilder(
future: context.read<UserProvider>().exists(uid)
builder: (context, snapshot) {
if (snapshot.hasdata){
if (snapshot.data == true) {
return const UserPage();
} else {
return const NewForm();
}
}
else // show a proggress bar
}
);
}
`
Does someone still have another solution?
I think you should do this:
Widget build(BuildContext context) {
return FutureBuilder(
future: context.read<UserProvider>().exists(uid)
builder: (context, snapshot) {
if (snapshot.hasdata){
if (snapshot.data == true) // then the user exist
else // the user doesn't exist
}
else // show a proggress bar
}
);
}
I need to update the text of my dialog while my report is loading. setState doest not work here.
class ReportW extends StatefulWidget {
const ReportW({Key key}) : super(key: key);
#override
_ReportWState createState() => _ReportWState();
}
class _ReportWState extends State<ReportMenuDownloadW> {
String loadingText;
void updateLoadingText(text){
setState(() {loadingText = text;});
}
#override
Widget build(BuildContext context) {
return MyWidget(
label:REPORT_LABEL,
onTap: () async {
showDialog(context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) {
return Dialog(
child: Column(
children: [
CircularProgressIndicator(),
Text(loadingText),
],
),
);});
});
await loadPDF(context,updateLoadingText);
Navigator.pop(context);
},
);
}
}
Is there an alternative solution if it is not possible ? I just need a progress text indicator over my screen while loading.
In your case you can use GlobalKey. For your code:
Define globalKey inside your widget:
// Global key for dialog
final GlobalKey _dialogKey = GlobalKey();
Set globalKey for your StatefulBuilder:
return StatefulBuilder(
key: _dialogKey,
builder: (context, setState) {
return Dialog(
child: Column(
children: [
CircularProgressIndicator(),
Text(loadingText),
],
),
);
},
);
Now you can update UI of your dialog like this:
void updateLoadingText(text) {
// Check if dialog displayed, we can't call setState when dialog not displayed
if (_dialogKey.currentState != null && _dialogKey.currentState!.mounted) {
_dialogKey.currentState!.setState(() {
loadingText = text;
});
}
}
Pay attention, you get unexpected behavior if user will close dialog manually.
How to prevent closing dialog by user: in showDialog use barrierDismissible: false and also wrap your dialog to WillPopScope with onWillPop: () async {return false;}
Possible question:
Why we check _dialogKey.currentState != null?
Because opening dialog and set globalKey take some time and while it's not opened currentState is null. If updateLoadingText will be call before dialog will be open, we shouldn't update UI for dialog.
Full code of your widget:
class OriginalHomePage extends StatefulWidget {
OriginalHomePage({Key? key}) : super(key: key);
#override
_OriginalHomePageState createState() => _OriginalHomePageState();
}
class _OriginalHomePageState extends State<OriginalHomePage> {
String loadingText = "Start";
// Global key for dialog
final GlobalKey _dialogKey = GlobalKey();
void updateLoadingText(text) {
// Check if dialog displayed, we can't call setState when dialog not displayed
if (_dialogKey.currentState != null && _dialogKey.currentState!.mounted) {
_dialogKey.currentState!.setState(() {
loadingText = text;
});
}
}
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () async {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
key: _dialogKey,
builder: (context, setState) {
return Dialog(
child: Column(
children: [
CircularProgressIndicator(),
Text(loadingText),
],
),
);
},
);
},
);
await loadPDF(context, updateLoadingText);
Navigator.pop(context);
},
child: Text("Open"),
);
}
}
Also i rewrote your code a bit, it seems to me more correct:
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
child: Text("Open"),
onPressed: () => _showDialog(),
),
),
);
}
// Global key for dialog
final GlobalKey _dialogKey = GlobalKey();
// Text for update in dialog
String _loadingText = "Start";
_showDialog() async {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async {
return false;
},
child: StatefulBuilder(
key: _dialogKey,
builder: (context, setState) {
return Dialog(
child: Padding(
padding: EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
Text(_loadingText),
],
),
),
);
},
),
);
},
);
// Call some function from service
await myLoadPDF(context, _setStateDialog);
// Close dialog
Navigator.pop(context);
}
// Update dialog
_setStateDialog(String newText) {
// Check if dialog displayed, we can't call setState when dialog not displayed
if (_dialogKey.currentState != null && _dialogKey.currentState!.mounted) {
_dialogKey.currentState!.setState(() {
_loadingText = newText;
});
}
}
}
Result:
Updated dialog
In my test code below I have a flag that determines whether to use a ChangeNotifierProvider or a ChangeNotifierProxyProvider. When I press the RaisedButton both approaches properly display my GroupEditorPage.
const isUsingChangeNotifierProxyProvider = true;
class GroupsPage extends StatelessWidget {
showGroupEditor(BuildContext context) {
Navigator.push(
context,
MaterialPageRoute(builder: (_) {
return isUsingChangeNotifierProxyProvider
? ChangeNotifierProxyProvider<CloudServicesProvider,
GroupEditorProvider>(
create: (_) => GroupEditorProvider(),
update: (_, cloudServicesProvider, groupEditorProvider) =>
groupEditorProvider.update(cloudServicesProvider),
child: GroupEditorPage(),
)
: ChangeNotifierProvider<GroupEditorProvider>(
create: (_) => GroupEditorProvider(),
child: GroupEditorPage(),
);
}),
);
}
#override
Widget build(BuildContext context) {
return SliversPage(
text: 'Testing',
sliverList: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return RaisedButton(
child: Text('+Create Group'),
onPressed: () => showGroupEditor(context),
);
},
childCount: 1,
),
),
);
}
}
But Provider.of only returns my GroupEditorProvider instance when ChangeNotifierProvider is used. When Change ChangeNotifierProxyProvider is used, groupEditorProvider below is null.
class GroupEditorPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final groupEditorProvider = Provider.of<GroupEditorProvider>(context);
I've been using Provider for some time but am new to ChangeNotifierProxyProvider so likely not understanding something fundamental.
Turns out I wasn't returning the provider instance from my GroupEditorProvider.update function:
update(CloudServicesProvider cloudServicesProvider) {
if (_cloudServicesProvider == null) {
this._cloudServicesProvider = cloudServicesProvider;
}
return this; // <--- was missing
}
Should Flutter have thrown an exception for this? I'll post to github if so.
Future<bool> show(BuildContext context) async {
return Platform.isIOS
? await showCupertinoDialog<bool>
(context: context, builder: (context)=>this)
:await showDialog<bool>(
context: context,
builder: (context) => this,
);
}
Can anyone help me to understand the term 'this',what does 'this' refer to and how does showDialog works that it returns Future.I tried to read documentation but still couldn't understand it?Is it the same as AlertDialog widget?
well, it's pretty much what the documentation said, it shows a material dialog above the current content of your app, as for this it passes the current widget as child for the dialog, as for the returned value is just like normal page navigation that when you call pop(context, {value}) method you can also return a value, so that value that inside pop will be returned from the dialog.
here is an example below:
class DialogTest extends StatefulWidget {
#override
_DialogTestState createState() => _DialogTestState();
}
class _DialogTestState extends State<DialogTest> {
// the value that will be typed to the dialog
String dialogText;
// the value that will be returned from the dialog
String returnedFromDialog;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample Code'),
),
body: Center(
child:
Text('You got this value from the dialog => $returnedFromDialog'),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
returnedFromDialog = await showDialog<String>(
context: context,
builder: (context) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
onChanged: (value) => dialogText = value,
),
FlatButton(
onPressed: () {
setState(() => Navigator.pop(context, dialogText));
},
child: Text(
'Close dialog',
style: TextStyle(color: Colors.red),
),
)
],
),
);
});
},
child: Icon(Icons.open_in_browser),
),
);
}
}
I want to show a dialog if I receive an error in a futurebuilder.
If I receiver an error, I want to show a dialog and force the user to click on the button, so that he can be redirected to another page.
The problems seems to be that it is not possible to show a dialog while widget is being built.
FutureBuilder(
future: ApiService.getPosts(),
builder: (BuildContext context, AsyncSnapshot snapShot) {
if (snapShot.connectionState == ConnectionState.done) {
if (snapShot.data.runtimeType == http.Response) {
var message =
json.decode(utf8.decode(snapShot.data.bodyBytes));
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text("Ok"),
onPressed: () => null",
)
],
);
});
}
return ListView.separated(
separatorBuilder: (BuildContext context, int index) {
return Divider(
color: Colors.grey,
height: 1,
);
},
itemBuilder: (BuildContext context, int index) {
return _buildPostCard(index);
},
itemCount: snapShot.data.length,
);
return Center(
child: CircularProgressIndicator(),
);
},
)
If I return the AlertDialog alone, it works. But I need the showDialog because of the barrierDismissible property.
Does any one know if that is possible?
Also, is this a good way to handle what I want?
Thanks
UPDATE
For further reference, a friend at work had the solution.
In order to do what I was looking for, I had to decide which future I was going to pass to the futureBuilder.
Future<List<dynamic>> getPostsFuture() async {
try {
return await ApiService.getPosts();
} catch (e) {
await showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text("Ok"),
onPressed: () => null",
)
],
);
});
}
}
}
Then in the futureBuilder I would just call
FutureBuilder(
future: getPostsFuture(),
Thanks
To avoid setState() or markNeedsBuild() called during build error when using showDialog wrap it into Future.delayed like this:
Future.delayed(Duration.zero, () => showDialog(...));
setState() or markNeedsBuild() called during build.
This exception is allowed because the framework builds parent widgets before its children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
So to avoid that Future Callback is used, which adds a call like this EventQueue.
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Future futureCall() async {
await Future.delayed(Duration(seconds: 2));
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: futureCall(),
builder: (_, dataSnapshot) {
if (dataSnapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
Future(() { // Future Callback
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Employee Data'),
content: Text('Do you want to show data?'),
actions: <Widget>[
FlatButton(
onPressed: () =>
Navigator.of(context).pop('No'),
child: Text('NO')),
FlatButton(
onPressed: () =>
Navigator.of(context).pop('Yes'),
child: Text('YES'))
],
));
});
return Container();
}
},
);
}
}
The builder param expects you to return a Widget. showDialog is a Future.
So you can't return that.
You show Dialog on top of other widgets, you can't return it from a build method that is expecting a widget.
What you want can be implemented the following way.
When you receive an error, show a dialog on the UI and return a Container for the builder. Modify your code to this:
if (snapShot.data.runtimeType == http.Response) {
var message =
json.decode(utf8.decode(snapShot.data.bodyBytes));
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text("Ok"),
onPressed: () => null",
)
],
);
});
return Container();
}