Changing the state Widget of one through another Widget - flutter

MyHomePageState:
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: bgColor,
body: ListView(
children: <Widget>[
Stack(
alignment: Alignment.topCenter,
children: <Widget>[
mainWidget(),
],
),
connectedStatusText(),
],
));
}
I'm trying to change the status of connectedStatusText() from mainWidget()!
My connectedStatus:
class connectedStatusText extends StatefulWidget
{
State<connectedStatusText> createState() {
return connectedStatus();
}
}
class connectedStatus extends State<connectedStatusText> {
String status = "IDLE";
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(text: 'Status:', style: connectedStyle, children: [
TextSpan(text: status, style: disconnectedRed)
]),
),
);
}
}
I want to change the $status text to "connected" through ontap of mainWidget().
mainWidget:
....
class mainWidget extends StatefulWidget
{
MyED createState() => new MyED();
}
class MyED extends State<mainWidget> {
child: new GestureDetector(
onTap: () => setState(() {
//change here
}
tried to set a global variable to connectedStatus:
GlobalKey<connectedStatus> key = GlobalKey<connectedStatus>();
and change by ontap...
child: new GestureDetector(
onTap: () => setState(() {
//change here
key.currentState.status = "CONNECTED";
}
)
}
but it does not work!
Any help for me to change this text through another place?

Please refer to below example code to update state using ValueNotifier and ValueListenableBuilder.
ValueNotifer & ValueListenableBuilder can be used to hold value and update widget by notifying its listeners and reducing number of times widget tree getting rebuilt.
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Screen2(),
);
}
}
class Screen2 extends StatefulWidget {
final String userId; // receives the value
const Screen2({Key key, this.userId}) : super(key: key);
#override
_Screen2State createState() => _Screen2State();
}
class _Screen2State extends State<Screen2> {
final ValueNotifier<bool> updateStatus = ValueNotifier(false);
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: Colors.blue,
body: ListView(
children: <Widget>[
Stack(
alignment: Alignment.topCenter,
children: <Widget>[
mainWidget(
updateStatus: updateStatus,
),
],
),
connectedStatusText(
updateStatus: updateStatus,
),
],
),
); // uses the value
}
}
class connectedStatusText extends StatefulWidget {
final ValueNotifier<bool> updateStatus;
connectedStatusText({
Key key,
this.updateStatus,
}) : super(key: key);
State<connectedStatusText> createState() {
return connectedStatus();
}
}
class connectedStatus extends State<connectedStatusText> {
String status = "IDLE";
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: /*
In order update widget we can use ValueListenableBuilder which updates the particular widget when the value changes (ValueNotifier value)
*/
ValueListenableBuilder(
valueListenable: widget.updateStatus,
builder: (context, snapshot, child) {
return RichText(
textAlign: TextAlign.center,
text: TextSpan(text: 'Status:', children: [
TextSpan(
text: (widget.updateStatus.value == true)
? "Active"
: status,
)
]),
);
}),
);
}
}
class mainWidget extends StatefulWidget {
final String userId; // receives the value
final ValueNotifier<bool> updateStatus;
mainWidget({
Key key,
this.userId,
this.updateStatus,
}) : super(key: key);
#override
_mainWidgetState createState() => _mainWidgetState();
}
class _mainWidgetState extends State<mainWidget> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
widget.updateStatus.value = !widget.updateStatus.value;
},
child: ValueListenableBuilder(
valueListenable: widget.updateStatus,
builder: (context, snapshot, child) {
return Text(snapshot.toString());
}));
// uses the value
}
}

Related

Flutter : setState outside

I'm new to Flutter and I just want to understand something about stateful widget. Here's a simple code that works perfectly just by switching the text color from red to blue when clicking on a button :
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Color myColor = Colors.red;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("My app")),
body: Column(
children: [
Text(
"Just a simple text",
style: TextStyle(color: myColor),
),
FloatingActionButton(
onPressed: () {
setState(() {
myColor =
(myColor == Colors.red) ? Colors.blue : Colors.red;
});
print(myColor);
},
child: Icon(Icons.home)),
],
));
}
}
My question is : if I get the column outside the stateful widget and call it as a component, how and where should I rewrite the setState function ? I begin with this code and I don't know how to continue :
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Color myColor = Colors.red;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("My app")),
body: HomePage());
}
}
Column HomePage()
{
return Column(
children: [
Text(
"Just a simple text",
style: TextStyle(color: myColor), // SHOULD I NOW INJECT myColor AS A PARAMETER OF HomePage ?
),
FloatingActionButton(
onPressed: () {print("WHERE TO PUT THE setState FUNCTION NOW ???")},
child: Icon(Icons.home)),
],
);
}
Your HomePage() is just a function that returns a Column, so you can just include it within the _MyWidgetState class to be able to access the state directly, and call the setState method, like that:
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Color myColor = Colors.red;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("My app")),
body: HomePage());
}
Column HomePage(){
return Column(
children: [
Text(
"Just a simple text",
style: TextStyle(color: myColor), // SHOULD I NOW INJECT myColor AS A PARAMETER OF HomePage ?
),
FloatingActionButton(
onPressed: () {
setState(() {
myColor = Colors.amber;
});
},
child: Icon(Icons.home)),
],
);
}
}
Here's a example class for how to pass data from one class to another class
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'main1.dart';
void main() {
runApp(MaterialApp(
home: Modalbtn(),
));
}
class Modalbtn extends StatefulWidget {
#override
_ModalbtnState createState() => _ModalbtnState();
}
class _ModalbtnState extends State<Modalbtn> {
String value = "0";
// Pass this method to the child page.
void _update(String newValue) {
setState(() => value = newValue);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
IconButton(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: 200,
child: Column(
children: [StatefulModalbtn(update: _update)],
),
);
});
},
icon: Icon(Icons.add),
iconSize: 20,
),
Text(
value,
style: TextStyle(fontSize: 40),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
class StatefulModalbtn extends StatelessWidget {
final ValueChanged<String> update;
StatefulModalbtn({required this.update});
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => update("100"), // Passing value to the parent widget.
child: Text('Update (in child)'),
);
}
}
If you insist of having the HomePage() function outside the class you could do this for example:
class MyWidget extends StatefulWidget {
MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Color myColor = Colors.red;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("My app")),
body: HomePage(myColor, changeColor));
}
void changeColor(Color color) {
setState((){
myColor = color;
});
}
}
Column HomePage(Color color, ValueSetter<Color> change)
{
return Column(
children: [
Text(
"Just a simple text",
style: TextStyle(color: color),
),
FloatingActionButton(
onPressed: () { change(Colors.blue);},
child: Icon(Icons.home)),
],
);
}

flutter dependOnInheritedWidgetOfExactType() returns null

I am trying to use InheritedWidget approach to share state down the Widget tree. For this, I am making a simple counter app. You can add, subtract, multiply or divide the counter however you like.
It's a small demo so best practices are not followed. The line with code context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>() seem to be null for some reason. When looking at samples and doc, it should find the MyInheritedWidget in the widget tree and return it. However, I am getting complaints from flutter tool that it is null. And, in deed it is null when asserted as well.
What is the reasoning here for failed return here? And, how do I need to do it such that I can receive the instance?
File: main.dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Counter(),
);
}
}
class Counter extends StatefulWidget {
const Counter({Key? key}) : super(key: key);
#override
CounterState createState() => CounterState();
}
class CounterState extends State<Counter> {
int counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Counter App'),
centerTitle: true,
),
body: MyInheritedWidget(
counterState: this,
child: Builder(
builder: (BuildContext innerContext) {
return CounterViewer(
counterState: MyInheritedWidget.of(context).counterState);
},
),
),
);
}
void addCounter(int value) {
setState(() {
counter++;
});
}
void subtractCounter(int value) {
setState(() {
counter--;
});
}
void multiplyCounter(int value) {
setState(() {
counter *= value;
});
}
void divideCounter(int value) {
setState(() {
counter = (counter / value).toInt();
});
}
}
class MyInheritedWidget extends InheritedWidget {
final CounterState counterState;
const MyInheritedWidget(
{Key? key, required Widget child, required this.counterState})
: super(key: key, child: child);
static MyInheritedWidget of(BuildContext context) {
final MyInheritedWidget? widget =
context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
assert(widget != null);
return widget!;
}
#override
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
return true;
}
}
class CounterViewer extends StatelessWidget {
final CounterState counterState;
const CounterViewer({Key? key, required this.counterState}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [
Container(
color: Colors.green.shade200,
width: MediaQuery.of(context).size.width,
height: 180,
child: Center(
child: Text(
'220',
style: TextStyle(
color: Colors.grey.shade50,
fontSize: 60,
fontWeight: FontWeight.bold,
),
),
),
),
Container(
color: Colors.grey.shade300,
padding: EdgeInsets.symmetric(vertical: 16),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {},
child: Text('Add'),
),
ElevatedButton(
onPressed: () {},
child: Text('Subtract'),
),
ElevatedButton(
onPressed: () {},
child: Text('Multiply'),
),
ElevatedButton(
onPressed: () {},
child: Text('Divide'),
),
],
),
)
],
);
}
}
Update: I seem to have passed the wrong context to the dependOnInheritedWidgetOfExactType() method. Changing from context to innerContext fixed the issue.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Counter App'),
centerTitle: true,
),
body: MyInheritedWidget(
counterState: this,
child: Builder(
builder: (BuildContext innerContext) {
return CounterViewer(
counterState: MyInheritedWidget.of(innerContext).counterState);
},
),
),
);
}

Flutter - How to Extract Widget with onPressed setState inside?

I want to Extract a Widget with onPressed setState inside but I get the Message "Reference to an enclosing class method cannot be extracted."
Is there a way to do that?
I would like to divide my code into different widgets so that it remains clear. Here is simplified an example of the code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Calculator(),
);
}
}
class Calculator extends StatefulWidget {
#override
_CalculatorState createState() => _CalculatorState();
}
class _CalculatorState extends State<Calculator> {
var myValue = 0;
void calculate() {
myValue = 12;
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: TextButton(
onPressed: () {
setState(() {
calculate();
});
},
child: Text(
'Button 001',
),
),
),
TextOutput(myValue: myValue),
],
),
);
}
}
class TextOutput extends StatelessWidget {
const TextOutput({
Key key,
#required this.myValue,
}) : super(key: key);
final int myValue;
#override
Widget build(BuildContext context) {
return Container(
child: Text(
myValue.toString(),
),
);
}
}
The part I want to extract into a separate widget:
Container(
child: TextButton(
onPressed: () {
setState(() {
calculate();
});
},
child: Text(
'Button 001',
),
),
),
Flutter offers VoidCallback and Function(x) (where x can be a different type) for callback-style events between child and parent widgets.
Simply You can pass Function onPressed; via constructor
Here is your Extracted Container widget:
class ExtractedContainer extends StatelessWidget {
final Function onPressed;
const ExtractedContainer({
Key key, #required this.onPressed,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: TextButton(
onPressed: () {
onPressed();
},
child: Text(
'Button 001',
),
),
);
}
}
And Here How to use it:
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ExtractedContainer(onPressed: calculate,),
TextOutput(myValue: myValue),
],
),
);
}
Your full code example
import 'package:flutter/material.dart';
class MyApp2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Calculator(),
);
}
}
class Calculator extends StatefulWidget {
#override
_CalculatorState createState() => _CalculatorState();
}
class _CalculatorState extends State<Calculator> {
var myValue = 0;
void calculate() {
myValue = 12;
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ExtractedContainer(onPressed: calculate,),
TextOutput(myValue: myValue),
],
),
);
}
}
class ExtractedContainer extends StatelessWidget {
final Function onPressed;
const ExtractedContainer({
Key key, #required this.onPressed,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: TextButton(
onPressed: () {
onPressed();
},
child: Text(
'Button 001',
),
),
);
}
}
class TextOutput extends StatelessWidget {
const TextOutput({
Key key,
#required this.myValue,
}) : super(key: key);
final int myValue;
#override
Widget build(BuildContext context) {
return Container(
child: Text(
myValue.toString(),
),
);
}
}
Setstate is related to the widget you want to refresh its state. If you extract it to another place, then setState refers to the state of the new widget.
In your case, the setState will only change the state of the container encapsulating your widget which you are trying to extract and its children, it doesn't migrate upward.
Unless, you look for the state of the widget you want, using exact type, and then trigger the state there, but this is overkill, a lot harder, requires more code, than what you currently have.
You can use VoidCallback on extract widget to get onPressed event
class MyContainer extends StatelessWidget {
final VoidCallback onTap;
const MyContainer({
Key? key,
required this.onTap,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: TextButton(
onPressed: onTap,
child: Text(
'Button 001',
),
),
);
}
}
And use like
MyContainer(
onTap: () {
print("tapped");
setState(() {
calculate();
});
},
),

How to invoke a rebuild of a stateless widget?

Context
I have two stateless widgets (pages): HomePage and DetailsPage. Obviously the application starts and launches the HomePage. There is a button the user can press to navigate to the DetailsPage with a Navigator.pop() button to navigate back to the HomePage.
I know when the DetailsPage is done being used with the .whenComplete() method. It is at this point I want to rebuild the HomePage widget.
Code
This is the minimum reproduction of my behavior.
main.dart
import 'package:example/home.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: HomePage());
}
}
home.dart
import 'package:example/details.dart';
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
static const name = 'Home Page';
const HomePage() : super();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: Text(name),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: DetailsPage.builder),
).whenComplete(() => print('Rebuild now.'));
},
),
),
);
}
}
details.dart
import 'package:flutter/material.dart';
class DetailsPage extends StatelessWidget {
static const name = 'Details Page';
static WidgetBuilder builder = (BuildContext _) => DetailsPage();
const DetailsPage();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(name),
MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: Text('Go Back'),
onPressed: () => Navigator.pop(context),
),
],
),
),
);
}
}
Question
How can I invoke a rebuild of this stateless widget (HomePage) at the .whenComplete() method callback?
You can force rebuild the widget tree as follows:
class RebuildController {
final GlobalKey rebuildKey = GlobalKey();
void rebuild() {
void rebuild(Element el) {
el.markNeedsBuild();
el.visitChildren(rebuild);
}
(rebuildKey.currentContext as Element).visitChildren(rebuild);
}
}
class RebuildWrapper extends StatelessWidget {
final RebuildController controller;
final Widget child;
const RebuildWrapper({Key? key, required this.controller, required this.child}) : super(key: key);
#override
Widget build(BuildContext context) => Container(
key: controller.rebuildKey,
child: child,
);
}
In your case,
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final RebuildController controller = RebuildController();
MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: RebuildWrapper(
controller: controller,
child: HomePage(
rebuildController: controller,
),
),
);
}
}
class HomePage extends StatelessWidget {
static const name = 'Home Page';
final RebuildController rebuildController;
const HomePage({Key? key, required this.rebuildController}) : super(key: key);
#override
Widget build(BuildContext context) {
print('Hello there!');
return Scaffold(
body: Center(
child: MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: const Text(name),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: DetailsPage.builder),
).whenComplete(rebuildController.rebuild);
},
),
),
);
}
}
class DetailsPage extends StatelessWidget {
static const name = 'Details Page';
static WidgetBuilder builder = (BuildContext _) => const DetailsPage();
const DetailsPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(name),
MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: const Text('Go Back'),
onPressed: () => Navigator.pop(context),
),
],
),
),
);
}
}
class RebuildController {
final GlobalKey rebuildKey = GlobalKey();
void rebuild() {
void rebuild(Element el) {
el.markNeedsBuild();
el.visitChildren(rebuild);
}
(rebuildKey.currentContext as Element).visitChildren(rebuild);
}
}
class RebuildWrapper extends StatelessWidget {
final RebuildController controller;
final Widget child;
const RebuildWrapper({Key? key, required this.controller, required this.child}) : super(key: key);
#override
Widget build(BuildContext context) => Container(
key: controller.rebuildKey,
child: child,
);
}
But it is unnatural to force rebuild stateless widgets as they are not supposed to be rebuilt. You should use stateful widget or other state management solutions so that your HomePage will only be updated on meaningful state change.
Source - this answer

statfulWidget with key concept

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?