Provider : Looking up a deactivated widget's ancestor is unsafe - flutter

Trying to get the value from provider, it works OK, but I get the error below. What i am doing wrong here?
#override
Widget build(BuildContext context) {
String route = Provider.of<User>(context) == null ? Router.LOGIN : Router.HOME;
Future.delayed(const Duration(seconds: 2), () => Navigator.pushReplacementNamed(context, route));
return Scaffold(........
error
E/flutter (23058): [ERROR:flutter/shell/common/shell.cc(209)] Dart Error: Unhandled exception:
E/flutter (23058): Looking up a deactivated widget's ancestor is unsafe.
E/flutter (23058): At this point the state of the widget's element tree is no longer stable.
E/flutter (23058): 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 problem is not being able to await the navigator call. If you need to navigate to the correct page on build, then create an async method with no return type, then call the method in initState.
_checkUserExistsAndNavigate() async {
String route = Provider.of<User>(context) == null ? Router.login : Router.home;
await Future.delayed(seconds: 2), () => Navigator.pushReplacementNamed(context, route);
}
#initState(() {
_checkUserExistsAndNavigate();
super.initState();
}

Related

Unhandled Exception: setState() called after dispose() with Firebase Realtime Database chat feature

I am receiving this error:
[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: setState() called after dispose(): _EventChatScreenState#7c8b5(lifecycle state: defunct, not mounted)
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
#0 State.setState.<anonymous closure> (package:flutter/src/wid<…>
However, the only place I am calling setState is in the initState:
#override
void initState() {
super.initState();
dbRef = dbInstance.ref("/events/");
var query = dbRef!.child(widget.event.event.eventId);
FirebaseList(
query: query,
onChildAdded: (index, snapshot) {
Map<dynamic, dynamic> childMap = snapshot.value as dynamic;
ChatMessage newChatMessage = ChatMessage(
chatMessageId: snapshot.key.toString(),
userId: childMap["userId"],
displayName: childMap["displayName"],
message: childMap["message"],
datetime: childMap["datetime"],
);
setState(() {
chatMessages.add(newChatMessage);
});
},
);
_messageFieldController = TextEditingController();
}
#override
void dispose() {
super.dispose();
_messageFieldController.dispose();
}
I'm not really sure why this is happening, but I included the dispose method since it the error references it.
Worth noting that I am doing this to make the screen scroll to the bottom of the chat messages which are display using a ListView.builder
void scrollToBottom() {
SchedulerBinding.instance.addPostFrameCallback((_) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
});
}
#override
Widget build(BuildContext context) {
final user = ref.watch(userProvider);
if (chatMessages.isNotEmpty) {
scrollToBottom();
}
If I remove the above code the issue seems to go away
instead of this
#override
void dispose() {
super.dispose();
_messageFieldController.dispose();
}
try this
#override
void dispose() {
_messageFieldController.dispose();
super.dispose();
}

Method 'dispose' was called on null. Receiver: null Tried calling: dispose()) - Push.Navigator

I added in last page of my app a button to move back to the previous page - expecting to refresh the complete app or page.
context,
MaterialPageRoute(builder: (context) => SplashPage()), // this mymainpage is your page to refresh
(Route<dynamic> route) => false,
);
I receive the below error message when pressed.
═══════ Exception caught by widgets library ═══════════════════════════════════
The method 'dispose' was called on null.
Receiver: null
Tried calling: dispose()
════════════════════════════════════════════════════════════════════════════════
What do I need to place in the Dispose widget to avoid calling null.
DateFormat format = DateFormat('dd/MM');
List<bool> flips = [false, false, false, false];
List tarots = [];
List unLockCard = [];
BannerAd _bannerAd;
#override
void dispose() {
_bannerAd.dispose();
super.dispose();
}
I looked into this solution Dispose widget when navigating to new route but can not apply.
There may be a situation that the banner has not loaded so tying to dispose it when its null will throw an error. You can add a condition check before you dispose
#override
void dispose() {
if(_bannerAd != null)
{
_bannerAd.dispose();
}
super.dispose();
}
if you are using null safety you can declare banner ad like
BannerAd? _bannerAd;
and dispose it like
_bannerAd?.dispose();

Can not get data from a network request in a method that uses Scaffold.of(context)

I have two class methods inside my Stateful widget. As you can see one method is called inside the other one.
1st class method
Future<void> _getExchangeHousesList() async {
const url = ApiEndpoints.getExchangeHouses;
Map<String, String> requestBody = {
'country_code': _userCountryCode,
'lat': _userLocation.latitude.toString(),
'long': _userLocation.longitude.toString(),
'currency_code': 'USD',
'entered_amount': '100',
};
final response = await http.post(
Uri.parse(url),
body: requestBody,
);
if (response.statusCode == 200) {
final body = json.decode(response.body);
final exchangeHousesList = body['data']['exchangehouse'] as List;
for (var exchangeHouse in exchangeHousesList) {
var exchangeHouseModal = ExchangeHouse.fromJson(exchangeHouse);
_housesList.add(exchangeHouseModal);
}
}
}
2nd class method
void _getBestExchangeHouses(context, screenHeight, screenWidth) async {
setState(() => _showFindHouseModal = false);
// Prepare data for the network request
final searchForPriceData = SearchForPrices(
countryCode: _userCountryCode,
currencyCode: _selectedCurrency.currencyCode,
enteredAmount: _enteredAmount,
userLatitude: _userLocation.latitude,
userLongitude: _userLocation.longitude,
);
// Send the network request
await _getExchangeHousesList(); // <----------------- First class method is called in here
// Below code is responsible for rendering the bottom sheet once the data is fetched.
const double sheetRadius = 35;
await Scaffold.of(context)
.showBottomSheet(
(context) => ExchangeHousesSheet(
height: screenHeight * 0.6,
width: screenWidth,
borderRadius: sheetRadius,
housesList: _housesList,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(sheetRadius),
),
)
.closed;
// Below code does clean up after the bottom sheet is closed
_enteredAmountController.text = ''; // Clears the entered amount in the find house modal after the sheet is closed
setState(() => _showFindHouseModal = true);
}
As I have pointed inside the second class method, I am calling the first class method in that second class method.
HOWEVER calling that first class method inside the second class method gives me the following error,
E/flutter (22176): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
E/flutter (22176): At this point the state of the widget's element tree is no longer stable.
E/flutter (22176): 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.
E/flutter (22176): #0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:4032:9)
E/flutter (22176): #1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:4046:6)
E/flutter (22176): #2 Element.findAncestorStateOfType (package:flutter/src/widgets/framework.dart:4093:12)
E/flutter (22176): #3 Scaffold.of (package:flutter/src/material/scaffold.dart:2144:43)
E/flutter (22176): #4 _AppHomeScreenState._getBestExchangeHouses (package:lanka_remittance/screens/main_screens/app_home_screen.dart:179:20)
E/flutter (22176): <asynchronous suspension>
E/flutter (22176):
I know that this is related to me calling Scaffold.of(context) inside the second class method. I can not figure out how to solve this though. (I have also come across posts where people mention to pass a scaffold key. I tried that but it did not work. I think that I might have passed the scaffold key wrong)
Can you please help me fix this?

Flutter: Provider shows Exception but app run fine while using default listen:true

I'm Using provider in initState() to call the api but if I use listen:false then it does not update UI and it always shows me loader but if I use listen:true then app works fine but in the terminal it shows me exception and tells me write listen:false.
My UI,
class ChopperNewsCard extends StatefulWidget {
#override
_ChopperNewsCardState createState() => _ChopperNewsCardState();
}
class _ChopperNewsCardState extends State<ChopperNewsCard> {
ScrollController scrollController = ScrollController();
int currentPage = 5;
ChopperApiStore _apiStore = ChopperApiStore();
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_apiStore = Provider.of<ChopperApiStore>(context,);//<--- here it tells me to write listen:false
});
_apiStore.getResponse(currentPage);
scrollController.addListener(() {
if (scrollController.position.pixels ==
scrollController.position.maxScrollExtent) {
if (currentPage < 20) {
currentPage = currentPage + 5;
_apiStore.getResponse(currentPage);
}
}
});
}
#override
void dispose() {
scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return Observer(builder: (context) {
return Container(
height: height * 0.37,
width: double.infinity,
child: _apiStore.res.articles == null
? CircularProgressIndicator()
: ListView.builder(...),
);
});
}
}
api calling class,
class ChopperApiStore extends _ChopperApiStore with _$ChopperApiStore{}
abstract class _ChopperApiStore with Store{
ApiCall apiCall = ApiCall();
#observable
ChopperNews res = ChopperNews();
#action
Future<void> getResponse(int page) async {
var data = await apiCall.getNews(page);
res = data;
}
}
the error I'm getting,
======== Exception caught by scheduler library =====================================================
The following assertion was thrown during a scheduler callback:
Tried to listen to a value exposed with provider, from outside of the widget tree.
This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.
To fix, write:
Provider.of<ChopperApiStore>(context, listen: false);
It is unsupported because may pointlessly rebuild the widget associated to the
event handler, when the widget tree doesn't care about the value.
The context used was: ChopperNewsCard(dependencies: [MediaQuery], state: _ChopperNewsCardState#8f6cd)
'package:provider/src/provider.dart':
Failed assertion: line 262 pos 7: 'context.owner.debugBuilding ||
listen == false ||
debugIsInInheritedProviderUpdate'
When the exception was thrown, this was the stack:
#2 Provider.of (package:provider/src/provider.dart:262:7)
#3 _ChopperNewsCardState.initState.<anonymous closure> (package:fruitley/week-5/bonus/chopper/widgets/chopper_news_card.dart:32:28)
#4 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1117:15)
#5 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1063:9)
#6 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:971:5)
...
I think if you want to use listen:true to have the build method called you are suppose to override didChangeDependencies rather then initState Checkout this article it might help https://medium.com/swlh/flutter-provider-and-didchangedependencies-15678f502262
ok I'm dumb. I didn't even need to use addPostFrameCallback.
I just removed it and if I want to use provider outside of widget tree that I must use listen:false as it was showing in the exception so now everything makes sense.

Flutter Provider is returning null in initState()

I am getting List of DropdownMenuItem from firebase with stream using provider if I use provider in build it doesn't return null works great but in initstate even if I am setting the listen value false returns null.
Here is the code:
List<DropdownMenuItem<category.List>> dropdownmenuoptions;
DropdownMenuItem<category.List> dropdownMenuItem;
String dropDownValue;
#override
void initState() {
dropdownmenuoptions = Provider.of<List<DropdownMenuItem<category.List>>>(
context,
listen: false);
dropdownMenuItem = dropdownmenuoptions.first;
dropDownValue = dropdownMenuItem.value.name;
super.initState();
}
Here is the error message:
The following NoSuchMethodError was thrown building Builder:
The getter 'first' was called on null.
Receiver: null
Tried calling: first
The relevant error-causing widget was
MaterialApp
lib/main.dart:37
When the exception was thrown, this was the stack
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
#1 _HomeScreenState.initState
package:tobetter/…/home/home_screen.dart:73
#2 StatefulElement._firstBuild
package:flutter/…/widgets/framework.dart:4833
#3 ComponentElement.mount
package:flutter/…/widgets/framework.dart:4649
... Normal element mounting (24 frames)
The direct context inside initState can't be used for everything and specialty with Provider . so, as a solution use didChangeDependencies instead , example:
#override
void didChangeDependencies() {
super.didChangeDependencies();
dropdownmenuoptions = Provider.of<List<DropdownMenuItem<category.List>>>(
context,
listen: false);
dropdownMenuItem = dropdownmenuoptions.first;
dropDownValue = dropdownMenuItem.value.name;
}
if you really need it inside your initState you can use it inside addPostFrameCallback
#override
void initState(){
...
SchedulerBinding.instance.addPostFrameCallback((_) {
dropdownmenuoptions = Provider.of<List<DropdownMenuItem<category.List>>>(
context,
listen: false);
dropdownMenuItem = dropdownmenuoptions.first;
dropDownValue = dropdownMenuItem.value.name;
}
});