I want to open a dialog in a parent statefull widget and use a callback to trigger that function from child class statefull widget, but the function called in parent does not get executed from child, the function does get executed but the showDialog seems not to be used, I even tried to move the function responsible to open to dialog to the child class but showDialog does not work either.
Here the code:
shareDilog function in parent class:
shareDialog(screenWidth, BuildContext cont) {
return showDialog(
context: cont,
builder: (BuildContext cont) {
print('inside inside');
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
elevation: 0,
child: StatefulBuilder(
builder: (BuildContext cont, StateSetter setState) {
return Container(
height: 680,
width: 650,
decoration: BoxDecoration(
color: Color(0xFF282828),
),
child: ShareDialog(),
);
}),
);
},
barrierDismissible: false
);
}
Where I pass this shareDialog function in parent to child:
Record(
visibleColumns: visibleColumns,
recordFullNameFieldName: recordFullNameFieldName,
oneRecord: listRecordsFilter[index],
screenWidth: screenWidth,
openShareDialog: shareDialog, // <-------
dashboardContext: context, // <--------
)
Where the call gets executed in child:
final Function(double, BuildContext)? openShareDialog; // the constructor parameter
PopupMenuItem(
onTap: () {
try {
widget.openShareDialog!(widget.screenWidth!, widget.dashboardContext!); // <---here the call back
// _showMyDialog(); // This is the test when I moved the function to the child
} catch(e) {
print(e);
}
},
child: ListTile(
leading: Icon(Icons.groups_sharp),
title: Text('Share'),
),
),
This can be achieved in many ways, here is my way:
Create the shareDialog() function in the parent widget. please try to assign different names for all the contexts. this may confuse flutter.
here is a mini version of your code for the same:
shareDialog(screenWidth, BuildContext cont) {
return showDialog(
context: cont,
builder: (ctx) {
print('inside inside');
return Dialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
elevation: 0,
child: Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Color(0xFF282828),
),
child: Text(
"Hello World!",
style: TextStyles.title2.colour(Colors.white),
),
),
);
},
);}
Next pass the function and other parameters to the child widget.
Record(showDialog: shareDialog)
and finally, use the passed function in the child widget. here I have created a dummy Record widget that contains a listTile which on tapping executes the shareDialog function:
class Record extends StatelessWidget {
final Function showDialog;
Record({this.showDialog});
#override
Widget build(BuildContext context) {
return ListTile(
onTap: () => showDialog(SizeConfig.screenWidth, context),
title: Text("Tile"),
leading: CircleAvatar(
backgroundColor: Colors.amber,
child: Icon(Icons.umbrella, color: Colors.white),
),
subtitle: Text("Show Share dialog"),
);
}
}
Related
I have a custom popup built, and an image is supposed to change whenever one of my variables is changed. When I call the setState method, the content in my showDialog doesn't change.
What am I doing wrong, or is there a better approach? Trying to change the state so the image can be changed in the showDialog.
Here's my code:
class LocationManagerPage extends StatefulWidget {
const LocationManagerPage({Key? key}) : super(key: key);
#override
State<LocationManagerPage> createState() => _LocationManagerPageState();
}
class _LocationManagerPageState extends State<LocationManagerPage> {
String downloadURL = "";
Future _uploadFile(String path) async {
// Logic that gets the download url to an image...
// When the download url is found, calling setState method
setState(() {
downloadURL = fileUrl;
});
}
showLocationPopup() {
return showDialog(
context: context,
builder: (context) {
return Center(
child: Material(
child: Container(
width: 427,
height: 676,
decoration: BoxDecoration(...),
child: SingleChildScrollView(
child: Column(
children: [
// Popup UI Widgets,
Center(
child: Container(
height: 150,
width: 150,
decoration: BoxDecoration(),
child: ClipRRect(
child: Image.network(
image,
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(20),
),
),
),
SizedBox(
height: 15,
),
Center(
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () async {
String? imageUrl = await urlFromWebImage();
print(imageUrl);
setState(() {
downloadURL = imageUrl!;
});
},
child: Button(
name: imageName,
),
),
),
),
// The rest of the popup UI
],
),
),
),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
.... // Not Important
);
}
}
To update dialog ui you need to use StatefulBuilder widget on showDialog's builder and use StatefulBuilder's setState.
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setState) => AlertDialog(
you can use the following approach
first initialize download url like this
ValueNotifier<String> downloadUrl = ValueNotifier("");
ValueListenableBuilder(
valueListenable: downloadUrl,
builder: (context, value, Widget? c) {
return Container(
height: 150,
width: 150,
decoration: BoxDecoration(),
child: ClipRRect(
child: Image.network(
downloadUrl.value, // here put download url it will auto update
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(20),
));
});
and without using setstate put value in download url it will auto update ui
downloadUrl.value = "" //your image url
or you can use StateFulBuilder
setstate rebuild your whole widget but upper approach only build image widget
I am trying to call setState inside an AlertDialog, but surprisingly setState is not changing the state variable. The variable (tasbeehChantCanAdd) i want to set from the dialog box is inside the body of the main page(outside the dialog box) This is the code for the alertDialog:
Future<void> _alertRemoveEntry(BuildContext context, String textToRemove) {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: (){},
child: StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(15.0),
),
),
title: const Text('Enter your info',style: TextStyle(fontWeight: FontWeight.normal,fontSize: 14),),
content: Container(
height: 150,
child: const Text("Sure to remove this?")
),
actions: <Widget>[
Container(
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
//side: BorderSide(color: Colors.red)
),
primary: Colors.purple,
),
child: Text("CANCEL"),
onPressed: () {
Navigator.of(context).pop();
},
),
Container(
width: 20,
),
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
//side: BorderSide(color: Colors.red)
),
primary: Colors.purple,
),
child: Text("REMOVE"),
onPressed: (){
setState((){
tasbeehChantCanAdd = "state changed";
});
ClassHub().myListSharePreference("sptasbeehAddedList", "set", tasbeehChantCanAdd, "");
Navigator.of(context).pop();
},
),
],),),
],
);
},
),
);
});
}
Please what am i doing wrong? Any help would be appreciated. Thanks
The main state class also have setState and while you are using StatefulBuilder it also has setState, being scope priority setState is coming from StatefulBuilder. You can rename it and use
child: StatefulBuilder(
builder: (context, setStateSB) {
....
setState((){ /// for state class IU update
tasbeehChantCanAdd = "state changed";
});
setStateSB((){ // update Ui inside dialog
tasbeehChantCanAdd = "state changed";
});
Try to add your AlertDialog widget inside StatefulBuilder hope its helpful to you.
Refer StatefulBuilder here
yourDropdown(BuildContext context) {
return showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, StateSetter setState) {
return AlertDialog(
);
},
);
},
);
}
Refer my answer here
Are you me from another dimension? Seriously, I just solved the same problem. I don't know if it is the recommended way of setting states on dialog, since the documentation on showDialog mentions something about states, but I solved it this way:
var choice = await showDialog(
context: context,
builder: (context) {
var list = [];
return StatefulBuilder(builder: (BuildContext context, setState) {
return AlertDialog(
[...]
);
});
});
Just put the variables you need to update through setState just before the StatefulBuilder
I have a dialog that appears after user click on send button . When dialog appears , I want to show a text after 5 seconds . I use Future.delayed but the text doesn't appear at all . It appears only when I close the dialog and open it again . I want to show the text after 5 seconds from opening the dialog .
Here is my function
void _initialize() {
Future<void>.delayed(const Duration(seconds: 3), () {
if (mounted) {
setState(() {
visibility = true;
});
}
});
}
}
And here is my dialog code in the onTab of the button
_initialize()
showDialog(context: context,
barrierDismissible: false,
builder: (BuildContext contextd){
return WillPopScope(
onWillPop: () {return Future.value(false);},
child: Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)
),
child: Padding(
padding: EdgeInsets.fromLTRB(20.w, 20.h, 20.w, 20.h),
child: Column(
children: [
//here are some widgets
Visibility(
visible:visibility?true:false,
child: Text("Resend?",style: TextStyle(decoration:
TextDecoration.underline,),)),
],),
),
),
);
});
Try using StatefulBuilder like this:
showDialog(
context: context,
builder: (BuildContext context) => StatefulBuilder(
builder: (context, setState) {
return //somthing to return;
},
));
This happens because your setState that update visibility has a different context than builder dialog. Visibility is actually update but only appear in dialog when close and open again because this is how build dialog is updated. If you want to work with the context dialog need to use that widget:
StatefulBuilder(
builder: (context, setState) {
Into your dialog and then call that setState.
Try using a StatefulBuilder. Using the bool visible and the setDialogState argument, you can update the state of the contents of the dialog.
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return StatefulBuilder(
builder(context, setDialogState) {
// this variable is used for the conditional display
bool visible = false;
// this future will update the visible variable in 3 seconds, after the dialog is displayed already
Future.delayed(Duration(seconds: 3)).then(() => setDialogState(() { visible = true;}));
return WillPopScope(
onWillPop: () { return Future.value(false); },
child: Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)
),
child: Padding(
padding: EdgeInsets.fromLTRB(20.w, 20.h, 20.w, 20.h),
child: Column(
children: [
//here are some widgets
Visibility(
visible:visible,
child: Text(
"Resend?",
style: TextStyle(
decoration: TextDecoration.underline,
),
),
),
],
),
),
),
);
},
);
};
For the elevated button, I would like to show the visible/hide part content through setState. However, I could do it in normal pages but not a Dialog.
Here's the code:
bool addmaterial = true;
ClipRRect(
borderRadius: BorderRadius.circular(50),
child: SizedBox(
height: 40,
width: 400,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all<Color>(
Color(0xfff4f4f4)),
),
onPressed: () {
addmaterial = !addmaterial;
},
Try adding a StateFulBuilder inside your showDialog widget. If you want to change the state inside the showDialog
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
actions: <Widget>[],
content: Container(),
);
},
);
},
);
I'm new to flutter and I'm trying to understand How to use provider state management in an application which users Moor to save some data into sqlite table. My application is a task recording application. I'm getting the above error in my widget tree when I open my bottom sheet add a task. I'm using provider: ^4.3.1
class TaskView extends StatelessWidget {
DateTime selectedDate = DateTime.now();
#override
Widget build(BuildContext context) {
return Provider<TaskViewModel>(
create: (_) => TaskViewModel(),
child: Scaffold(
appBar: AppBar(
title: Text('Tasks'),
),
body: Text("Temporary body!"),
floatingActionButton: FloatingActionButton(
onPressed: () {
showMaterialModalBottomSheet(
context: context,
builder: (context, scrollController) => Container(
child: bottomSheet(context),
),
);
},
child: Icon(
Icons.add,
color: Colors.white,
),
backgroundColor: Colors.blueAccent,
)
)
);
}
Widget bottomSheet(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
left: 16.0,
top: 16.0,
right: 16.0,
bottom: 16.0 + MediaQuery.of(context).viewInsets.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Task',
),
),
SizedBox(height: 8),
TextField(
readOnly: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(Icons.date_range, color: Colors.grey),
onPressed: () => _selectDate(context),
),
labelText: 'Date',
),
),
SizedBox(height: 8),
Align(
alignment: Alignment.topRight,
child: context.watch<TaskViewModel>().state == ViewState.IDLE
? FlatButton(
child: Text("Save"),
color: Colors.blueAccent,
textColor: Colors.white,
onPressed: () => _onClickInsertTask(context))
: _loadingButtonChild(context))
],
),
);
}
Widget _loadingButtonChild(BuildContext context) {
return Container(
height: 20,
width: 20,
margin: EdgeInsets.all(5),
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white)),
);
}
/// This function is responsible for displaying the date picker when user click
/// on task due date inputFiled
Future<Null> _selectDate(BuildContext context) async {
final DateTime picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(2015, 8),
lastDate: DateTime(2101));
if (picked != null && picked != selectedDate) {
print("Date selected ${selectedDate.toString()}");
}
}
/// This function is responsible for triggering insert task block event
void _onClickInsertTask(BuildContext context) {
var insertTask = TaskData(task: "task", dueDate: selectedDate);
context.read<TaskViewModel>().insertTask(insertTask);
}
}
The error suggested checking.
- The provider you are trying to read is in a different route.
I have not given the provider to s route but as the direct parent view.
- You used a BuildContext that is an ancestor of the provider you are trying to read.
I didn't understand what it means but I made the suggested fix in the error. like below
#override
Widget build(BuildContext context) {
return Provider<TaskViewModel>(
create: (_) => TaskViewModel(),
builder: (context, child) => Scaffold(
appBar: AppBar(
title: Text('Tasks'),
),
body: Text("Temporary body!"),
floatingActionButton: FloatingActionButton(
onPressed: () {
showMaterialModalBottomSheet(
context: context,
builder: (context, scrollController) => Container(
child: bottomSheet(context),
),
);
},
child: Icon(
Icons.add,
color: Colors.white,
),
backgroundColor: Colors.blueAccent,
)));
}
Still get the same error. Another thing to note here is error suggested the below.
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
But I could not find a builder: (context) so I used the builder: (context, child). Please let me know what I should change to get this working. Thanks.
Edit:
BaseModel
class BaseViewModel extends ChangeNotifier {
ViewState _state = ViewState.IDLE;
ViewState get state => _state;
void setState(ViewState viewState) {
_state = viewState;
notifyListeners();
}
}
TaskViewModel
class TaskViewModel extends BaseViewModel{
final TaskRepository _repository = TaskRepository();
Resource<int> insertTaskStatus;
Future<void> insertTask(TaskData task) async {
setState(ViewState.PROCESSING);
var tasksCompanion = TasksCompanion(task: Value(task.task),dueDate: Value(task.dueDate));
insertTaskStatus = await _repository.insertTask(tasksCompanion);
setState(ViewState.COMPLETED);
}
}
Although you call showMaterialModalBottomSheet in the Scaffold wrapped by the provider, the provider is not above both TaskView's Scaffold and the modalBottomSheet. Why?
The provider you are trying to read is in a different route.
So, it seems that the modalBottomSheet is on a different route that doesn't have a provider above. If you take a look at the implementation of showModalBottomSheet you'll see:
return Navigator.of(context, rootNavigator: useRootNavigator).push(_ModalBottomSheetRoute<T>(....);
Clearly, it's a new route. So, to access the provider it should be above both routes. Since, the modalBottomSheet route is managed by the MaterialApp, you have to place the provider above the MaterialApp.
Provider uses lazy loading by default. So, objects are created when they are required and not on app start. However, if you don't want this behavior you can set lazy: false individually. For more info check the offical docs.
Another Easier option, for example if you are creating a package, that is supposed to inherit to be used in Another materialApp and you wish to use showModalBottomSheet, to read context of the parent provider, you have to disable the context of the showModalBottomSheet. This will force it to use the tree that has context for the provider in the parent widget.
#override
Widget build(BuildContext context) {
return Provider<TaskViewModel>(
create: (_) => TaskViewModel(),
builder: (context, child) => Scaffold(
appBar: AppBar(
title: Text('Tasks'),
),
body: Text("Temporary body!"),
floatingActionButton: FloatingActionButton(
onPressed: () {
showMaterialModalBottomSheet(
context: context,
builder: (_) => Container(
child: bottomSheet(context),
),
);
},
child: Icon(
Icons.add,
color: Colors.white,
),
backgroundColor: Colors.blueAccent,
)));
}