Navigator.push not working inside async method - flutter

I am trying to automatically navigate to another screen after a future method inside an async method. But it's not working on the first-page launch. I keep getting this error.
**Error**
I/flutter ( 5094): Looking up a deactivated widget's ancestor is unsafe.
I/flutter ( 5094): At this point the state of the widget's element tree is no longer stable.
I/flutter ( 5094): To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
void getPlaceDetails(String placeId, BuildContext ctx, String type) async {
try{
await mainBloc.fetchRideEstimate(context,
mainBloc.pickUpAddress.longitude,
mainBloc.pickUpAddress.latitude,
mainBloc.destinationAddress.longitude,
mainBloc.destinationAddress.latitude).then((value){
Navigator.pop(ctx);
//keeps throwing an error here during first screen launch.
Navigator.push(
context, SlideFromLeftPageRoute(widget:
EstimatedSummaryPage()));
});
}catch(e){
print(e.toString());
}
}
print((thisPlace.placeName));
}
Build Widget
isSearchingFrom?
Expanded(child:
MediaQuery.removePadding(
context: context,
removeTop: true,
child: ListView.separated(
itemBuilder: (context, index){
return FlatButton(
padding: EdgeInsets.zero,
onPressed: () {
getPlaceDetails(pickUpPredictionlist[index].placeId,
_scaffoldKey.currentContext, "from");
},
child: PlacesListTile(
prediction: pickUpPredictionlist[index],
),
);
},
separatorBuilder: (context, index) {
return Divider(
height: 2,
);
},
itemCount: pickUpPredictionlist.length),
)):
Container(),

Related

Flutter context error after multiple pops on dialogs

I have a function called from a button in one of my menu pages that builds an AlertDialog passing a context.
The dialog contains a button that calls a function (called testFunction) that:
first disposes the current dialog using the passed context;
then creates a new loading dialog;
then calls an async function which, when done, disposes the current loading dialog and creates a new final dialog.
But it gives me this error when I try to build the loading dialog on the third step:
E/flutter ( 2550): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
E/flutter ( 2550): At this point the state of the widget's element tree is no longer stable.
E/flutter ( 2550): To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
The function called from the menu button:
static void buildDeckPurchaseDialog(BuildContext context) {
showDialog(context: context, builder: (BuildContext context) {
return AlertDialog(
content: SizedBox(
width: 80,
height: 130,
child: Center(
MenuAnimatedButton(
width: 110,
height: 50,
function: () => testFunction(context), // Executed on button tap
),
),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
backgroundColor: kBoxColor,
);
});
}
testFunction() called from the button in the dialog built from the previous function:
Future<dynamic> testFunction(BuildContext context) async {
try {
// Disposing the previous dialog
Navigator.of(context).pop();
// Showing loading dialog
CustomDialogs.buildLoadingDialog(context, "Processing purchase...");
// Making the async request
return await FirebaseFunctions.instance.httpsCallable('test').call({
'test': 1,
}).then((value) {
// Disposing the loading dialog
Navigator.of(context).pop(); // <- ERROR HERE
// Building the last dialog (which is not shown)
CustomDialogs.buildSimpleDialog("End of function", context);
}).onError((error, stackTrace) => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const ErrorScreen())));
} on FirebaseFunctionsException {
Navigator.push(context, MaterialPageRoute(builder: (context) => const ErrorScreen()));
}
}
I think I should use didChangeDependencies() method but I don't know how.
What I was doing wrong was giving the context passed to the buildDeckPurchaseDialog function the same name as the context created by the showDialog function builder (builder: (BuildContext context)).
This way, testFunction(context) took the builder context as an argument and not the passed context.
Then write the function like this:
static void buildDeckPurchaseDialog(BuildContext passedContext) {
showDialog(context: passedContext, builder: (BuildContext context) {
return AlertDialog(
content: SizedBox(
width: 80,
height: 130,
child: Center(
MenuAnimatedButton(
width: 110,
height: 50,
function: () => testFunction(passedContext), // Executed on button tap
),
),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
backgroundColor: kBoxColor,
);
});
}

FutureBuilder not updated after setState call

I have a FutureBuilder that returns a ListViewBuilder in a class.
When loading the class the FutureBuilder loads the future which is a call to a API, then the ListView shows the received items inside Cards.
It is working fine, at this moment there are three items that should and are showed.
Then I am trying to verify if the class is updated when executing setState at a button click action. I am manually adding or removing items from the database that is called from the API, but clicking on the refres button after adding/removing items from the database, the list is not changing.
Here you have the code:
Container(
height: 120,
child:
FutureBuilder(
future: fetchFotosInformesIncidenciasTodos(
widget.informeActual.codigo),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<dynamic>? filteredList =
snapshot.data as List?;
filteredList ??= [];
listaFotosInformeIncidenciasActual =
filteredList;
WidgetsBinding.instance
.addPostFrameCallback((t) {
setState(() {
numeroFotosSubidas =
filteredList!.length +
numeroFotosSubidasAhora;
});
});
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: filteredList.length,
shrinkWrap: false,
itemBuilder: (BuildContext context, index) {
FotoInformeIncidenciasModelo foto =
filteredList![index];
var urlFoto = Constantes
.adminInformesIncidenciasUrl +
foto.archivo;
return GestureDetector(
onTap: () {
print("pulsada foto ${foto.id}");
},
child: Card(
elevation: 6,
child: (Column(
children: [
Image.network(
urlFoto,
width: 60,
height: 80,
),
],
)),
));
},
);
}
return Image.asset(
"imagenes/vacio.png",
fit: BoxFit.contain,
);
},
),
),
And here the refresh button:
InkWell(
onTap: (){
setState(() {
print("refrescando");
});
},
child: Text("refrescar")),
I would like to know why is the call to setState not forcing to update the FutureBuilder and the ListView Builder
The future function fetchFotosInformesIncidenciasTodos(widget.informeActual.codigo)
which is being called directly from the Future block. You need to make an instance of the future and invoke the same whenever you want a new request for the future eg.
Future<Response> _futureFun;
....
#override
void initState() {
super.initState();
_futureFun =
fetchFotosInformesIncidenciasTodos(widget.informeActual.codigo)
}
_futureFun = fetchFotosInformesIncidenciasTodos(widget.informeActual.codigo){}
#override
Widget build(BuildContext context) {
....
FutureBuilder<Response>(
future: _futureFun,
....
}
And to refresh the data again, just call the function fetchFotosInformesIncidenciasTodos(widget.informeActual.codigo) again and there is not need to setState.

How to wrap navigation with blocprovider.value which inside a bloc listener

BlocConsumer<VehicleCubit, VehicleState>(
listener: (context, state) {
if (state is VehicleLoaded) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) {
final registrationNumber =
controller.text.trim().toUpperCase();
return BlocProvider.value(
value: BlocProvider.of<VehicleCubit>(context),
child: VehicleDetailsScreen(
registrationNumber: registrationNumber,
),
);
},
),
);
}
},
// TODO: Fix routing
builder: (context, state) {
if (state is VehicleLoading) {
return Center(
child: CircularProgressIndicator(
valueColor:
new AlwaysStoppedAnimation<Color>(brandColor),
),
);
}
if (state is VehicleNotLoaded) {
toast('Unable to fetch vehicle, retry..');
}
return ElevatedButton(
onPressed: () {
FocusScope.of(context).unfocus();
if (controller.text.trim().isEmpty) return;
final registrationNumber =
controller.text.trim().toUpperCase();
BlocProvider.of<VehicleCubit>(context)
.fetchVehicle(registrationNumber, context);
},
child: Text(
'Proceed',
style: TextStyle(fontSize: 16),
),
style: ButtonStyle(
shape: MaterialStateProperty.resolveWith(
(states) => RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
)),
backgroundColor: MaterialStateProperty.resolveWith(
(states) => Color(0xFFE07A72)),
),
);
},
)
What is wrong here? I was trying to use navigation and pass then current cubit to its children but getting errors
Here is the error:
======== Exception caught by widgets library =======================================================
The following ProviderNotFoundException was thrown building Container(bg: MaterialColor(primary value: Color(0xff2196f3)), constraints: BoxConstraints(0.0<=w<=Infinity, h=100.0)):
Error: Could not find the correct Provider<BlocBase<dynamic>> above this BlocBuilder<BlocBase<dynamic>, dynamic> Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- You added a new provider in your `main.dart` and performed a hot-reload.
To fix, perform a hot-restart.
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that BlocBuilder<BlocBase<dynamic>, dynamic> is under your MultiProvider/Provider<BlocBase<dynamic>>.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
```
consider using `builder` like so:
```
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>()),
}
),
}
```
If none of these solutions work, consider asking for help on StackOverflow:
https://stackoverflow.com/questions/tagged/flutter
The relevant error-causing widget was:
Container file:///D:/Development/FlutterDevelopment/xpcover/lib/presentation/vehicle_insurance/vehicle_details_screen.dart:38:13
```
When the exception was thrown, this was the stack:
#0 Provider._inheritedElementOf (package:provider/src/provider.dart:332:7)
#1 Provider.of (package:provider/src/provider.dart:284:30)
#2 ReadContext.read (package:provider/src/provider.dart:610:21)
#3 _BlocBuilderBaseState.initState (package:flutter_bloc/src/bloc_builder.dart:130:36)
#4 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4632:57)
```
==================================================================================================

Showing snackbar from alert dialog

I'm at a loss with this one. So I know that to show a snack bar, you have to have access to a build context whose ancestor is a scaffold. To solve this I usually just make a separate widget within the scaffold within which a new build context can be called. However, I can't seem to get this to work when I use an alert dialog.
The 'child' widget i've made under the scaffold looks like this:
class DeleteButton extends StatelessWidget {
DeleteButton({#required this.vm, #required this.popCallback});
final AddJobVM vm;
final Function popCallback;
#override
Widget build(BuildContext context) {
final continueCallBack = () async {
print("deleting ${vm.jobName}");
ToasterBundle toast;
toast = await vm.deleteJob();
print(toast.success);
Scaffold.of(context).showSnackBar(generateSnackBar(toast));
await Future.delayed(
Duration(seconds: 2),
);
if (toast.success) {
popCallback();
}
};
return Padding(
padding: EdgeInsets.only(right: kStandardPadding),
child: GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) {
return AlertDialogueBlurredBG(
title: 'Delete Job',
content: 'Are you sure you want to delete this job?',
continueCallBack: continueCallBack,
);
});
},
child: Icon(
Icons.delete_outline,
color: kColorWhite,
size: 28,
),
),
);
}
}
But I'm getting an error when I call the 'continueCallBack':
[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
#0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:3781:9)
#1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:3795:6)
#2 Element.findAncestorStateOfType (package:flutter/src/widgets/framework.dart:3914:12)
#3 Scaffold.of (package:flutter/src/material/scaffold.dart:1453:42)
#4 DeleteButton.build.<anonymous closure> (package:upworkv2/screens/jobs/add_edit_job_screen.dart:615:16)
<asynchronous suspension>
#5 DeleteButton.build.<anonymous closure> (package:upworkv2/screens/jobs/add_edit_job_scree<…>
I would have thought that using a call back which references the build context outside of the alert dialog would have worked but no dice. Any ideas on where I'm going wrong here?
Builder Widget will help in this case, just see How I use & implement it,
body: Builder(
builder: (BuildContext innerContext) {
return RaisedButton(
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Are you sure?'),
content: Text('Do you want to go to background?'),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('NO')),
FlatButton(
onPressed: () {
Scaffold.of(innerContext).showSnackBar(SnackBar(
content: Text('Added added into cart'),
duration: Duration(seconds: 2),
action:
SnackBarAction(label: 'UNDO', onPressed: () {}),
));
},
child: Text('YES'))
],
),
);
},
);
},
),
This exception happens because you are using the context of the widget that instantiated Scaffold. Not the context of a child of Scaffold.
Output:

CheckboxListTile has null value using Bloc and Stream on Flutter

I am creating register form on my flutter app (version 1.17.4). I am using CheckboxListTile in order to user accept the terms. This widget is validated by bloc and stream
CheckboxListTile
Widget _createAcceptConditions(LoginBloc bloc) {
return StreamBuilder(
stream: bloc.getAcceptCondition,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Container(
child: Container(
padding: EdgeInsets.only(left: 5, top: 10),
child: CheckboxListTile(
title: Text("I accept the terms"),
value: bloc.acceptCondition,
activeColor: Colors.deepPurple,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
bloc.setAcceptCondition(value);
})),
);
},
);
}
LoginBloc class
final _acceptCondition = BehaviorSubject<bool>();
Stream<bool> get getAcceptCondition =>
_acceptCondition.stream.transform(validAcceptCondition);
//Setter
Function(bool) get setAcceptCondition => _acceptCondition.sink.add;
//Getter
bool get acceptCondition => _acceptCondition.value;
This is the validator
final validAcceptCondition =
StreamTransformer<bool, bool>.fromHandlers(handleData: (accept, sink) {
accept ? sink.add(accept) : sink.addError("You must accept the conditions");
});
When I restart the app an try to register I got
════════ Exception caught by widgets library ═══════════════════════════════════
The following assertion was thrown building StreamBuilder<bool>(dirty, state: _StreamBuilderBaseState<bool, AsyncSnapshot<bool>>#8f6de):
'package:flutter/src/material/checkbox_list_tile.dart': Failed assertion: line 269 pos 15: 'value != null': is not true.
The relevant error-causing widget was
StreamBuilder<bool>
package:neighbour_mobile/…/pages/register_page.dart:205
When the exception was thrown, this was the stack
#2 new CheckboxListTile
package:flutter/…/material/checkbox_list_tile.dart:269
#3 RegisterPage._createAcceptConditions.<anonymous closure>
package:neighbour_mobile/…/pages/register_page.dart:211
#4 StreamBuilder.build
package:flutter/…/widgets/async.dart:509
#5 _StreamBuilderBaseState.build
package:flutter/…/widgets/async.dart:127
#6 StatefulElement.build
package:flutter/…/widgets/framework.dart:4619
...
It seem the bloc is waiting for any user acction in order to put true or false into CheckboxListTile, how ever the default value is null
The value in the CheckBox cannot be null, and when you create a BehaviorSubject or a Stream they doesn't have any data. So you can work with the snapshot value and defining a initialData property in you StreamBuilder to initialize a default value when the Stream is created, try the next:
Widget _createAcceptConditions(LoginBloc bloc) {
return StreamBuilder(
stream: bloc.getAcceptCondition,
// Add a initialData
initialData: false,
builder: (BuildContext context, AsyncSnapshot snapshot) {
// In this point you can validate the snapshot to show the error you are getting
/**if(snapshot.hasError){
// Do or show something, for example a Snackbar
}*/
return Container(
child: Container(
padding: EdgeInsets.only(left: 5, top: 10),
child: CheckboxListTile(
title: Text("I accept the terms"),
// You don't need to use the acceptCondition in this section, because you have the value in the snapshot
// value: bloc.acceptCondition,
// In this part the first time the snapshot will be false
value: snapshot.hasData && snapshot.data ? true : false,
activeColor: Colors.deepPurple,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
bloc.setAcceptCondition(value);
},
),
),
);
},
);
}
Hope it helps.