Flutter ElevatedButton onPressed functions Avoid using unnecessary statements - flutter

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();

Related

Flutter: Is it possible to know if you're currently off stage?

I have a number of pages in my app wrapped in Offstage widgets. Each page makes use of the provider package to render based on state updates (e.g. the user does something, we make a network call and display the result).
As the pages are wrapped in Offstage widgets, the build() methods (and subsequent network calls) are called even if it's not the current page.
Is there a way inside the build() method to know if the widget is currently off stage (and if so, skip any expensive logic)?
I'm assuming I can work something with global state etc, but I was wondering if there was anything built-in in relation to the Offstage widget itself, similar to mounted
You can try finding the parent OffStage widget and see if the offstage property is true or false
#override
Widget build(BuildContext context) {
final offstageParent = context.findAncestorWidgetOfExactType<Offstage>();
if (offstageParent != null && offstageParent.offstage == false) {
// widget is currently offstage.
print('offstaged child');
} else {
// widget is not offstage
print('non-offstaged child');
}
return const Text('Example Widget');
}
I made a custom-made mechanism for the goal you wanna achieve:
First, I am declaring a new Map<String, bool> in a separate file alone that will hold the offStage bool value with the key of each class widget.
Map<String, bool> offStageMap = {};
then in the implementation of the StatefulWidget where the offstage widget is in:
class ExampleWidget extends StatefulWidget {
ExampleWidget({super.key}) {
widgetMapKey = runtimeType.toString();
}
late final String widgetMapKey;
#override
State<ExampleWidget> createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
final bool defaultIsOffStaged = false;
bool? localStateIsOffStages;
#override
void initState() {
offStageMap[widget.widgetMapKey] ??= defaultIsOffStaged;
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
bool previousIsOffStaged = offStageMap[widget.widgetMapKey]!;
setState(() {
localStateIsOffStages =
offStageMap[widget.widgetMapKey] = !previousIsOffStaged;
});
},
child: Offstage(
offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
child: Container(),
),
);
}
} },
child: Offstage(
offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
child: Container(),
),
);
}
}
let me explain what this is about.
first I declared a defaultIsOffStaged where it should be the initial offStage value when nothing is saved in that map.
when that widget is inserted in the widget tree (initState() called), the widget.widgetMapKey of the ExampleWidget widget will be saved in that map with the value of the default one which is defaultIsOffStaged.
offStageMap[widget.widgetMapKey] ??= defaultIsOffStaged;
in the offstage property o the OffStage widget, in this line:
offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
the nullable localStateIsOffStages will be null for the first time since it has no value yet, so offStageMap[widget.widgetMapKey]! which equals to defaultIsOffStaged will be the bool value of offstage.
until now what we have, is a map containing the key that belongs only to the ExampleWidget which is its widget.widgetMapKey with its offStage value, right?
now from all places in your app, you can get the offStage value of that widget with its widgetMapKey like this:
print(offStageMap[ExampleWidget().widgetMapKey]); // true
now let's say you want to change the offstage property of that widget, in my code I used a simple example of GestureDetector, so when we tap in the Text("toggle offstage") area, it toggles offStage, here is what happens:
we got the existing value in the map:
bool previousIsOffStaged = offStageMap[widget.widgetMapKey]!;
then assign the opposite of it, to that widget key in the map, and the localStateIsOffStages bool variable which was nullable, now it has a value.
and as normal so the state updates I wrapped it in a SetState(() {})
now the widget's offstage will be toggled, and every time the widget key in the map will be updated with that new value.
the localStateIsOffStages I declared just to hold the local state when this is happening while the StatefulWidget state updates.
after the StatefulWidget is disposed of (when you pop the route as an example) and open that route again, the initState() will execute but since we have now an entry in the map, it's not null so nothing will happen inside initState().
the localStateIsOffStages will be null, so the offStage property of the Offstage widget will be the value from the map, which is the previous value before the widget is disposed.
that's it, from other places you can check for the offstage value of that specific widget like this:
print(offStageMap[ExampleWidget().widgetMapKey])
you can do it for all your widget pages, so you will have a map containing the offStage values of them all.
I take it one step up, and made those methods that I guess they will help:
this will return a List with the pages where the value is true.
List<String> offstagedPages() {
List<String> isOffStagedPages = [];
offStageMap.forEach((runtimeType, isOffStaged) {
if (isOffStaged) {
isOffStagedPages.add(runtimeType);
}
});
return isOffStagedPages;
}
this will return a true if a page is off staged and false if not:
bool isPageWidgetOffStaged(String runtimeType) {
if (offStageMap.containsKey(runtimeType)) {
return offStageMap[runtimeType]!;
}
return false;
}
Hope this helps a little.
Maybe it's not applicable to you, but you might be able to solve it by simply not using Offstage. Consider this app:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool showFirst = true;
void switchPage() {
setState(() {
showFirst = !showFirst;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Stack(children: [
Offstage(offstage: !showFirst,child: A("first", switchPage)),
Offstage(offstage: showFirst,child: A("second", switchPage)),
]))));
}
}
class A extends StatelessWidget {
final String t;
final Function onTap;
const A(this.t, this.onTap, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('$t is building');
return TextButton(onPressed: ()=> onTap(), child: Text(t));
}
}
You will notice by the prints that both pages are build. But if you rewrite it like this without Offstage, only the visible one is build:
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Stack(children: [
if (showFirst) A("first", switchPage),
if (!showFirst) A("second", switchPage),
]))));
}
If you want to just keep state alive your pages , you can use https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html , you may check this blog for example usage, https://medium.com/manabie/flutter-simple-cheatsheet-4370a68f98b3
If you are using Navigator, you can just extends NavigatorObserver. Then you will get didpush and didpop, use state to manage elementlifecycle, you will get page onPause and onResume fun.

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 slider not updating widget variables

am playing around with the slider widget on flutter, and I can't figure out why it does not update certain values in a different widget, example code is shown below;
When i move the slider, it has no issues moving, but the value i'm trying to update on the other widget does not update even though the onchanged is updating the variable passed through in a set state accordingly.
any help would be greatly appreciated!
Scaffold Code
class TestPage extends StatelessWidget {
static const id = "test_page";
#override
Widget build(BuildContext context) {
double testValue = 0;
return Scaffold(
body: Column(
children: [
Text("Hello World"),
TestBoxNumber(
numberDisplay: testValue,
),
TestSlider(testValue: testValue),
],
),
);
}
}
Slider Code
class TestSlider extends StatefulWidget {
double testValue;
TestSlider({required this.testValue});
#override
_TestSliderState createState() => _TestSliderState();
}
class _TestSliderState extends State<TestSlider> {
#override
Widget build(BuildContext context) {
return Slider(
activeColor: themeData.primaryColorLight,
value: widget.testValue,
min: 0,
max: 100,
divisions: 100,
label: widget.testValue.round().toString(),
onChanged: (double value) {
setState(() {
widget.testValue = value;
});
},
);
}
}
Different Widget Code
class TestBoxNumber extends StatefulWidget {
final double numberDisplay;
const TestBoxNumber({required this.numberDisplay});
#override
_TestBoxNumberState createState() => _TestBoxNumberState();
}
class _TestBoxNumberState extends State<TestBoxNumber> {
#override
Widget build(BuildContext context) {
return Container(
child: Text(widget.numberDisplay.toString()),
);
}
}
The problem is that you are constructing TestBoxNumber widget in such a way that value (testValue) will always be the same (testValue is never returned out of the TestSlider widget).
How to overcome this issue?
You can make your TestPage a StatefullWidget. Then create callback from TestSlider, so when you change value in TestSlider you will call some function in TestPage (with setState in it, causing re-rendering your page).
Or if you don't want your whole TestPage widget to be Statefull (if, let's say, you predict a lot of other static widgets in it and you don't want them to be re-rendered because you just moved a slider), you can create wrapper Statefull widget and put both TestSlider and TestBoxNumber widgets in it. This is more flexible approach, imho.
Here is small scheme of what I mean by wrapping two widgets in another one:
UPD: btw, there is no point in making TestBoxText a statefull widget if it's only purpose is to display a text and you pass it's value through the constructor.

Pass parent state to generic child widget

I'm building a flutter app, and I have built a customized AppBar widget for it. This appbar has a SearchBar widget, which calls whatever callback is passed to it onChange. Now, there are multiple screens that use this SearchBar, and each of them will do something different with the user input. But I've noticed that on each of the screens that use the appbar, I'd have to use a state to control the SearchBar inputted text. So, I'm trying to not have to create the state for every screen, and have a Widget that wraps my screens, and controls the input in it's state, and passes it's state down to the child I provide to this apps. This would be similar to React's Higher Order Components, which wrap another component and can pass props to it.
This seems to me like a good design pattern, but I don't know how to implement it. Since the child widget that I would pass to this second order component that would wrap my screens, won't be getting any info from it, the child is simply passed as a widget (note: the code is also doing other stuff, working as a general wraper to replace similar repetitive code in all of my screens):
class CustomScaffold extends StatefulWidget {
final ScreenInfo appBarInfo;
final Builder child;
final Widget bottomBarApp;
final Widget appBar;
final EdgeInsetsGeometry padding;
const CustomScaffold({
#required this.child,
Key key,
this.bottomBarApp,
this.appBar,
this.padding,
this.appBarInfo,
}) : super(key: key);
#override
_CustomScaffoldState createState() => _CustomScaffoldState();
}
class _CustomScaffoldState extends State<CustomScaffold> {
String searchTerm = '';
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
widget.appBar ??
GradientAppBar(
title: widget.appBarInfo.label,
searchBar: SearchBar(
callback: (String input) {
setState(() {
searchTerm = input;
});
},
placeholder: widget.appBarInfo.searchPlaceholder,
),
),
Padding(
padding: widget.padding,
child: widget.child,
),
],
),
),
bottomNavigationBar: widget.bottomBarApp ?? CustomBottomBarNavigator(),
);
}
}
I'm thinking that I could passa builder instead of a widget as the child, but I'm not sure how this would work. Also, I'm still learning bloc in general, so I'm not sure if it would be a good idea to use bloc for this. I'm guessing bloc's purpose is a little different, and would complicate this specific pattern.
Does this idea make sense? What would be the best way to implement it?
Thanks in advance.

How to change values within a stateful widget class from a different class?

I have a stateful widget LetterButton()
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text(widget.caption),
onPressed: onChanged,
color: colors[currentIndex],
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
);
}
In my main.dart file I declare an array of LetterButtons
List<LetterButton> buttonArray;
which I initialize and fill during initState() via the method
void makeButtons() {
for (var letter in alphabet) {
buttonArray.add(
LetterButton(letter),
);
}
}
The buttons in the list are then displayed in the UI
Wrap(
children: buttonArray,
)
How can I change the value of currentIndex (an int in
class LetterButtonState extends State<LetterButton>) or otherwise change all the buttons to the same color from main.dart?
NOTE: I asked a similar question a few days ago, but the answer was a little above my current knowledge, as are responses I've seen to similar Q's here on SO. I have a little understanding of callbacks, and experimented a little with the provider package, but there's such a variety of answers and info available online that it's hard for me to even know what I don't know to be able to answer my question :-)
Create stateful widget with state as public access, so that you can access outside of the package and provide key to constructor. So that you can refer key and get can get state to change value. See the following example
class LetterButton extends StatefulWidget {
LetterButton({GlobalKey key}) : super(key: key);
#override
LetterButtonState createState() => LetterButtonState();
}
class LetterButtonState extends State<LetterButton> {
int value = 0;
//this public method is to update int value
setValue(int value) {
setState(() {
this.value = value;
});
}
#override
Widget build(BuildContext context) {
return Center(
child: Text(value.toString()),
);
}
}
//In Main.dart
GlobalKey<LetterButtonState> _buttonStateKey = GlobalKey();
//while creating widget
LetterButton(key:_buttonStateKey)
//in onTapCallback you can call to update value
_buttonStateKey.currentState?.setValue(10);
Just send your created function to new class by parameter and the new class should be Constarcter with Function lThen you can call the function from a new class.