Pass callback Function as Parameter to Widget while keeping Lint Rules "argument_type_not_assignable" - flutter

In my widget I pass a function callback as a parameter to the widget:
onTap: onTapRestaurantItemCallback
Out in the screen widget, which contains the widget above, I then execute the function callback:
onTapRestaurantItemCallback: () { // Handling the callback method }
I am using Lint for Dart/Flutter to check my code:
https://pub.dev/packages/lint
The linter is not happy with my function parameter and gives me an error, I am violating the rule:
argument_type_not_assignable
"The argument type 'Function' can't be assigned to the parameter type 'void Function()'"
https://dart.dev/tools/diagnostic-messages#argument_type_not_assignable
Apparently the problem is that the widgets "onTap" expects void, whereas the callback returns a Future.
I can make the error go away in the widget by writing:
onTap: () => onTapRestaurantItemCallback
But then my function callbacks stop working.
Do you know how I can pass a function callback to my widget like I do and still keep the
argument_type_not_assignable
lint rule?
Below I paste a more full code on the scenario I have described above:
// WIDGET USED BY OTHER WIDGET BELOW
class RestaurantItem extends StatelessWidget {
// FUNCTION CALLBACK
final Function onTapRestaurantItemCallback;
const RestaurantItem({
// FUNCTION CALLBACK
#required this.onTapRestaurantItemCallback,
});
#override
Widget build(BuildContext context) {
return GestureDetector(
// FUNCTION CALLBACK
onTap: onTapRestaurantItemCallback,
// CONTAINING SCREEN WIDGET - USING WIDGET ABOVE
// .... //
RestaurantItem(
// FUNCTION CALLBACK
onTapRestaurantItemCallback: () => { // Handling the callback method }
),

Related

flutter setstate from another function?

I have a void function called updateCardCode() which takes newCardCode as argument,
in the same class, I have a second function called showPopUp() which is an async function and returns showDialog function returns AlertDialog, also in the same class I have a build widget.
here is updateCardCode() code:
void updateCardCode(String newCardCode) {
setState(() {
cardCode1 = newCardCode;
});
}
showPopUp() calls updateCardCode() if certain conditions are met but I want setState to have a result on the Build widget which returns Scaffold. is that possible?
call the setState at the end of the showPopUp() function like
showPopUp().then(()=>setState((){}));

Anonymous function and its parameter in Dart Flutter

I'm very new to Dart. I was learning basic stuff for Flutter. And I've got some questions while working on BottomNavigationBar:
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
I have no idea why there's index as a parameter in the function. Where is it coming from?
Also, in the setState function, why is there a function and what does it mean?
I'm sorry these questions are very elementary but I couldn't find clear answers!
Thank you in advance!
onTap: (index) {} : Index use for Keep track of the index of the selected BottomNavigationBarItem.
setState() : Whenever you change the state of a State object, make the change in a function that you pass to setState.
For more info :
onTap : https://api.flutter.dev/flutter/material/BottomNavigationBar/onTap.html
setState : https://api.flutter.dev/flutter/widgets/State/setState.html
Firstly onTap is a callback function and depending on widget it can have zero o more parameters. For example in InkWell widget the onTap callback has no parameters and in BottomNavigationBar the onTap callback take index as parameter to recognize the index of items in that widget.
Secondly, as you know setState function is used for updating UI and called inside State class.
When you use StatefulWidget, every StatfulWidget has a state object like code below:
class MyWidget extends StatefulWidget { // immutable Widget
#override
_MyWidgetState createState() => _MyWidgetState();
// creating State Object of MyWidget
}
class _MyWidgetState extends State<MyWidget> { // State Object
#override
Widget build(BuildContext context) {
return Container();
}
}
Since StatefulWidget itself is immutable (cannot be modified), we use State Object to modify the UI.
We tell this State Object to update our screen's UI using a function called setState().
void setState(VoidCallback fn) {
...
}
This setState() takes a function as it's parameter.
The function called inside setState does not need have any parameter as well.
finally, you can hold ctrl key and click on which function you want and see the implementation of the function.

Flutter - run code before or after execution of Function() that was passed as a parameter

Example: a widget, that accepts a Function() function as a parameter. Now i want to execute code inside this widget, when function is called somewhere above in hierarchy, before or after it. Is this possible?
Here is the button widget
class AppButton extends StatelessWidget {
final Function()? onPressed;
const AppButton(
{Key? key,required this.onPressed}): super(key: key);
#override
Widget build(BuildContext context) {
return ElevatedButton(onPressed: onPressed);
}
Here is how I call it
AppButton(
onPressed: () {
[some code]
},
),
Now the question is: is it possible to call a code, like setState inside the AppBar, before or after [some code] gets executed on tap? Like to inject code into onPressed inside AppButton?
Yes, it's possible, You should take the Function() function as an argument and in the Widget(e.g ElevatedButton in the property onPressed: function)
and in the Constructor get the function.
so, when you calling the widget it will ask for the function as well their you will give the function like this
YouWidgetName(function: () {
// here will be your code for that particular area.
}),

Flutter ElevatedButton onPressed functions Avoid using unnecessary statements

I have a Flutter stateful widget inside a Stepper widget, and it looks like this:
The first step of the stepper widget has another widget as its content, and that widget is a stateful widget as below:
import 'package:flutter/material.dart';
class ConductorStart extends StatefulWidget {
const ConductorStart({
Key? key,
required this.continued,
}) : super(key: key);
final VoidCallback continued;
#override
ConductorStartState createState() => ConductorStartState();
}
class ConductorStartState extends State<ConductorStart> {
int _currentStep = 0;
bool _pressedButton = false;
void tapped() {
setState(() => _pressedButton = true);
}
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
if (!_pressedButton)
ElevatedButton(
onPressed: () {
tapped;
widget.continued;
},
child: const Text('Continue'),
)
else
const SizedBox.shrink(),
],
);
}
}
The ElevatedButton has two functions when called: tapped, and widget.continued. tapped should make the button disappear. and widget.continued is a function from the parent widget that is supposed to make the parent widget to continue to step 2 when clicked. Currently the two functions have a warning of Avoid using unnecessary statements, and they do not get executed properly. When I click the continue button. Nothing happens. What did I do wrong here?
I'll totally go with Pat9RB. Just posting it here as an answer for others to quickly find the relevant issue.
Adding just definitions helps for adding a callback parameter in the constructor.
For Eg.: onPressed: myOnPressed;
where, onPressed needs a VoidCallback, and myOnPressed is a VoidCallback argument.
But, you need to call the function in order to execute it from any other function body.
Either call using .call() method, like, tapped!.call(); or append with callback syntax (), like, tapped();

Flutter: setState() doesn't execute until the enclosing function returns

So I was trying this code in flutter:
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _i = 1;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MaterialButton(
child: Text('You Pressed Me $_i'),
onPressed: () {
setState(() {
_i++;
print('inside i = $_i');
});
sleep(Duration(seconds: 10));
_i++;
print('outside i = $_i');
}
),
),
),
);
}
}
Expected behavior(after running and pressing the button once): the button shows text "You Pressed Me 2",
and the variable _i then gets incremented to 3 without affecting the visual result.
Actual behavior: setState() is execued and then _i is incremented again and no visual change happens i.e. the text on screen doesn't update, and when onPressed() returns, setState() causes the widget to rebuild and the screen updates and this is what appears on screen after 10 seconds: "You Pressed Me 3".
There is this quote from the docs about setState():
The provided callback is immediately called synchronously. It must not
return a future (the callback cannot be async), since then it would be
unclear when the state was actually being set.
I understand that the call is synchronous (so it's blocking, according to this answer) so it should return first (and this already happens) and then update the screen (or schedule that for some time in the future) and then return control to the following line (the latter 2 things don't happen).
I even tried it without the sleep but same result.
so what am I missing or misunderstanding?
Event Loop
There is something called Event Loop
Event Loop process Events in order
You have two events in order
Event A => Click => by the User
0.onPressed: () {
1.setState(() {
3. i++
4. Mark as widget dirty
5. Add to the global dirty widgets list
});
6.i++
});
Event B => Vsync signal => provided by the OS
7. check dirty widgets list
8. repaint
More
Here a YouTube video from Flutter in Focus series
Isolates and Event Loops
by flutter team member Andrew Brogdon (#RedBrogdon)
Or read it here
Dart asynchronous programming: Isolates and event loops
Ref.:
setState method - State class - widgets library - Dart API
markNeedsBuild method - Element class - widgets library - Dart API
scheduleBuildFor method - BuildOwner class - widgets library - Dart API
drawFrame method - WidgetsBinding class - widgets library - Dart API
handleDrawFrame method - SchedulerBinding class - scheduler library - Dart API
buildScope method - BuildOwner class - widgets library - Dart API
dart engine loop - Google Search
Dart Programming - Loops - Tutorialspoint
optimization - What is the optimal render loop in Dart 2? - Stack Overflow
Understanding Flutter Render Engine - Stack Overflow
Technical overview - Flutter
Flutter - Dart API docs
flutter/spinning_square.dart at master · flutter/flutter
14 .dart engine - Google Search
scheduler library - Dart API
flutter/binding.dart at master · flutter/flutter
scheduler library - Dart API
frame scheduling flutter - Google Search
scheduleFrame method - SchedulerBinding class - scheduler library - Dart API
scheduler library - Dart API
packages/flutter/lib/scheduler.dart - external/github.com/flutter/flutter - Git at Google
flutter/spinning_square.dart at master · flutter/flutter
dart engine - Google Search
threading | Dart Package
isolate flutter - Google Search
If you want to run code after the widget is rebuild use:
WidgetsBinding.instance.addPostFrameCallback((_) {
//doStuffAfterNextBuild
});
The function passed to setState is run directly.
"The provided callback is immediately called synchronously"
setState only marks the widget as dirty.
"Calling setState notifies the framework that the internal state of this object has changed"
setState does not itself manipulate your state in any way.
(Quotes from https://api.flutter.dev/flutter/widgets/State/setState.html)
See for example the following:
class _MyWidgetState extends State<MyWidget> {
String s = ""
// ... Somewhere in the tree
onPressed: () {
// State is updated immediately with this call
setState(() {
this.s = "1";
// Implementation of setState calls
// _element.markNeedsBuild();
});
// State is updated again
this.s = "2";
}
}
This will result in the states s beeing "2".
Although the closure passed to setState sets s to "1", it is again modified after that in the closure of onPressed.
You can actually update the state outside of setStates closure entirely:
class _MyWidgetState extends State<MyWidget> {
String s = ""
// ... Somewhere in the tree
onPressed: () {
// State is updated immediately
this.s = "1";
setState(() {
// Implementation of setState calls
// _element.markNeedsBuild();
});
}
}
If you look at the implementation of setState you can see that it does not actually do anything with the callback besides of executing it and making sure you did not give it an asynchronous function and that the widget still exists:
#protected
void setState(VoidCallback fn) {
assert(fn != null);
assert(() {
if (_debugLifecycleState == _StateLifecycle.defunct) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() called after dispose(): $this'),
ErrorDescription(
'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.'
),
ErrorHint(
'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.'
),
ErrorHint(
'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().'
),
]);
}
if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() called in constructor: $this'),
ErrorHint(
'This happens when you call setState() on a State object for a widget that '
"hasn't been inserted into the widget tree yet. It is not necessary to call "
'setState() in the constructor, since the state is already assumed to be dirty '
'when it is initially created.'
),
]);
}
return true;
}());
final dynamic result = fn() as dynamic;
assert(() {
if (result is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() callback argument returned a Future.'),
ErrorDescription(
'The setState() method on $this was called with a closure or method that '
'returned a Future. Maybe it is marked as "async".'
),
ErrorHint(
'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().'
),
]);
}
// We ignore other types of return values so that you can do things like:
// setState(() => x = 3);
return true;
}());
_element.markNeedsBuild();
}
(Also from https://api.flutter.dev/flutter/widgets/State/setState.html)
Check out this great video for more insight into how State works in Flutter:
https://www.youtube.com/watch?v=dkyY9WCGMi0
P.S.
Just for fun you could also handle the creation of the element for the StatefulWidget your self and mark it manually.
Although you definitely should not
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage();
late final StatefulElement element;
#override
_MyHomePageState createState() => _MyHomePageState();
#override
StatefulElement createElement() {
this.element = StatefulElement(this);
return element;
}
}
class _MyHomePageState extends State<MyHomePage> {
String s = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(
'$s',
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
this.s = "2";
widget.element.markNeedsBuild();
},
child: Icon(Icons.add),
),
);
}
}