How to call navigator inside Stream Builder in flutter? - flutter

I have a problem that when I try to put the the navigator pop() method inside the string builder this error appears: setState() or markNeedsBuild() called during build.
I read that you can't call the navigator inside the stream builder so does anyone have any ideia on how to fix this?
This is my code:
return Scaffold(
resizeToAvoidBottomPadding: false,
body: Center(
child: Container(
padding: EdgeInsets.all(15),
color: Colors.white,
child: Column(
children: <Widget>[
SizedBox(height: 55),
SvgPicture.asset('images/svg_example.svg'),
SizedBox(height: 55),
Text("Login App",
style: TextStyle(
fontWeight: FontWeight.bold, color: Colors.black)),
SizedBox(height: 40),
emailField,
SizedBox(height: 45),
passwordField,
SizedBox(height: 45),
loginButton,
SizedBox(height: 15),
StreamBuilder<ApiResponse<LoginResponse>>(
stream: userBloc.authenticationUserStream,
builder: (context,
AsyncSnapshot<ApiResponse<LoginResponse>> snapshot) {
// it will observe changes on the ApiResponse<LoginResponse>
if (!snapshot.hasData) return Container();
switch (snapshot.data.status) {
case Status.LOADING:
return Loading(
loadingMessage: "loading",
);
case Status.COMPLETED:
prefs.saveTokenPref(snapshot.data.data.token);
prefs.saveUserPref(snapshot.data.data.user);
goToMain();
return Container(width: 0.0, height: 0.0);
case Status.ERROR:
// Here you can go to another screen after login success.
return Center(
child: Text("${snapshot.data.message}"),
);
default:
return Container();
}
},
)
],
)),
),
);
}
goToMain() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MainScreen()),
);
}

Ok, so given my question has a -1 because someone just feel like giving without really helping the post I put here the answer for this question:
You just need to do this on your initState()
This will listen to the stream itself and make UI logic outside the Stream Builder.
#override
void initState() {
userBloc = UserBloc();
super.initState();
userBloc.userSubject.listen((state) {
if (state.status == Status.COMPLETED) {
goToMain();
}
});
}

Related

Type 'Future<dynamic>' is not subtype of type 'Widget'

I am showing markers from API on google maps. Here is my build method. When the program reaches the _widgetbuilder() method, it throws the specific error of type Future is not a subtype of the widget. If someone could please help to solve the problem and also tell me that what exactly this error means.....
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: FutureBuilder<List<MarkersOnMap>>(
future: future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData)
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
if (snapshot.hasData && snapshot.data.isEmpty) {
return Center(
child: Container(
child: Column(
children: [
Text(
'No Properties Added Yet\nPlease Add Some!',
style:
TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
ElevatedButton.icon(
onPressed: () {
Navigator.push(
context,
PageTransition(
duration: Duration(microseconds: 500),
type: PageTransitionType.fade,
child: AddNewEproperty(
createEproperty: widget.createEproperty),
),
);
},
label: Text('Add'),
icon: Icon(Icons.add),
),
],
),
),
);
} else
_widgetbuilder();
if (snapshot.hasData) {
return ListView.builder(
itemCount: allWidgets.length + 1,
shrinkWrap: true,
padding: EdgeInsets.only(top: 16),
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, i) {
return Stack(
children: <Widget>[
Container(),],);},);},},),);}
This is the _widgetbuilder() method. When it reaches this return _widgetbuilder, throws _typeerror.
_widgetbuilder() async {
allWidgets = [];
widget.markersonmap = await future;
widget.markersonmap.forEach(
(element) {
print(element);
allWidgets.add(
Container(
height: 25,
width: 50,
child: new DecoratedBox(
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
borderRadius: BorderRadius.circular(5.0),
color: Colors.black54),
child: Text(
element.ePropertiesCardsList.price.toString(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
),
);
},
);
}
You are getting this error because your function _widgetbuilder returns Future<dynamic> because the function is async.
Widget _widgetbuilder(){
// code here
}
The function should be in this structure to return of type Widget. The code that needs to be async should either be taken out of build function or use .then pattern for async code instead of async await if you really need to.
This short 9 min video will help you understand async better in flutter.
In here now the type error is solved but after reading 'future.then..... it does not goto the future and fetch data but jumps to the next foreach line and then calls it as null.
_widgetbuilder() {
allWidgets = [];
// widget.markersonmap = await future;
future.then((value) {
widget.markersonmap = value;
});
widget.markersonmap.forEach(
(element) {
print(element);
allWidgets.add(
Container(
// other code
}

FutureBuilder not working in OnTap() of GestureDetector

Upon user registration, I wish to call my database at Firebase to check that the email is unique. If it is not, I wish to show some form of an error message to the user.
However, for some reason the build method of my FutureBuilder is not called at all and none of my widgets in the submit() function are shown:
final usersRef = Firestore.instance.collection("users");
class _CreateAccountWithEmailState extends State<CreateAccountWithEmail> {
Future<QuerySnapshot> existingUserWithEmail;
emailExists(String email) {
Future<QuerySnapshot> existingUserWithEmailFromDb =
usersRef.where("email", isEqualTo: email).getDocuments();
setState(() {
existingUserWithEmail = existingUserWithEmailFromDb;
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: header(context, titleText: "Complete your profile"),
body: ListView(children: [
....
Padding(
padding: EdgeInsets.only(top: topPadding),
child: Center(
child: GestureDetector(
onTap: () =>
{submit(context, context.read<UserSignupForm>().email)},
child: Container(
height: 50.0,
width: 350.0,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(7.0)),
child: Center(
child: Text(
"Save",
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
fontWeight: FontWeight.bold),
))),
),
),
)
]));
}
Widget submit(BuildContext context, String email) {
return FutureBuilder(
future: emailExists(email),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: Text('Please wait its loading...'));
} else {
if (snapshot.hasError)
return Center(child: Text('Error: ${snapshot.error}'));
else
return Center(
child: new Text(
'Test: ${snapshot.data}'));
}
});
}
}
Via debugging, I found that the future in the FutureBuilder is called, but not the builder. Any ideas please?
Okay, so basically calling submit function which returns FutureBuilder actually makes no sense because it has a return type of Widget.
I don't actually quite fully know what you are wanting to accomplish here.
But here's one solution to show the user if the user exists with the email already.
final usersRef = Firestore.instance.collection("users");
class _CreateAccountWithEmailState extends State<CreateAccountWithEmail> {
bool? userExistsAlready;
bool isBusy = false;
Future<void> emailExists(String email) {
setState(() {
isBusy = true;
});
final getDocs = await usersRef.where("email", isEqualTo: email).get();
setState(() {
userExistsAlready = getDocs.docs.isNotEmpty;
isBusy = false;
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: header(context, titleText: "Complete your profile"),
body: ListView(children: [
....
Padding(
padding: EdgeInsets.only(top: topPadding),
child: Center(
child: isBusy
? CircularProgressIndicator()
: GestureDetector(
onTap: ()
=> emailExists(context.read<UserSignupForm>().email),
child: Container(
height: 50.0,
width: 350.0,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(7.0)),
child: Center(
child: Text(
"Save",
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
fontWeight: FontWeight.bold),
),
)
),
),
),
),
userExistsAlready == null ? SizedBox.shrink() :
Text('User Exists Already: ${userExistsAlready}')
],
),
);
}

How to refetch the data inside a FutureBuilder in Flutter?

Here is my code:
#override
void initState() {
super.initState();
weatherFuture = _getWeather();
}
#override
Widget build(BuildContext context) {
double viewBoxWidth = MediaQuery.of(context).size.width;
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FutureBuilder(
future: weatherFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Container(
width: viewBoxWidth * 0.35,
color: Colors.orange, // Todo: Remove this color later
child: WeatherTileColumn(
topTileDisplayText: snapshot.data.areaName,
bottomTileDisplayText: snapshot.data.weatherDescription,
topTileIcon: 'πŸ—Ί',
bottomTileIcon:
_getWeatherIcon(snapshot.data.weatherConditionCode),
),
);
} else {
return Text(
'Loading Data...',
style: TextStyle(fontFamily: 'Merriweather', fontSize: 15),
textAlign: TextAlign.center,
);
}
}),
Container(
width: viewBoxWidth * 0.2,
margin: EdgeInsets.only(top: 95),
child: TextButton(
child: Icon(
Icons.refresh,
color: Colors.black87,
size: 55,
),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.transparent)),
onPressed: () {
setState(() {
weatherFuture = _getWeather();
});
},
),
),
FutureBuilder(
future: weatherFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Container(
width: viewBoxWidth * 0.35,
color: Colors.purple,
child: WeatherTileColumn(
topTileDisplayText: snapshot.data.temperature.toString(),
bottomTileDisplayText:
snapshot.data.tempFeelsLike.toString(),
topTileIcon: '🌑',
bottomTileIcon: 'πŸ˜Άβ€πŸŒ«οΈ',
),
);
} else {
return Text(
'Loading Data...',
style: TextStyle(fontFamily: 'Merriweather', fontSize: 15),
textAlign: TextAlign.center,
);
}
},
)
],
),
);
In this code, if you look at the main widgets inside the children of the Row widget, you will see that there are two FutureBuilder widgets and a Container widget.
What has to happen is upon pressing that TextButton inside the Container, I want the two future builder widgets to be re updated with newly fetched data.
My current attempt was to do:
setState() {
weatherFuture = _getWeather();
}
inside the callback function for the onPressed method. And it does not work. I looked at many solutions and articles and could not get this to work.
(Also I checked Reload data when using FutureBuilder stack overflow post and this is not a duplicate. In that post, the button that rebuilds the FutureBuilder widget is inside the FutureBuilder widget but in this case it isn't. So that solution did not work for me.)

Flutter: Future keeps rebuilding because it is being called inside a stream. How to only make it call again, if possible?

So I wanted to display a list of songs but the future that displays a Uint8List artwork of the songs is called from a future. The code works but the album art looks as if it is glitching because it is constantly being called. I had not idea how to fix this and I have tried many solutions. Please help.
Here is my code:
StreamBuilder<List<SongInfo>>(
stream: widget.songs,
builder: (context, snapshot) {
if (snapshot.hasError)
return Utility.createDefaultInfoWidget(Text("${snapshot.error}"));
if (!snapshot.hasData)
return Utility.createDefaultInfoWidget(
CircularProgressIndicator());
return (snapshot.data.isEmpty)
? NoDataWidget(
title: "There is no Songs",
)
: Column(
children: [
Container(
padding:
EdgeInsets.symmetric(vertical: 10, horizontal: 15),
alignment: Alignment.centerRight,
child: Text("${snapshot.data.length} Songs"),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (context, songIndex) {
SongInfo song = snapshot.data[songIndex];
return ListItemWidget(
title: Text("${song.title}"),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: <Widget>[
Text("Artist: ${song.artist}"),
Text(
"Duration: ${Utility.parseToMinutesSeconds(int.parse(song.duration))}",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500),
),
],
),
trailing: (widget.addToPlaylistAction == true)
? IconButton(
icon: Icon(Icons.playlist_add),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(_dialogTitle),
content: FutureBuilder<
List<PlaylistInfo>>(
future: model.getPlayList(),
builder:
(context, snapshot) {
if (snapshot.hasError) {
print("has error");
return Utility
.createDefaultInfoWidget(
Text(
"${snapshot.error}"));
}
if (snapshot.hasData) {
if (snapshot
.data.isEmpty) {
print("is Empty");
return NoDataWidget(
title:
"There is no playlists",
);
}
return PlaylistDialogContent(
options: snapshot.data
.map((playlist) =>
playlist.name)
.toList(),
onSelected: (index) {
snapshot.data[index]
.addSong(
song: song);
Navigator.pop(
context);
},
);
}
print("has no data");
return Utility
.createDefaultInfoWidget(
CircularProgressIndicator());
}),
);
});
},
tooltip: "Add to playlist",
)
: Container(
width: .0,
height: .0,
),
leading: song.albumArtwork == null
? FutureBuilder<Uint8List>(
future: model.audioQuery.getArtwork(
type: ResourceType.SONG,
id: song.id,
size: Size(100, 100)),
builder: (context, snapshot) {
SchedulerBinding.instance
.addPostFrameCallback(
(_) => setState(() {
isServiceError = false;
isDataFetched = true;
}));
if (snapshot.data.isEmpty)
return CircleAvatar(
backgroundImage: AssetImage(
"assets/images/title.png"),
);
if (isDataFetched) {
return CircleAvatar(
backgroundColor: Colors.transparent,
backgroundImage: MemoryImage(
snapshot.data,
),
);
} else {
return CircleAvatar(
child: CircularProgressIndicator(),
);
}
})
: CircleAvatar(
backgroundImage: FileImage(
IO.File(song?.albumArtwork)),
),
);
},
),
),
],
);
},
I would prefer not to set state inside future builders or stream builders using post-frame callbacks. The reason being you basically asking flutter to build the widget again in the next frame while building the current one which recursively sets the whole thing in a loop. Maybe you can create a new stateful widget and do the loading task manually inside the initState if you need those isServiceError and isDataFetched flags.
The problem in your current code seems to be related to:
SchedulerBinding.instance
.addPostFrameCallback((_) => setState(() {
isServiceError = false;
isDataFetched = true;
}));
Which is called inside the future builder. Everytime you set the state, the same code is called again as the widget is rebuilt thus forming a loop in which the whole thing is built again and again needlessly.
You can avoid it by checking the flags before assigning a post-frame callback like so:
if(!isDataFetched)
{
SchedulerBinding.instance
.addPostFrameCallback((_) => setState(() {
isDataFetched = true;
}));
}
So in the next frame, isDataFetched will be true hence no further post-frame callbacks.
This solution however is not really a proper solution because as I mentioned above, it's not a good idea to set the state in future builders using post-frame callbacks. If you don't need those flags outside the future builder, you should simply avoid them and rely on snapshot.hasData and snapshot.hasError inside the builder itself.

How to access Provider providers in Dialogs in Flutter

The Provider package makes use of InheritedWidget. This is a problem when I want to access a provider when I'm in a Dialog. If I load a dialog using
showDialog(... builder: (context) => MyDialog);
I can't access anything using InheritedWidget because my dialog isn't part of the main widget tree. This also means that I can't access my Provider providers, correct?
My question is: How can I access my providers in a dialog if it's not part of the main app widget tree?
final firebaseAuth = Provider.of<FirebaseAuth>(context);
I have the same problem with using BLoCs. If I try to retrieve them in a dialog via InheritedWidget, they fail. I've gotten around this by passing the BLoC in the constructor but this seems to defeat the purpose of InheritedWidgets.
Instead of passing the BLoC in the constructor, you can make use of BlocProvider.value.
https://pub.dev/documentation/flutter_bloc/latest/flutter_bloc/BlocProvider/BlocProvider.value.html
This will allow you to provide your existing BLoC instance to your new route (the dialog). And you still get all the benefits of InheritedWidget
// Get the BLoC using the provider
MyBloc myBloc = BlocProvider.of<MyBloc>(context);
showDialog(
context: context,
builder: (BuildContext context) {
Widget dialog = SimpleDialog(
children: <Widget>[
... // Now you can call BlocProvider.of<MyBloc>(context); and it will work
],
);
// Provide the existing BLoC instance to the new route (the dialog)
return BlocProvider<MyBloc>.value(
value: myBloc, //
child: dialog,
);
},
);
.value() also exists for ChangeNotifierProvider, ListenableProvider, etc.
https://pub.dev/documentation/provider/latest/provider/ChangeNotifierProvider/ChangeNotifierProvider.value.html
https://pub.dev/documentation/provider/latest/provider/ListenableProvider/ListenableProvider.value.html
I got stuck at this part for a while. I honestly didn't want to pass the provider, also unpacking the widget code to grab the parent context is hard when you are dealing with a complex widget (And it doesn't seem like the best approach).
This made more sense
handleFileViewerClicked(context) async {
var reportState = Provider.of<ReportState>(context, listen: false);
/**
*The dialog will live in a new context and requires a new provider to be created for the report state
* For more information read the Provider.Consumer documentation and showDialog function signature.
*/
showDialog(
context: context,
//Notice the use of ChangeNotifierProvider<ReportState>.value
builder: (_) => ChangeNotifierProvider<ReportState>.value(
value: reportState,
child: FileViewer(),
),
);
}
Your child widget which is FileViewer in that case can make use of
class FileViewer extends StatelessWidget {
.
.
Widget build(BuildContext context) {
//you can enable or disable listen if you logic require so
var reportState = Provider.of<ReportState>(context);
return Text('${reportState.files.length}');
}
}
I was able to access Provider data by passing in the data set into the alert dialog. Interestingly, you have to call setState() in the Dialog in order to see the changes in your Dialog.
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
final provider = Provider.of<DataSet>(context);
return Scaffold(
body: Container(
child: RaisedButton(
child: Text('Show Dialog'),
onPressed: () {
showDialog(context: context,
builder: (context) {
return DialogContent(dataSet: provider);
});
},
),
),
);
}
}
class DialogContent extends StatefulWidget {
final DataSet dataSet;
const DialogContent({Key key, this.dataSet}) : super(key: key);
#override
_DialogContentState createState() => _DialogContentState();
}
class _DialogContentState extends State<DialogContent> {
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Dialog with data'),
content: Text('${widget.dataSet.pieceOfData}'),
actions: <Widget>[
FlatButton(
child: Text('Increase Data'),
onPressed: () {
setState(() {
widget.dataSet.increaseData();
});
},
),
],
);
}
}
class DataSet with ChangeNotifier {
int pieceOfData = 1;
increaseData() {
pieceOfData += 1;
notifyListeners();
}
}
Try this. Create a different stateful widget that housed the dialog and return that dialog stateful widget when you call a showDialog() method. Example below
class MainScreen extends StatefulWidget {
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build((BuildContext context) {
MainProvider mainProvider = MainProvider.of(context);
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
),
body: Center(
child: Container(
child: RaisedButton(
onPressed: ()=> _openBottomSheet(context, mainProvider),
child: Text("Open Dialog"),
)
)
)
);
}
_openBottomSheet(BuildContext context, MainProvider mainProvider) async {
await showModalBottomSheet<bool>(
context: cntxt,
builder: (_) {
return BottomSheetDialog();
}
);
}
}
class BottomSheetDialog extends StatefulWidget {
#override
_BottomSheetDialogState createState() => _BottomSheetDialogState();
}
class _BottomSheetDialogState extends State<BottomSheetDialog> {
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
MainProvider mainProvider = MainProvider.of(context);
return Container(
width: MediaQuery.of(context).size.width,
height:MediaQuery.of(context).size.height/2.2,
margin: EdgeInsets.fromLTRB(16,16,16,0),
decoration: BoxDecoration(
color: mainProvider.color,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
child: RaisedButton(
onPressed: ()=> mainProvider.changeColor(),
child: Text("Open Dialog"),
)
)
}
}
class MainProvider with ChangeNotifier {
static MainProvider of(BuildContext context) {
return Provider.of<MainProvider>(context);
}
Color _color = Colors.white;
bool _isColorChanged = false;
Color get color => _color;
bool get isColorChanged => _isColorChanged;
changeColor() {
if(!isColorChanged) {
_color = Colors.green;
}else{
_color = Colors.white;
}
_isColorChanged = !_isColorChanged;
notifyListeners();
}
}
If that's an option for you, simply lift the provider up above MaterialApp. This might be a good solution for globally unique providers, e.g. user configurations or similar:
You have to pass the thing being provided directly to the dialog constructor to access it in the dialog's new context. You can also give it to a new Provider widget at the top of your dialog tree if you have a very deep widget tree in the dialog and you want to access it from somewhere deeper.
If you are using Bloc, typically you tell Provider to call the Bloc's dispose method when the provider widget is disposed to clean up the streamcontrollers/subscriptions. Obviously, you might not want to do this if you are re-providing the bloc to the dialog, or if this bloc is used outside the dialog.
Using stateful or stateless widgets in the dialog is up to you, as long as you have access to the bloc you can use a streambuilder and listen to some stream as per usual.
an example:
class EditEventDialog extends StatelessWidget {
final GroupBloc groupBloc;
EditEventDialog({this.groupBloc})
: assert(groupBloc != null);
#override
Widget build(BuildContext context) {
return Provider(
builder: (context) => groupBloc,
child: Dialog(
child: Container(
height: 400.0,
width: 200.0,
child: StreamBuilder<StatusStreamType>(
stream: groupBloc.statusStream,
builder: (context, snapshot) {
....
and to call it:
onPressed: () => showDialog(
builder: (newContext) {
GroupBloc groupBloc = Provider.of<GroupBloc>(context);
return EditEventDialog(
groupBloc: groupBloc,
);
},
context: context,
)
I faced the same issue today and I was able to work around it by wrapping the dialog in a Stateful Builder and setting the state in the new widget tree.
context: context,
builder: (context) {
return StatefulBuilder(builder: (context, setState) {
return Dialog(
child: SingleChildScrollView(
child: Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: height * .05),
child: Text('Choose An Avatar'),
),
Stack(
children: <Widget>[
Align(
alignment: Alignment.center,
child: CircleAvatar(
minRadius: width * .09,
maxRadius: width * .09,
backgroundColor: Colors.brown,
backgroundImage: AssetImage(
'assets/profile${appData.avatar}.png'),
),
),
Positioned.fill(
left: width * .04,
child: Align(
alignment: Alignment.centerLeft,
child: Container(
width: width * .18,
child: Material(
color: Colors.transparent,
child: InkWell(
child: Icon(Icons.arrow_left,
size: width * .18),
onTap: () {
setState(() {
appData.changeAvatar();
});
},
),
),
),
),
),
],
),
],
),
),
),
),
);
});
});
I only way I've found to gain access to the Bloc provider from within the dialog is by defining the dialog outside of the showDialog call.
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocConsumer<MyCubit, MyState>(
listener: (context, state) {
if (state.shouldShowDialog == true) {
final dialog = AlertDialog(
content: Text("Info");
actions: <Widget>[
TextButton(
child: const Text('Approve'),
onPressed: () => {
context
.read<MyCubit>()
.handleDialogApproved();
Navigator.of(context, rootNavigator: true).pop();
}
)
],
);
showDialog<void>(
context: context,
builder: (BuildContext context) {
return dialog;
},
);
}
},
builder: (context, state) {
return Container();
},
);
}
}
Widget reviseRatesButton(BuildContext c) {
return Consumer<RideRequestProvider>(
builder: (c, provider, child) {
return OutlinedButton(
onPressed: () async {
alertDialogNew(
c,
content: ChangeNotifierProvider.value(
value: provider,
builder: (context, child) {
return Consumer<RideRequestProvider>(
builder: (context, provider, child) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Text(
"Offer your fare",
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
const SizedBox(
height: 5,
),
CustomTextFormField(
hint: "Enter your fair/day",
keyboardType: TextInputType.number,
controller: provider.fareController,
onChanged: (String? val) {
provider.calculateFare();
},
),
const SizedBox(
height: 5,
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Weekly (5 days)',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
Text.rich(
TextSpan(
text: provider.weeklyFare
.toStringAsFixed(2),
children: [
TextSpan(
text: '/week',
style: TextStyle(
color: Colors.blue.shade700,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
],
),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
Column(
children: [
const Text(
'Monthly(22 days)',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
Text.rich(
TextSpan(
text: provider.monthlyFare
.toStringAsFixed(2),
children: [
TextSpan(
text: '/month',
style: TextStyle(
fontSize: 12,
color: Colors.blue.shade700,
fontWeight: FontWeight.w600,
),
),
],
),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
],
),
],
),
);
},
);
}),
);
},
child: const Text(
"Revise Rates",
),
style: OutlinedButton.styleFrom(
side: const BorderSide(width: 1.0, color: Colors.blue),
),
);
},
);}
I've been stuck at this for a few moments, but ChangeNotifierProvider.value works like a charm.
A bit late in finding this, but just had this same challenge and realised a solution: You need to maintain a reference to the context outside of the showDialog call. By default we usually just use "context" as the name of the context both outside and inside the showDialog, thus masking the outside context from use within the showDialog. So, instead, use a different name inside the showDialog (e.g. "c") and then you can still use "final firebaseAuth = Provider.of(context);" inside the showDialog and it will find the FirebaseAuth object from the main tree as you wish.
Here's a short excerpt from some code I am working on which works now:
showDialog(
context: context,
builder: (c) {
final action = Provider.of<ActionType>(context);
final host = Provider.of<String>(context);
return AlertDialog(
title: Text('Action API'),
actions: [
FlatButton(
onPressed: () {
Navigator.pop(c);
},
etc.