How to pass data by reference to stateful widget? I want to change a variable inside a stateful widget and update original variable - flutter

I want to pass data to a stateful widget, change the data inside the widget and also have it updated in the original location.
I want to avoid global variables and I am wondering if I can pass a variable to a stateful widget by reference.
Here is some example code where data is passed to the widget. If I use the slider, the counter is only update inside the widget, not in the main layout tree.
I appreciate any help.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'Counter inside main layout tree: $_counter',
style: Theme.of(context).textTheme.title,
),
TestWidget(_counter),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class TestWidget extends StatefulWidget {
int counter;
TestWidget(this.counter);
#override
_TestWidgetState createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
#override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
side: BorderSide(color: Colors.orange),
),
child: Column(children: <Widget>[
Text("This card is an external Widget"),
Slider(
min: 0,
max: 100,
divisions: 101,
onChanged: (double val) {
setState(() {
widget.counter = val.toInt();
});
},
value: widget.counter.toDouble(),
),
Text("Counter inside external widget: ${widget.counter}",
style: Theme.of(context).textTheme.title),
]));
}
}

Actually the StatefulWidget is immutable and its state is maintained by the State class. You cannot pass values by reference and update the widgets. Instead you can just pass the value and and a function that updates the value.
Example:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter([int value]) {
setState(() {
_counter = value ?? (_counter + 1);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'Counter inside main layout tree: $_counter',
style: Theme.of(context).textTheme.title,
),
TestWidget(
counter: _counter,
updateCount: _incrementCounter,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class TestWidget extends StatelessWidget {
final int counter;
final ValueChanged<int> updateCount;
const TestWidget({Key key, this.counter, this.updateCount}) : super(key: key);
#override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
side: BorderSide(color: Colors.orange),
),
child: Column(
children: <Widget>[
Text("This card is an external Widget"),
Slider(
min: 0,
max: 100,
divisions: 101,
onChanged: (double val) {
updateCount(val.toInt());
},
value: counter.toDouble(),
),
Text(
"Counter inside external widget: $counter",
style: Theme.of(context).textTheme.title,
),
],
),
);
}
}
Hoper that helps!

Related

set state Badge appbar - flutter

I'm trying to increment the app bar icon by making a custom app bar. But I can't update the value with setState. I tried to update the value by the setAppBarValue function, but it didn't work.
MyAppBar.of(context)?.setAppBarValue(_counter);
import 'package:badges/badges.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_incrementCounter();
MyAppBar.of(context)?.setAppBarValue(_counter);
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class MyAppBar extends StatefulWidget implements PreferredSizeWidget {
MyAppBar() : super();
#override
Size get preferredSize => const Size.fromHeight(60);
#override
_MyAppBarState createState() => _MyAppBarState();
static _MyAppBarState? of(BuildContext context) =>
context.findAncestorStateOfType<_MyAppBarState>();
}
class _MyAppBarState extends State<MyAppBar>
with SingleTickerProviderStateMixin {
int _appBarValue = 0;
setAppBarValue(int value) {
setState(() {
_appBarValue = value;
});
}
#override
Widget build(BuildContext context) {
return AppBar(
title: const Text("teste"),
actions: <Widget>[Center(child: _alertBadge(context))]);
}
Widget _alertBadge(BuildContext context) {
return Badge(
position: BadgePosition.topEnd(
top: 0,
end: 3,
),
badgeStyle: BadgeStyle(
badgeColor: Colors.red,
),
badgeContent: Text(
_appBarValue.toString(),
style: TextStyle(color: Colors.white),
),
child:
IconButton(icon: Icon(Icons.shopping_bag_outlined), onPressed: () {}),
);
}
}
You Have to pass the increment variable value to the appbar widget like this:
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(
appBarValue: _counter,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_incrementCounter();
// MyAppBar.of(context)?.setAppBarValue(_counter);
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
And use that value in your app bar widget:
class MyAppBar extends StatefulWidget implements PreferredSizeWidget {
const MyAppBar({
Key? key,
required this.appBarValue,
}) : super(key: key);
final int appBarValue;
#override
Size get preferredSize => const Size.fromHeight(60);
#override
_MyAppBarState createState() => _MyAppBarState();
static _MyAppBarState? of(BuildContext context) =>
context.findAncestorStateOfType<_MyAppBarState>();
}
class _MyAppBarState extends State<MyAppBar>
with SingleTickerProviderStateMixin {
// int _appBarValue = 0;
//
// setAppBarValue(int value) {
// setState(() {
// _appBarValue = value;
// });
// }
#override
Widget build(BuildContext context) {
return AppBar(
title: const Text("teste"),
actions: <Widget>[Center(child: _alertBadge(context))]);
}
Widget _alertBadge(BuildContext context) {
return Badge(
position: BadgePosition.topEnd(
top: 0,
end: 3,
),
badgeStyle: BadgeStyle(
badgeColor: Colors.red,
),
badgeContent: Text(
widget.appBarValue.toString(),
style: TextStyle(color: Colors.white),
),
child:
IconButton(icon: Icon(Icons.shopping_bag_outlined), onPressed: () {}),
);
}
}

My screen doesn't reflect the changes in my app though the setState method works well

I'm trying to call a StatefulWidget(i.e FirstPage()) within a MaterialApp. I'm pretty much new to flutter and I don't know where I went wrong. According to my knownledge I've used StatefulWidget to tell flutter my screen on that page is going to encounter some changes in UI. But I got no idea to fix this error.
main.dart file:
import 'package:flutter/material.dart';
import 'package:flutter_project/main.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: FirstPage());
}
}
class FirstPage extends StatefulWidget {
const FirstPage({Key? key}) : super(key: key);
#override
State<FirstPage> createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
#override
Widget build(BuildContext context) {
String buttonName = "Click";
int currentIndex = 0;
return Scaffold(
appBar: AppBar(
title: const Text("App title "),
),
body: Center(
child: currentIndex == 0
? Container(
width: double.infinity,
height: double.infinity,
color: Colors.blueAccent,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 280,
height: 80,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
side: BorderSide.none,
borderRadius: BorderRadius.circular(18),
),
backgroundColor: const Color.fromRGBO(9, 8, 99, 90),
foregroundColor: Colors.red,
),
onPressed: () {
setState(() {
buttonName = "Clicked";
//print(buttonName0);
});
},
child: Text(buttonName),
),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
//'BuildContext' - datatype and 'context' - variable name
return const SecondPage();
},
),
);
},
child: const Text("Move to new page"),
),
],
),
)
: Image.asset("images/img.png"),
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(label: "Home", icon: Icon(Icons.home)),
BottomNavigationBarItem(label: "Settings", icon: Icon(Icons.settings))
],
currentIndex: currentIndex,
onTap: (int index) {
//index value here is returned by flutter by the function 'onTap'
setState(() {
currentIndex = index;
//print(currentIndex);
});
},
),
); //Scaffold represents the skeleton of the app(displays white page)
}
}
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
);
}
}
Images:
Before pressing Click and Settings button
After pressing Click and Settings looks the same
I want the screen to change the ElevatedButton Click to Clicked when onPressed() is triggered and also, the app should be able to switch settings page when the onTap() method is triggered in the bottom navigation bar.
The code worked initially when I refrained from calling an entire page of Scaffold from Material app, but as soon as I changed the part
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: FirstPage()); //<-- this part
}
}
I'm getting this error.
Put your variables outside the build method.Else it will reset to default on every build.
It will be like
class _FirstPageState extends State<FirstPage> {
//here
String buttonName = "Click";
int currentIndex = 0;
#override
Widget build(BuildContext context) {
// Not here
return Scaffold(
appBar: AppBar(
More about StatefulWidget

How can i make a moveable overlay?

I want to show a minimize moveable calling screen in top of the app
I tried with stack it does not meet my expectation
#Raiyan, you have to use picture-in-picture concept to implement such floating child.
In flutter, multiple plugins are there, that we can use for the, some are as follows:
https://pub.dev/packages/pip_view
https://pub.dev/packages/floating
https://pub.dev/packages/easy_pip
floating package will fit in your case, it provides picture in Picture mode management for Flutter.
Sadly the gif is not working... But by on taping and draging on the green window will make the green window move.
Try this:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return OverlayWindow(
overlayChild: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text(
"Overlay Window",
style: TextStyle(fontSize: 20),
),
Icon(
Icons.android,
size: 80,
),
],
),
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
),
);
}
}
class OverlayWindow extends StatefulWidget {
const OverlayWindow(
{Key? key, required this.overlayChild, required this.child})
: super(key: key);
final Widget overlayChild;
final Widget child;
#override
State<OverlayWindow> createState() => _OverlayWindowState();
}
class _OverlayWindowState extends State<OverlayWindow> {
double _top = 0;
double _left = 0;
#override
Widget build(BuildContext context) {
return Stack(
children: [
widget.child,
Positioned(
top: _top,
left: _left,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
_top = max(0, _top + details.delta.dy);
_left = max(0, _left + details.delta.dx);
});
},
child: Container(
height: 300,
width: 200,
color: Colors.green,
child: widget.overlayChild,
),
),
)
],
);
}
}
More about things like that, you can find here:
https://docs.flutter.dev/development/ui/advanced/gestures

ListView.builder inside a Column inside a SingleChildScrollView

all. I am trying to add a ListView.builder to a Column that is inside a SingleChildScrollView. However, I am getting an exception, likely due to the fact that there is no constraint for the ListView.builder. Here is my code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ListView.builder(
itemBuilder: (context, index) => const Text('a'),
itemCount: 2,
),
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
If I use a Container and set a defined height, the code above works. However, I am trying to get the ListView.builder to not have a fixed size. I've tried using the Expanded widget and I still get this error. Is there a way to make this work without a defined height? Thanks
In the column widget add mainAxisSize:MainAxisSize.min and in List view.builder add shrinkWrap:true and physics:NeverScrollablePhysics(). That should solve the issue and instead of center widget use SafeArea or a container with specific height.
Here's your working code
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ListView.builder(
shrinkWrap:true,// -> Add this here
physics:NeverScrollablePhysics(),// -> And this one
itemBuilder: (context, index) => const Text('a'),
itemCount: 2,
),
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}

calling setState from drawer?

Context
Using the standard flutter demo I added a drawer. I put the contents of my drawer in another class in another file. Both are stateful widgets. I use a floating action button in the drawer with setState incrementing the global variable for the number shown on the main screen.
What happens
When I press it nothing happens.
It does not update the text on the main page/main.dart until I use the floating action button on the main page/main.dart. Then it adds all the increments I added in the drawer too.
So it's just not rebuilding the widget.
How do I get it to rebuild the widget? I thought everything you needed was that they were both inside a setstate?
Best possible cause I have come up with
Is it because even though I use a stateful widget inside a stateful widget, the setstate only works on the embedded stateful widget because the embedded widget is technically a created object in the main.dart?
Code for main.dart
import 'package:flutter/material.dart';
import 'drawer.dart';
DrawerClass _drawer = DrawerClass();
int counter = 0;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void _incrementCounter() {
setState(() {
counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
endDrawer: SafeArea(child: Drawer(child: Container(child: _drawer,),)),
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Code for drawer
import 'package:flutter/material.dart';
import 'main.dart';
class DrawerClass extends StatefulWidget {
#override
_DrawerClassState createState() => _DrawerClassState();
}
class _DrawerClassState extends State<DrawerClass> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF222831),
body: Container(
width: 100,
child: Center(
child: FloatingActionButton(
tooltip: 'Increment',
child: Icon(Icons.add),
onPressed: () {
setState(() {
counter++;
});
},
),
),
),
);
}
}
Just pass the _incrementCounter to the DrawerClass. With this change your DrawerClass can now be a StatelessWidget and there will be no need for the counter and _drawer variables to be global. Please see the code below :
main.dart
import 'package:flutter/material.dart';
import 'drawer.dart';
//DrawerClass _drawer = DrawerClass();
//int counter = 0;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int counter = 0;
void _incrementCounter() {
setState(() {
counter++;
});
}
#override
Widget build(BuildContext context) {
final DrawerClass _drawer = DrawerClass(
increment: _incrementCounter,
);
return Scaffold(
endDrawer: SafeArea(
child: Drawer(
child: Container(
child: _drawer,
),
)),
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
drawer.dart
import 'package:flutter/material.dart';
class DrawerClass extends StatelessWidget {
final Function increment;
const DrawerClass({Key key, this.increment}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF222831),
body: Container(
width: 100,
child: Center(
child: FloatingActionButton(
tooltip: 'Increment',
child: Icon(Icons.add),
onPressed: increment,
),
),
),
);
}
}