I am pretty new to OOP and Flutter. I created the following widget and want to call the widget function when creating it inside another stateless Widget.
My plan is to create the widget as a child and then use the defined variable widgetState to check if it is true and if it is, it should call the void printSample() function. The conditional check shout be triggered by onTap of the widget (InkWell).
Widget with the Function:
class WhiteCard extends StatelessWidget {
final widgetState = true;
final VoidCallback onTap;
final Color switchColor = Colors.red;
void printSample() {
print("Sample text");
}
const WhiteCard({
Key? key,
required this.onTap,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Container(
width: 320,
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18),
color: Colors.white,
),
child: Center(
child: ScreenController.isLargeScreen(context)
? Text("large")
: ScreenController.isMediumScreen(context)
? Text("medium")
: Text("small"),
),
),
);
}
}
Widget that calls the WhiteCard()
class LargeScreen extends StatelessWidget {
const LargeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Expanded(
child: Container(
color: Colors.orangeAccent,
child: Center(
child: WhiteCard(
onTap: () {
CALL THE FUNCTION FROM WIDGET);
},
),
),
),
);
}
}
You can also define a function to leave the definition of object cleaner:
class LargeScreen extends StatelessWidget {
const LargeScreen({Key? key}) : super(key: key);
onTapFunction() {
// write he whatever you need to do when taping widget
}
#override
Widget build(BuildContext context) {
return Expanded(
child: Container(
color: Colors.orangeAccent,
child: Center(
child: WhiteCard(
onTap: onTapFunction,
),
),
),
);
}
}
So if you need to call the onTapFunction from elsewhere, you can.
Well if I understood your question correctly then your onTap should be something like this:
onTap: () {
if (widgetState) {
printSample();
}
Good luck, Feel free to respond if you have queries
Related
I've simple widget tree, and try to figure out why Provider.of<>() doesn't work in the GestureDetector, onTap() callback.
This is my model:
class ShareObject {
int intField;
ShareObject(this.intField);
}
class ShareObjectProvider extends ShareObject with ChangeNotifier {
ShareObjectProvider(super.intField);
void increment() {
intField++;
notifyListeners();
}
}
Here is my simple tree, where I try to invoke method from model:
class ParentWidgetState extends State<ParentWidget> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => ShareObjectProvider(0),
child: Scaffold(
body: const WidgetThree(),
),
);
}
}
class WidgetThree extends StatelessWidget {
const WidgetThree({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: SizedBox(
width: 200,
height: 200,
child: Container(
color: Colors.deepOrange,
child: Center(
child: GestureDetector(
onTap: () =>
{Provider.of<ShareObjectProvider>(context).increment()},
child: Text(
"Test ${Provider.of<ShareObjectProvider>(context).intField}",
style: const TextStyle(color: Colors.blueAccent),
),
)
),
),
),
);
}
}
Why when I change to Provider.of<ShareObjectProvider>(context, listen: false).increment() it start working correctly?
As per documentation,
Provider.of<T>(context)
works like a watch, which makes the widget to rebuild again
listen: false
makes it work like read
in your case:
late ShareObjectProvider provider;
void initState() {
super.initState();
provider = Provider.of<ShareObjectProvider>(listen: false);
}
then use it in your GestureDetector
here's the link for provider's documentation where the above said things are mentioned
Your syntax is not good for provider. you need to add listen : true OR listen: false.
Provider.of<ShareObjectProvider>(context, listen: false).increment()
I built custom app for training. I created buttons with gesture detector and i assigned number to them and i created global variable "score". I want buttons to add their numbers to "score" variable and i want to show the variable in a container but Somehow it does not work.Can it be about states?Does anyone help me?
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
appBarTheme: const AppBarTheme(
backgroundColor: Colors.deepPurple,
centerTitle: true,
elevation: 3,
),
),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("My Application"),
),
body: const Body(),
);
}
}
int score = 5;
class Body extends StatefulWidget {
const Body({Key? key}) : super(key: key);
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 50,
width: 200,
decoration: const BoxDecoration(color: Colors.grey),
child: Center(child: Text(score.toString())),
),
const SizedBox(
height: 70,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Numb(
color: Colors.pink,
numb: 1,
),
Numb(
color: Colors.pink,
numb: 2,
),
Numb(
color: Colors.pink,
numb: 3,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Numb(
color: Colors.pink,
numb: 4,
),
Numb(
color: Colors.pink,
numb: 5,
),
Numb(
color: Colors.pink,
numb: 6,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Numb(
color: Colors.pink,
numb: 7,
),
Numb(
color: Colors.pink,
numb: 8,
),
Numb(
color: Colors.pink,
numb: 9,
),
],
),
],
);
}
}
class Numb extends StatefulWidget {
final int? numb;
final Color? color;
const Numb({
Key? key,
required this.numb,
required this.color,
}) : super(key: key);
#override
State<Numb> createState() => _NumbState();
}
class _NumbState extends State<Numb> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
score += widget.numb!;
});
},
child: Container(
margin: projectPadding.allPad * 0.5,
decoration: BoxDecoration(color: widget.color),
height: MediaQuery.of(context).size.height * 0.06,
width: MediaQuery.of(context).size.width * 0.1,
child: Center(
child: Text(widget.numb.toString()),
),
),
);
}
}
class projectPadding {
static const EdgeInsets horizantalPad = EdgeInsets.symmetric(horizontal: 20);
static const EdgeInsets verticalPad = EdgeInsets.symmetric(vertical: 20);
static const EdgeInsets allPad = EdgeInsets.all(20);
}
Numb setState only update the _NumbState ui. in order to update parent widget, you can use callback method that will trigger setState on parent UI.
class Numb extends StatefulWidget {
final int? numb;
final Color? color;
final Function(int) callBack;
const Numb({
Key? key,
required this.numb,
required this.color,
required this.callBack,
}) : super(key: key);
#override
State<Numb> createState() => _NumbState();
}
///....
onTap: () {
setState(() {
score += widget.numb!;
});
widget.callBack(score);
},
And you can add logic and others operation like
Numb(
color: Colors.pink,
numb: 1,
callBack: (p0) {
setState(() {});
},
),
I will also recommend starting state-management like riverpod / bloc
Why doesn't it work?
When you use the setState method, it triggers a rebuild of the widget where it is called.
In your code, you call the setState method in your Numb widget, which will therefore be rebuilt.
The problem is that the score value that you display on screen is located in your Body widget, and you want this widget to be rebuilt whenever the score changes.
So how do we do that?
How to make it work?
This is a State Management issue and there are multiple ways to solve it. You can find in the official documentation a good example to understand how this works.
Lifting the State Up and Callbacks
Following the previous logic described above, you would have to call the setState method in the Body widget to trigger a rebuild, and this whenever the score changes, which means whenever a Numb widget is pressed.
For that you can take advantage of callbacks which are basically functions that you can pass as parameters, and that will run the code they contain when they are called (you can see the official documentation's example about accessing a state using callbacks).
class Numb extends StatelessWidget { //<-- you can turn Numb into a StatelessWidget which is much simpler since you don't have to call the 'setState' method in it
final int? numb;
final Color? color;
final Function(int) callback; //<-- add your callback as a field...
const Numb({
Key? key,
required this.numb,
required this.color,
required this.callback, //<-- ... and in the constructor...
}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => callback(numb!), //<-- ... and simply call it this way in the onTap parameter, giving it the numb value
child: Container(
margin: projectPadding.allPad * 0.5,
decoration: BoxDecoration(color: color),
height: MediaQuery.of(context).size.height * 0.06,
width: MediaQuery.of(context).size.width * 0.1,
child: Center(
child: Text(numb.toString()),
),
),
);
}
}
class _BodyState extends State<Body> {
#override
Widget build(BuildContext context) {
var updateScoreCallback = (int number) => setState(() => score += number); //<-- your callback takes an int as parameter and call setState adding the input number to the score
return Column(
children: [
...,
Numb(
color: Colors.pink,
numb: 1,
callback: updateScoreCallback, //<-- give your callback to your Numb widgets
),
Numb(
color: Colors.pink,
numb: 2,
callback: updateScoreCallback,
),
...
}
}
Like that, pressing any of your Numb widget will call the callback, that will call the setState method in the appropriate Body widget.
State Management with Packages
The previous method to handle state management with callbacks is good when your case is simple, but if you need for example to access a state from multiple places in your code, it can be quickly too difficult to manage and inefficient. For that, there are multiple packages that are available to make things easier. The official recommandation to start with is the Provider package as it is simple to use, but depending on your need you may want to look for other options like BLoC, Redux, GetX, etc. (full list of state management approaches.
Using the Provider approach, start by adding the package in your project (flutter pub add provider), and add the following changes to your code:
import 'package:provider/provider.dart';
class ScoreData with ChangeNotifier { //<-- create a structure to hold your data, and use the mixin 'ChangeNotifier' to make ScoreData able to notify its listeners for any changes
int _score = 5;
int get score => _score;
void addToScore(int number) {
_score += number;
notifyListeners(); //<-- this method comes from ChangeNotifier (which comes from the Flutter SDK and not from the Provider package), and notify all listeners
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("My Application"),
),
body: ChangeNotifierProvider<ScoreData>( //<-- this says that at this level of the widget tree, you provide a ScoreData that can notify its listeners for any changes...
create: (context) => ScoreData(), //<-- ... and you provide the ScoreData here by creating a new one (you can provide an existing one using the "value" parameter)
child: const Body(),
),
);
}
}
class Body extends StatefulWidget {
const Body({Key? key}) : super(key: key);
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 50,
width: 200,
decoration: const BoxDecoration(color: Colors.grey),
child: Center(
child: Text(Provider.of<ScoreData>(context).score.toString())), //<-- this enables you to retrieve the provided ScoreData higher in the widget tree, and to listen to its value
),
...
],
);
}
}
class Numb extends StatelessWidget {
final int? numb;
final Color? color;
const Numb({
Key? key,
required this.numb,
required this.color,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () =>
Provider.of<ScoreData>(context, listen: false).addToScore(numb!), //<-- here as well you retrieve the provided ScoreData, but you only want to call its method "addToScore" without needing to listen to its changes, so add the "listen: false" parameter to make it work
child: Container(
...
),
);
}
}
I am using the pull_to_refresh package.
I am having a Stack() with two elements. One of them is the Refresher(). When I pull down on my screen, activating the refreshing animation, the build method is called constantly. The problem is that my second Widget in my Stack is quite complex to build and takes some time. I want to prevent having it build all the time when triggering the Refresher-Animation. Is this possible?
My simplified code would look like this:
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: Stack(children: <Widget>[
SafeArea(
child: Column(children: [
Expanded(
child: Container(
margin: EdgeInsets.all(0),
width: 100.w,
constraints: const BoxConstraints.expand(),
child: SizedBox(
width: 100.w,
child: Refresher( refresher stuff )
)
)
)
)
),
SecondItem()
)
)
}
Somehow the build method of SecondItem is called all the time. Not the build method of the whole scaffold.
If Your second Item dont want to get refresh, then add it as a separeate class like,
Expanded(
child: Container(
margin: EdgeInsets.all(0),
width: 100.w,
constraints: const BoxConstraints.expand(),
child: SizedBox(
width: 100.w,
child: Refresher( refresher stuff )
)
)
)
)
),
SecondItem()
)
class SecondItem extends StatefulWidget {
#override
_SecondItemState createState() => _SecondItemState();
}
class _SecondItemState extends State<SecondItem> {
int counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Text("Your Second Widget"),
);
}
}
Now your SecondItem() will not get refresh when you refresh your FirstItem()
Since I wasn't really able to replicate the problem, I build a working structure that implements refreshing.
First the main widget, in my case MyHomePage.
This widget implements the Scaffold and Stack with FirstWidget and SecondWidget as children.
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: const <Widget>[
FirstWidget(),
SecondWidget(),
],
),
);
}
}
FirstWidget is a statefull widget with a counter in the state.
It implements the refresher with a specific controller.
Once the refresh is triggered, it calls set state and updates the counter within his state.
That should trigger only his build again and not any other.
I implemented a Text to show the counter value increasing at each refresh, and a Print to expose the build.
class FirstWidget extends StatefulWidget {
const FirstWidget({
Key? key,
}) : super(key: key);
#override
State<FirstWidget> createState() => _FirstWidgetState();
}
class _FirstWidgetState extends State<FirstWidget> {
late int _counter;
late RefreshController _refreshController;
#override
void initState() {
_counter = 1;
_refreshController = RefreshController(initialRefresh: false);
super.initState();
}
#override
Widget build(BuildContext context) {
print('First widget built');
return SafeArea(
child: Column(
children: [
Container(
margin: const EdgeInsets.all(0),
width: double.infinity,
height: 500,
color: Colors.red,
child: SmartRefresher(
controller: _refreshController,
onRefresh: () async {
setState(() {
_counter++;
});
await Future.delayed(const Duration(milliseconds: 1000));
_refreshController.refreshCompleted();
},
),
),
Text("Counter: $_counter"),
],
),
);
}
}
Last we got the SecondWidget which is a another simple widget with a print statement.
In case of build it writes on the console.
When the FirstWidget refresh, the second doesn't build becouse his state has not changed.
class SecondWidget extends StatefulWidget {
const SecondWidget({
Key? key,
}) : super(key: key);
#override
State<SecondWidget> createState() => _SecondWidgetState();
}
class _SecondWidgetState extends State<SecondWidget> {
#override
Widget build(BuildContext context) {
print('Second widget built');
return const Center(child: Text('Second here!'));
}
}
Possible cause of your problem.
It could be that when refreshing, you actually are updating the state of a parent widget that, on cascade, causes the re build of your second widget.
If state is handled correctly, and your second widget doesn't depends on your first widget state, the refresh should not rebuild the second.
Where does the FocusScope widget create in the tree and we pass every context in it and it can request to any focus nodes. When we pass context to FocusScope it will start looking above the context and we never used the FocusScope widget in the code in the hierarchy where does it create and how does it resolves in the case of scaffold if we pass context that is above in the tree then it throws an exception then we use builder type of thing but in FocusScope why it doesn't throw an error?
Here is the example for the FocusScope
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
/// A demonstration pane.
///
/// This is just a separate widget to simplify the example.
class Pane extends StatelessWidget {
const Pane({
Key? key,
required this.focusNode,
this.onPressed,
required this.backgroundColor,
required this.icon,
this.child,
}) : super(key: key);
final FocusNode focusNode;
final VoidCallback? onPressed;
final Color backgroundColor;
final Widget icon;
final Widget? child;
#override
Widget build(BuildContext context) {
return Material(
color: backgroundColor,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Center(
child: child,
),
Align(
alignment: Alignment.topLeft,
child: IconButton(
autofocus: true,
focusNode: focusNode,
onPressed: onPressed,
icon: icon,
),
),
],
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool backdropIsVisible = false;
FocusNode backdropNode = FocusNode(debugLabel: 'Close Backdrop Button');
FocusNode foregroundNode = FocusNode(debugLabel: 'Option Button');
#override
void dispose() {
super.dispose();
backdropNode.dispose();
foregroundNode.dispose();
}
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
final Size stackSize = constraints.biggest;
return Stack(
fit: StackFit.expand,
// The backdrop is behind the front widget in the Stack, but the widgets
// would still be active and traversable without the FocusScope.
children: <Widget>[
// TRY THIS: Try removing this FocusScope entirely to see how it affects
// the behavior. Without this FocusScope, the "ANOTHER BUTTON TO FOCUS"
// button, and the IconButton in the backdrop Pane would be focusable
// even when the backdrop wasn't visible.
FocusScope(
// TRY THIS: Try commenting out this line. Notice that the focus
// starts on the backdrop and is stuck there? It seems like the app is
// non-responsive, but it actually isn't. This line makes sure that
// this focus scope and its children can't be focused when they're not
// visible. It might help to make the background color of the
// foreground pane semi-transparent to see it clearly.
canRequestFocus: backdropIsVisible,
child: Pane(
icon: const Icon(Icons.close),
focusNode: backdropNode,
backgroundColor: Colors.lightBlue,
onPressed: () => setState(() => backdropIsVisible = false),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// This button would be not visible, but still focusable from
// the foreground pane without the FocusScope.
ElevatedButton(
onPressed: () => debugPrint('You pressed the other button!'),
child: const Text('ANOTHER BUTTON TO FOCUS'),
),
DefaultTextStyle(
style: Theme.of(context).textTheme.headline2!,
child: const Text('BACKDROP')),
],
),
),
),
AnimatedPositioned(
curve: Curves.easeInOut,
duration: const Duration(milliseconds: 300),
top: backdropIsVisible ? stackSize.height * 0.9 : 0.0,
width: stackSize.width,
height: stackSize.height,
onEnd: () {
if (backdropIsVisible) {
backdropNode.requestFocus();
} else {
foregroundNode.requestFocus();
}
},
child: Pane(
icon: const Icon(Icons.menu),
focusNode: foregroundNode,
// TRY THIS: Try changing this to Colors.green.withOpacity(0.8) to see for
// yourself that the hidden components do/don't get focus.
backgroundColor: Colors.green,
onPressed: backdropIsVisible
? null
: () => setState(() => backdropIsVisible = true),
child: DefaultTextStyle(
style: Theme.of(context).textTheme.headline2!,
child: const Text('FOREGROUND')),
),
),
],
);
}
#override
Widget build(BuildContext context) {
// Use a LayoutBuilder so that we can base the size of the stack on the size
// of its parent.
return LayoutBuilder(builder: _buildStack);
}
}
i am studying key in flutter. and in explanation, when i want swap widget in statefulWidget i need to add key value. because when flutter check element structure if type, state are not same they don't response. this is how i understand.
void main() => runApp(new MaterialApp(home: PositionedTiles()));
class PositionedTiles extends StatefulWidget {
#override
State<StatefulWidget> createState() => PositionedTilesState();
}
class PositionedTilesState extends State<PositionedTiles> {
List<Widget> tiles = [
StatefulColorfulTile(key: UniqueKey()), // Keys added here
StatefulColorfulTile(key: UniqueKey()),
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(children: tiles),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles),
);
}
swapTiles() {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
}
}
class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({Key key}) : super(key: key); // NEW CONSTRUCTOR
#override
ColorfulTileState createState() => ColorfulTileState();
}
class ColorfulTileState extends State<ColorfulTile> {
Color myColor;
#override
void initState() {
super.initState();
myColor = UniqueColorGenerator.getColor();
}
#override
Widget build(BuildContext context) {
return Container(
color: myColor,
child: Padding(
padding: EdgeInsets.all(70.0),
));
}
}
but i saw this code.
Widget build(BuildContext context) {
return Column(
children: [
value
? const SizedBox()
: const Placeholder(),
GestureDetector(
onTap: () {
setState(() {
value = !value;
});
},
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
!value
? const SizedBox()
: const Placeholder(),
],
);
}
this code is also use statefulWidget. in this code when user taps Box it's changed but i think there're no key value and in element structure there are different type(one is SizedBox and the other is placeHolder) so i think there aren't changed. why they're changed? what i misunderstand?