Flutter app crash after converting Provider 3 to 4 - flutter

I tried to upgrade my Flutter app to use Provider 4.0.1 today and the following code crashed on assigning a value to null.
Here is the code I am attempting to convert. I only changed SingleChildCloneableWidget to SingleChildStatelessWidget which compiled OK.
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
List<SingleChildStatelessWidget> providers = [
...independentServices,
...dependentServices,
...uiConsumableProviders
];
List<SingleChildStatelessWidget> independentServices = [
Provider.value(value: Api()),
Provider.value(value: Tbl()),
Provider.value(value: Bill()),
Provider.value(value: Sale()),
Provider.value(value: Category()),
Provider.value(value: Menu()),
];
List<SingleChildStatelessWidget> dependentServices = [
ProxyProvider<Api, AuthenticationService>(
update: (context, api, authenticationService) => AuthenticationService(api: api),
),
];
List<SingleChildStatelessWidget> uiConsumableProviders = [
StreamProvider<User>(
create: (context) => Provider.of<AuthenticationService>(context, listen: false).user,
),
lazy: false
];
I implemented it like this:
StreamController<User> _userController = StreamController<User>();
Stream<User> get user => _userController.stream;
The crash occurred at this line:
Future<void> _setFixedLanguageStrings(BuildContext context) async {
User _user = Provider.of<User>(context);
_user.homeString = await translate(context, 'Home');
The getter 'language' was called on null. Receiver: null
This was working fine with Provider 3.0.3 but obviously I need to do more.
My original code came from this tutorial.
edit: I fixed that problem by adding lazy: false in the stream provider create method but then another error later in this code.
Future<String> translate(BuildContext context, _term) async {
final String _languageCode = Provider.of<User>(context).language;
which produced this error:
Exception has occurred.
_AssertionError ('package:provider/src/provider.dart': Failed assertion: line 213 pos 7: 'context.owner.debugBuilding || listen ==
false || _debugIsInInheritedProviderUpdate': 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(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. )
I added listen: false to the line above which seems to have fixed that problem, however the next provider I attempted to use produced this error:
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(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. 'package:provider/src/provider.dart': Failed
assertion: line 213 pos 7: 'context.owner.debugBuilding || listen ==
false || _debugIsInInheritedProviderUpdate'
Should I now go to every instance where I call a provider and add listen: false? I need somebody to explain what has changed and why as I am fairly new at Flutter and the docs are sparse for Provider. There are many times where I call Provider in my code and this last error did not return a code location.
Is listen: false now always required when it wasn't before or have I missed something else? I am starting to add listen: false to every call to instantiate a Provider variable and it appears to be working but is this the correct approach? Should I just add listen: false to every call to Provider.of and call it a day?
edit: The error arises whenever the provider is called from outside the visible part of the widget tree. This distinction is important.

I have the same "problem", if i add listen: false everywhere i call Provider the problem is gone but i dont know if thats the right solution...?

On Event Handlers like onPressed, OnTap, onLongPressed etc. we must use
Provider.of<T>(context,listen:false)
reason being that they will not listen for any update changes, instead are responsible for making changes.
whereas widgets like Text etc. are responsible for displaying...hence need to be updated on every change made....therefore use
Provider.of<T>(context,listen:true) //by default is listen:true

listen:true being the default is logical.
It's not specifying inside an event handler that is not logical.listen: false
Also, 4.1.0 will somehow have a shorter alternative to Provider.of:
context.read<T>() // Provider.of<T>(context, listen: false)
context.watch<T>() // Provider.of<T>(context)

listen : false called when the data wouldn't updating any thing in the UI, and should be used, like removing all cards in a widget when button clicked.
For more info's, read this go_to_link

In my case I was getting the following error:-
I/flutter ( 7206): Tried to listen to a value exposed with provider, from outside of the widget tree.
I/flutter ( 7206):
I/flutter ( 7206): This is likely caused by an event handler (like a button's onPressed) that called
I/flutter ( 7206): Provider.of without passing `listen: false`.
I/flutter ( 7206):
I/flutter ( 7206): To fix, write:
I/flutter ( 7206): Provider.of<AstroDetailsProvider>(context, listen: false);
I/flutter ( 7206):
I/flutter ( 7206): It is unsupported because may pointlessly rebuild the widget associated to the
I/flutter ( 7206): event handler, when the widget tree doesn't care about the value.
As you can see that solution is present in the error message itself.
Hence, we are not supposed to use provider with default (listen:true) inside event handlers.
Alternatively,
context.read<T>() is same as Provider.of<T>(context, listen: false) And
context.watch<T>() is same as Provider.of<T>(context)```
Ref :- https://github.com/rrousselGit/provider/issues/313

If you are using provider outside the build without listen: false
then, of course, you can't listen to changes as it didn't build the widgets again for changes. this doesn't come as a default value because the provider is not supposed to use outside the build as it used as a state management tool and to inject dependencies.
But however, if you are using outside the build you have to use listen: false

If we use,
Provider.of<T>(context, listen: true).method();
inside build method and if we have
notifyListeners();
in that method(), then it causes infinite recursion , as on each
notifyListeners();
invoke ,all provider calls with listen : true will execute , i.e build method is re executed, that means
method(),
is called again and again and result in error

Related

Why my toast cannot show when i try use connect activity flutter at initState?

i try to show toast event my connection is disabled, i just create toast at initState, so i think i can't build widget in initState, that's true?
Why don't you make a global bool variable that is set to false, and in case of connectionStateChange, use setState() to update bool value to true. Check the bool value in initState() to return a Toast
You can build widgets inside initState like ScaffoldMessenger which extends StatefulWidget.One of the solutions can be the below code:
if(result == ConnectivityResult.none){
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("No internet connection"),
));
}

showModalBottomSheet and Unhandled Exception: setState() called after dispose() on parent widget

Context:
I have a modal bottom sheet that pops up, upon selection of Camera/Gallery acquires/selects an image XFile and returns it for processing (uploading) done with the help of image_picker.
This is done with a sample line:
ListTile(
onTap: () {
// definition: Future<XFile?> showCamera(IdPhotoOrientation orientation);
showCamera(orientation).then((value) => Navigator.of(context).pop<XFile?>(value));
},
...
),
Picking an image with showModalBottomSheet is done by returning the selected XFile and processing it on a chained function _handleFile(XFile, enum):
return showModalBottomSheet<XFile?>(
context: context,
builder: (context) {
return SingleChildScrollView(
child: ListBody(
children: [
...
ListTile(
onTap: () {
showCamera(orientation).then((value) => Navigator.of(context).pop<XFile?>(value));
},
leading: Icon(Icons.camera),
title: Text("From Camera"),
),
...
],
),
);
},
).then((value) => _handleFile(value, orientation));
What is the problem:
While processing file in _handle(XFile?, int), I need to update the state of the app to show progress bar updates, circular indicators, uploading status, etc.
Future<void> _handleFile(XFile? xfile, int orientation) {
if (xfile == null) {
return Future.value();
}
// store locally with Uploading Status
var imageService = locator<ImageService>();
setState(() { <-------- offending line (ui_partner_registration_id_photos.dart:103:5)
remoteImageStatus[xfile] = UploadStatus.Uploading;
images[orientation] = xfile;
});
// Upload and update result / error
return imageService.uploadIDPhoto(File(xfile.path), orientation).then((value) {
setState(() {
idPhotos[orientation] = value;
remoteImageStatus[xfile] = UploadStatus.Done;
});
print("Uploaded [${xfile.path}]");
}).onError((error, stackTrace) {
print("Error uploading image");
print(stackTrace);
setState(() {
remoteImageStatus[xfile] = UploadStatus.Error;
});
});
}
Why is this a problem?
setState() cannot be called on a stateful widget that is no longer visible/active/in-focus which is now the case for the showModalBottomSheet. That being said, after calling Navigator.pop() this should no longer be the case as the parent stateful widget is now in focus, this is causing my confusion.
(temporary) Solution
A temporary solution (which does not give exactly the desired result) is to add a mounted check as described here with an example here:
if (mounted) {
setState((){
// perform actions
})
}
StackTrace:
[VERBOSE-2:ui_dart_state.cc(199)] Unhandled Exception: setState() called after dispose(): _RegisterIDPhotosState#b75f9(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/widgets/framework.dart:1052:9)
#1 State.setState (package:flutter/src/widgets/framework.dart:1087:6)
#2 _RegisterIDPhotosState._handleFile (my-awesome-app/viewcontrollers/register/partner/ui_partner_registration_id_photos.dart:103:5)
#3 _RegisterIDPhotosState.pickImageWithModalPopup.<anonymous closure> (package:my-awesome-app/viewcontrollers/register/partner/ui_partner_registration_id_photos.dart:188:23)
#4 _rootRunUnary (dart:async/zone.dart:1362:47)
#5 _CustomZone.runUnary (dart:async/zone.dart:1265:19)
<asynchronous suspension>
Question:
After selecting a file and starting the upload process, how can I call setState() as in the example of _handleFile(XFile?, int) above?
Refactor that logic to a ChangeNotifier or ValueNotifier higher up in the widget tree and make your Widgets use it to share state between them see the official docs for a more in thorough description.
The setState approach won't work because you are handling 2 different widgets there. You state:
"That being said, after calling Navigator.pop() this should no longer be the case as the parent stateful widget is now in focus, this is causing my confusion."
Whats causing your confusion is that setState is not a global callback which is executed in the currently focused Sateful Widget, setState is nothing more than executing your callback and calling markNeedsBuild for the specific widget in which the setState call was made, which in your case is no longer mounted.
That being said the docs I pointed you to is a recommended way of sharing state in a Flutter app.

Flutter + Bloc 6.0.6. BlocBuilder's "builder" callback isn't provided with the same state as the "buildWhen" callback

I'm building a tic-tak-toe app and I decided to learn BLoC for Flutter along. I hava a problem with the BlocBuilder widget.
As I think about it. Every time Cubit/Bloc that the bloc builder widget listens to emits new state the bloc builder goes through this routine:
Call buildWhen callback passing previous state as the previous parameter and the newly emitted state as the current parameter.
If the buildWhen callback returned true then rebuild.
During rebuilding call the builder callback passing given context as context parameter and the newly emitted state as state parameter. This callback returns the widget that we return.
So the conclusion is that the current parameter of the buildWhen call is always equal to the state parameter of the builder call. But in practice it's different:
BlocBuilder<GameCubit, GameState>(
buildWhen: (previous, current) => current is SetSlotSignGameState && (current as SetSlotSignGameState).slotPosition == widget.pos,
builder: (context, state) {
var sign = (state as SetSlotSignGameState).sign;
// Widget creation goes here...
},
);
In the builder callback, it throws:
The following _CastError was thrown building BlocBuilder<GameCubit, GameState>(dirty, state:
_BlocBuilderBaseState<GameCubit, GameState>#dc100):
type 'GameState' is not a subtype of type 'SetSlotSignGameState' in type cast
The relevant error-causing widget was:
BlocBuilder<GameCubit, GameState>
The method where I emit the states that is in the GameCubit class:
// [pos] is the position of the slot clicked
void setSlotSign(Vec2<int> pos) {
// Some code
emit(SetSlotSignGameState(/* Parameter representing the sign that is being placed in the slot*/, pos));
// Some code
emit(TurnChangeGameState());
}
Briefly about types of states. SetSlotSignGameState is emitted when a user taps on a slot in the tic-tac-toe grid and the slot is empty. So this state means that we need to change sign in some slot. TurnChangeGameState is emitted when we need to give the turn to the next player.
Temporary solution. For now I fixed it by saving the state from buildWhen callback in a private field of the widget's state and then using it from the builder. BlocListener also has this problem but there I can just move the check from listenWhen callback into listen callback. The disadvantage of this solution is that it's very inelegant and inconvenient.
buildWhen is bypassed (not even called) on initial state OR when Flutter requests a rebuild.
I have created a small "test" to emphasize that:
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(BlocTestApp());
}
class BlocTestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider<TestCubit>(
// Create the TestCubit and call test() event right away
create: (context) => TestCubit()..test(),
child: BlocBuilder<TestCubit, String>(
buildWhen: (previous, current) {
print("Call buildWhen(previous: $previous, current: $current)");
return false;
},
builder: (context, state) {
print("Build $state");
return Text(state);
},
),
),
);
}
}
class TestCubit extends Cubit<String> {
TestCubit() : super("Initial State");
void test() {
Future.delayed(Duration(seconds: 2), () {
emit("Test State");
});
}
}
OUTPUT:
I/flutter (13854): Build Initial State
I/flutter (13854): Call buildWhen(previous: Initial State, current: Test State)
As can be seen from output the initial state is built right away without calling buildWhen. Only when the state changes buildWhen is examined.
Other References
This behavior is also outlined here by the creator of Flutter Bloc library (#felangel):
This is expected behavior because there are two reasons for a
BlocBuilder to rebuild:
The bloc state changed
Flutter marked the widget as needing to be rebuilt.
buildWhen will prevent builds triggered by 1 but not by 2. In
this case, when the language changes, the whole widget tree is likely
being rebuilt which is why the BlocBuilder is rebuilt despite
buildWhen.
Possible solution
In your situation, based on the little code you revealed, is better to store the entire Tic-Tac-Toe configuration in the state and use BLOC events to alter it. In this way you do not need that buildWhen condition.
OR make the check inside the builder function if the logic let you do that (this is the most common used solutions with BLOC).
To respond to you question (if not clear so far :D): Sadly, you can not rely on buildWhen to filter the state types sent to builder function.
Could you please check if SetSlotSignGameState extends the abstract class GameState

Flutter Navigation from Firestore Flag

Diagram of what I want to do]1
I want to navigate to Screen2 from screen1 when the value of screen2_flag changes to true (on firebase cloud firestore ) ,
I have screen 1 as shown in the diagram ,which is working perfectly fine until the value of screen2_flag is false , when I change the value of flag from false to true .
void initState() {
super.initState();
DocumentReference reference =
Firestore.instance.collection('myColection').document('myDoc');
reference.snapshots().listen((querySnapshot) {
print('got sanpshot' + querySnapshot.data['screen2_flag'].toString());
if (querySnapshot.data['screen2_flag'].toString() == 'true') {
Navigator.pushNamed(context, screen2.id);
}
});
Widget build(BuildContext context) {
// my code
...
}
}
I am able to see screen2 on my emulator. But Getting below error on Console
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: setState() callback argument returned a Future.
E/flutter (24551): The setState() method on _WaitingRoomState#25dfa was called with a closure or method that returned a Future. Maybe it is marked as "async".
E/flutter (24551): Instead of performing asynchronous work inside a call to setState(), first execute the work (without updating the widget state), and then synchronously update the state inside a call to setState().
E/flutter (24551): #0 State.setState. (package:flutter/src/widgets/framework.dart:1151:9)
E/flutter (24551): #1 State.setState (package:flutter/src/widgets/framework.dart:1167:6)
The error told you the problem:
setState() callback argument returned a Future
The setState() method on _WaitingRoomState#25dfa was called with a
closure or method that returned a Future
and told you also the solution:
Instead of performing asynchronous work inside a call to setState(),
first execute the work (without updating the widget state), and then
synchronously update the state inside a call to setState()
So you do the async call,await for it until you get the flag value(as I suppose because you didn't show that part of code), then call setState() with the new values updated.

Flutter remove dialogs in sequence

I am trying to remove dialogs in a specific sequence but I am getting an following error:
E/flutter (14457): [ERROR:flutter/shell/common/shell.cc(188)] Dart Error: Unhandled exception:
E/flutter (14457): Looking up a deactivated widget's ancestor is unsafe.
E/flutter (14457): 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 inheritFromWidgetOfExactType() in the widget's didChangeDependencies() method.
Here the sequence:
List item
Open Confirm dialog 1
Close (onPressed) dialog 1
Open Loading dialog 2.
Close (Navigator.of(context).pop())
Open dialog 3 with success message.
Both Dialog 1 and 2 cloese with Navigator.of(context).pop().
How can I fix this?
When you use the function Navigator.of(context).pop(),don't use the context from previous dialog,try to use the page's context。
The reason about Looking up a deactivated widget's ancestor is unsafe is previous dialog is closed,but you use that dialog's context. You can see the source code:
static NavigatorState of(
BuildContext context, {
bool rootNavigator = false,
bool nullOk = false,
}) {
final NavigatorState navigator = rootNavigator
? context.rootAncestorStateOfType(const TypeMatcher<NavigatorState>())
: context.ancestorStateOfType(const TypeMatcher<NavigatorState>());// here check the ancestor state,and throw the error
assert(() {
if (navigator == null && !nullOk) {
throw FlutterError(
'Navigator operation requested with a context that does not include a Navigator.\n'
'The context used to push or pop routes from the Navigator must be that of a '
'widget that is a descendant of a Navigator widget.'
);
}
return true;
}());
return navigator;
}