There are some parts that I don't understand well about Flutter's Key - flutter

I practiced after watching a video explaining Flutter's Key.
https://api.flutter.dev/flutter/foundation/Key-class.html
This video shows an example of changing the location of a container with a specific color. (About 1 minute and 50 seconds)
In the video, the statefulwidget says that without a key, the location will not change.
But I wrote the example code myself and confirmed that it worked without giving a key to the stateful widget.
I think I wrote the example code wrong. Below is the code I wrote.
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',
home: KeyPractice(),
);
}
}
class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({#required this.color});
final Color color;
#override
_StatefulColorfulTileState createState() => _StatefulColorfulTileState();
}
class _StatefulColorfulTileState extends State<StatefulColorfulTile> {
#override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: widget.color,
);
}
}
class KeyPractice extends StatefulWidget {
#override
_KeyPracticeState createState() => _KeyPracticeState();
}
class _KeyPracticeState extends State<KeyPractice> {
List<Widget> tiles;
#override
void initState() {
super.initState();
tiles = [
StatefulColorfulTile(
color: Colors.blueAccent,
),
StatefulColorfulTile(
color: Colors.amber,
),
];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: tiles,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.autorenew,
),
onPressed: () {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
},
),
);
}
}
The above codes switch positions with each other.
What happens to the example of how the widget does not reposition each other when the stateful widget in the video does not assign keys?
And I understand that the key works only on the Stateful widget, does the Stateless use the key?
And I understood that Key only works with the Stateful widget. I wonder if the Stateless widget uses a key.
If I misunderstood, please teach me.

You're storing the color in the State of KeyPractice. The example they use stores it in the State of the child, in your case: StatefulColorfulTile.
Below is an example of the use of keys to correctly reposition widgets like you're trying to do. My example ended up very similar to what's shown on this medium article. Removing the keys here prevents the widgets from reflecting the color swap, but the use of the keys allows for the intended behavior.
import 'package:flutter/material.dart';
import 'dart:math';
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',
home: KeyPractice(),
);
}
}
class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({Key key}) : super(key: key);
#override
_StatefulColorfulTileState createState() => _StatefulColorfulTileState();
}
class _StatefulColorfulTileState extends State<StatefulColorfulTile> {
final Color myColor = UniqueColorGenerator.getColor();
#override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: myColor,
);
}
}
class KeyPractice extends StatefulWidget {
#override
_KeyPracticeState createState() => _KeyPracticeState();
}
class _KeyPracticeState extends State<KeyPractice> {
List<Widget> tiles;
#override
void initState() {
super.initState();
tiles = [
StatefulColorfulTile(key: UniqueKey()),
StatefulColorfulTile(key: UniqueKey()),
];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: tiles,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.autorenew,
),
onPressed: () {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
},
),
);
}
}
class UniqueColorGenerator {
static Random random = new Random();
static Color getColor() {
return Color.fromARGB(255, random.nextInt(255), random.nextInt(255), random.nextInt(255));
}
}

Related

How to change scaffold colour randomly on clicking separate file's gesturdetector button

I have made two files... one is main.dart and other is homescreen.dart. Homescreen is for scaffold body which is created separately. Now there is a button in home screen for changing colour of scaffold. How to do this?
The main purpose is to know access scaffold from other stateful widget class file...
main.dart
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(child: Scaffold(body: HomeScreen(),)),
);
}
}
homescreen.dart
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
//My query is to PLACE CODE HERE TO CHANGE SCAFFOLD COLOR ON CLICKING
},
child: Center(
child: Container(
color: Colors.red,
height: 60,
width: 200,
child: Center(child: Text('Change Color',)),
),
),
);
}
}
Try this:
main.dart
import 'package:flutter/material.dart';
import 'home.dart';
import 'dart:math' as math;
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Color _color = Colors.white;
void changeColor(){
setState(() {
_color = Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(child: Scaffold(
backgroundColor: _color,
body: HomeScreen(changeColor: changeColor,),)),
);
}
}
home.dart
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
VoidCallback? changeColor;
HomeScreen({Key? key, this.changeColor}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: changeColor,
child: Center(
child: Container(
color: Colors.red,
height: 60,
width: 200,
child: const Center(
child: Text(
'Change Color',
),
),
),
),
);
}
}
You can do it like this :
/// EDIT :
I edit it to get the Color random
import 'dart:math' as math;
class _MyAppState extends State<MyApp> {
Color _newColor = Colors.white; // variable with the color you want to change
final rnd = math.Random(); // random
Color getRandomColor() =>
Color(rnd.nextInt(0xffffffff)); // little function to get the color random
void _changeNewColor() { // function that you are going to send to yout HomeScreen
setState(() {
_newColor = getRandomColor();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: HomeScreen(change: _changeNewColor), // function
backgroundColor: _newColor, // here the variable
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({
Key? key,
this.change,
}) : super(key: key);
final Function()? change; // instance and get the funct
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: change,
child: Center(
child: Container(
color: Colors.red,
height: 60,
width: 200,
child: const Center(
child: Text(
'Change Color',
)),
),
),
);
}
}

Flutter - how to call child widget's method from parent

Let's say we have two stateful widgets.
ParentWidget(){
}
ChildWidget() {
someMethod(){
// some code, for example setState code
}
}
Now when I use the ChildWidget in ParentWidget, how do I call the someMethod()?
If you need call function on widget you can use:
context.findAncestorWidgetOfExactType<T>()
If you need call function on state of that widget you can use:
context.findRootAncestorStateOfType<T>();
read more at:
https://api.flutter.dev/flutter/widgets/BuildContext/findAncestorWidgetOfExactType.html
https://api.flutter.dev/flutter/widgets/BuildContext/findRootAncestorStateOfType.html
Here is way what I've used.
Make a GlobalKey instance
Pass the Globalkey as a Key parameter to child widget.
Call GlobalKey.currentState.method();
ParentWidget(){
GlobalKey<ChildWidgetState> globalKey = GlobalKey();
ChildWidget(key: globalKey);
...
globalKey.currentState.someMethod();
}
ChildWidget() {
ChildWidget({Key key}) : super(key: key);
someMethod(){
// some code, for example setState code
}
}
TestCode
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
GlobalKey<ChildWidgetState> globalKey = GlobalKey();
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
InkWell(
onTap: () {
globalKey.currentState.someMethod();
},
child: Text('ParentWidget'),
),
ChildWidget(key: globalKey),
],
);
}
}
class ChildWidget extends StatefulWidget {
ChildWidget({Key key}) : super(key: key);
#override
ChildWidgetState createState() => ChildWidgetState();
}
class ChildWidgetState extends State<ChildWidget> {
void someMethod() {
print('someMethod is called');
}
#override
Widget build(BuildContext context) {
return Text('childWidget');
}
}
edit: this approach works, but thanks #Andrija for pointing this out:
Just wanted to note - even though the answers below are good - you
should not be doing this in the first place. This is an anti-pattern
in flutter.

flutter change background on click

I need to change the background color by clicking on the screen to random, I can't figure out how to do it.
import 'package:flutter/widgets.dart';
import 'dart:math';
main() => runApp(
Directionality(
textDirection: TextDirection.ltr,
child: Container(
color: Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0),
child: MyApp(),
),
),
);
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: GestureDetector( // используется как обычный виджет
onTap: () { // одно из свойств GestureDetector
// Этот метод будет вызван, когда дочерний элемент будет нажат
print('You pressed me');
},
),
);
}
}
1. You need to make a StateFulWidget because you want to change the background when tapped.
2. You need to make a list of colors that can be set as the background color.
3. You need to add a variable which holds the current background color's index in the list of colors.
4. You need to change this variable when tapped to set a new background color.
Note: If you want to have random colors you can check the random_color package which is easy to use.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: "Title"),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
MyHomePageState createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
List<Color> _colors = [Colors.blue, Colors.red, Colors.green, Colors.yellow];
var _index = 0;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _colors[_index % _colors.length],
body: GestureDetector(
onTap: () {
setState(() {
_index++;
});
},
child: Stack(
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.transparent,
),
],
)
)
);
}
To do this you want to store the color as part of the state of the widget that you want. Then, when you detect a press, you can change the color and call setState to trigger a rebuild to show the new color. This involves modifying your code to use a StatefulWidget as I have done below.
The following code uses the exact same widgets as your original, with just modifications to make the necessary parts stateful.
import 'package:flutter/widgets.dart';
import 'dart:math';
main() => runApp(
Directionality(
textDirection: TextDirection.ltr,
child: MyApp(),
),
);
class MyApp extends StatefulWidget {//Changed to a `StatefulWidget`
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
//Store the color as a part of your `State`
Color color = Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
#override
Widget build(BuildContext context) {
return Container(
color: color,
child: Container(
child: GestureDetector(
onTap: () {
print('You pressed me');
//`setState` rebuilds your widget to show the new color
//It's not possible to use a `StatelessWidget` here
setState(() {
color = Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
});
},
),
),
);
}
}
Try this
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Home(),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
Color color;
#override
void initState() {
color = Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
color = Color((Random().nextDouble() * 0xFFFFFF).toInt())
.withOpacity(1.0);
});
},
child: Scaffold(
backgroundColor: color,
),
);
}
}

Call a setState of a statefull widget from the stateless widget

I have a stateless widget class that has a widget whose movements need to be tracked. I cannot keep this widget inside the stateful widgets as I don't want the state of this widget to be refreshed.
I have the following code.
import 'package:flutter/material.dart';
import 'package:control_pad/control_pad.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Column(
children: <Widget>[
Expanded(
child: JoystickView(
onDirectionChanged: (degree, direction) {
//Change the state here.
},
),
),
Expanded(
child: MyStateFull(),
),
],
),
);
}
}
class MyStateFull extends StatefulWidget {
#override
_MyStateFullState createState() => _MyStateFullState();
}
class _MyStateFullState extends State<MyStateFull> {
double degree = 10;
double direction = 10;
//Call this from the stateless Widget
void changedDirection(degree, direction) {
setState(() {
this.degree = degree;
this.direction = direction;
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Text(
"The degree Moved is $degree and the direction is $direction",
style: TextStyle(fontSize: 25, color: Colors.black),
),
);
}
}
This code produces the following output.
I want the direction and degree values to be changed as the joystick is moved.
Thank You.
I tried it myself and found the solution. This can be done using streams. I will post the code just in case someone needs it in the future.
import 'package:flutter/material.dart';
import 'package:control_pad/control_pad.dart';
class MyStateLess extends StatelessWidget {
StreamController<List<double>> _controller = StreamController<List<double>>();
GlobalKey<_MyStateFullState> statefulKey = new GlobalKey<_MyStateFullState>();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
JoystickView(
onDirectionChanged: (degree, direction) {
List<double> temp = new List<double>();
temp.add(degree);
temp.add(direction);
_controller.add(temp);
},
),
MyStateFull(stream: _controller.stream, key: statefulKey),
],
);
}
}
class MyStateFull extends StatefulWidget {
final Stream<List<double>> stream;
MyStateFull({Key key, #required this.stream}) : super(key: key);
#override
_MyStateFullState createState() => _MyStateFullState();
}
class _MyStateFullState extends State<MyStateFull> {
double _degree = 0.0;
double _direction = 0.0;
#override
void initState() {
super.initState();
widget.stream.listen((event) {
setState(() {
_degree = event[0];
_direction = event[1];
});
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Text("$_degree, $_direction"),
);
}
}

show/hide a widget without recreating it

Let's say I have 2 cards and one is shown on screen at a time. I have a button that replaces the current card with other cards. Now assume that there is some data on card 1 and some data on card 2 and I don't want to destroy the data on each of them or I don't want to rebuild any of them again.
I tried using Stack Widget and overlapping one on top of others with a boolean on the top card. The value of this boolean is reversed by calling setstate when the button is pressed. The issue is as soon as I press the button, the new card rebuilds all over again and then shown or initState is called again, which I don't want. Any Solution?
EDIT: Sample Code:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var toggleFlag = false;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: toggleFlag
? CustomWidget(color: Colors.blue)
: CustomWidget(color: Colors.red),
),
floatingActionButton: new FloatingActionButton(
onPressed: _toggleCard,
tooltip: 'Increment',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
void _toggleCard() {
setState(() {
toggleFlag = !toggleFlag;
});
}
}
class CustomWidget extends StatefulWidget {
var color;
CustomWidget({this.color});
#override
State<StatefulWidget> createState() {
return new MyState();
}
}
class MyState extends State<CustomWidget> {
#override //I don't want this to be called again and again
Widget build(BuildContext context) {
return new Container(
height: 100.0,
width: 100.0,
color: widget.color,
);
}
}
1-Solution:
You have an array of widgets like this
final widgetList[widget1(), widget2()]
int currentIndex = 0;
IndexedStack (
   index: currentIndex,
   children: widgetList,
 ));
2-Solution:
With the Stack widget
int currentIndex = 0;
Stack(
children: [
Offstage(
offstage: currentIndex != 0,
child: bodyList[0],
),
Offstage(
offstage: currentIndex != 1,
child: bodyList[1],
),
Offstage(
offstage: currentIndex != 2,
child: bodyList[2],
),
],
)
3-Solution:
You need to add this to your stateful widget state
AutomaticKeepAliveClientMixin <Widgetname> like this
class _WidgetState extends State <Widgetname> with AutomaticKeepAliveClientMixin <Widgetname> {
#override
   bool get wantKeepAlive => true;
}
just wrap that Widget inside a Visibility widget then set "maintainSate" to true
Visibility(
visible: toggleFlag,
maintainState: true,
child: const CustomWidget(),
)
Stateless widgets are always considered to be perishable. If you want to preserve state, use a StatefulWidget and a State subclass.