What exactly are GlobalKeys and keys in flutter? - flutter

I have tried to learn this concept for over a month and I just can't seem to figure out what exactly is GlobalKeys and various types of keys in simpler terms, Is it just for form validator or does it serve more purpose,There aren't clear basic example of this in youtube too or perhaps may be there is a book somewhere ?
Here in basic flutter app
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: 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({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: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
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),
),
);
}
}
Here there are lines like
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
\\\\
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
What does Key /super keyword have to do with it or how do we use it?

I found this very helpful: https://medium.com/flutter/keys-what-are-they-good-for-13cb51742e7d
I found two uses for GlobalKeys (I'm sure there's more):
First one: this is what the article above discusses: how to link a state object to a widget. I'm going to oversimplify few things here.
Normally, flutter will not use Global Keys. When you create a stateful widget, two object get created: a widget, and it's state. The idea is that the widget itself will be destroyed at the end of the build (or after it is painted on the screen). Once you initiate the build again (through setState() for exmaple) - a widget will be recreated.
Of course - you want your state object to persist between the changes - this is where you store your data after all. And you want Flutter to link back your state object to newly created Widget object instance.
Most of the time, this is an easy thing for Flutter - it will just find the same widget in the same position, link it to it's state object and it's done.
In case your widget moves around in the next build - this will not work, and new state object will be created and you will lose your state.
Take a look at this modified Flutter Counter app - I moved the counter into a separate Widget, but also linked it back to the the original _MyHomePageState. Each time we rebuild the main widget we will flip the order of the button and text box in the column - and you will see that the count never changes: it always show zero.
Since the widget changed the position, it's state is dropped and new one created.
You can run this in dartpad quickly:
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _key=GlobalKey();
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: [
if (_counter.isOdd) Counter(onPressed: _incrementCounter),
const Text(
'You have pushed the button this many times:',
),
if (_counter.isEven) Counter(onPressed: _incrementCounter),
],
),
),
);
}
}
class Counter extends StatefulWidget {
final VoidCallback onPressed;
const Counter({Key? key, required this.onPressed}) : super(key: key);
#override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
widget.onPressed();
}
#override
Widget build(BuildContext context) {
return Column(children: [
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
ElevatedButton(
style:
ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
onPressed: _incrementCounter,
child: const Text('Increment'),
)
]);
}
}
Now simply by adding a GlobalKey to Counter widget (in both calls), we help flutter link the state object. Note that we created the _key value once in the state object - since we want to pass the same key each time (otherwise what's the point...)
Counter(onPressed: _incrementCounter, key: _key),
And after this change, even when Widget changes the place and order on screen - your state is kept.
Now you would ask: why wouldn't Flutter do this by default for all the Widgets? Turns out - GlobalKeys are very expensive to maintain, and if you want to render 60 frames per second (16ms each frame) - you want to optimize every single step. By 'no keys by default' approach, everything is optimized, and works in large majority of the cases - but in few situations when you need global keys - you need to learn a little bit about it, and it is very simple to use.
Second use I found for it: you need global key to find the your exact Widget position on screen after it renders. Widget itself will not know where it will end up, you only know this after the rendering is done. You need this if you want to do custom animation or something like that.
This is a simple function that will tell you where your widget is:
Rect getRectFromKey(GlobalKey key) {
RenderBox renderBox = (key.currentContext!.findRenderObject())! as RenderBox;
var targetPosition = renderBox.localToGlobal(Offset.zero);
var targetSize = renderBox.size;
// A Rect can be created with one its constructors or from an Offset and a Size using the & operator:
Rect rect = targetPosition & targetSize;
return rect;
}

Multiple widgets of the same type and at the same level in a widget tree may not update as expected unless they have unique keys, given that these widgets hold some state.
Explicitly setting a key to a widget helps Flutter understand which widget it needs to update when state changes.
keys also store and restore the current scroll position in a list of widgets.
Global key usually use to change parent widget from any portion of app considering state unchanged. Gobal keys are broad than keys in short.

Global keys are for Form validations. Other than global keys there is value,object,and unique key in Flutter. You get clear idea about all this through below link.
Keys in Flutter

Related

How to interact with a child Stateful Widget from a parent Stateless Widget?

Context :
Say I have a custom DisplayElapsedTime Widget which is Stateful and holds a Text Widget. With a Timer, every 1sec, the text value is updated with the current elapsed time given by a Stopwatch.
And now, say I have a page which is Stateless and has the DisplayElapsedTime Widget as a child.
What I would like to do :
On the click of a "Start" button in my page, I would like to start the DisplayElapsedTime (which means starting the Stopwatch and the Timer).
From my page, I would also like to have access to the elapsed time value of the Stopwatch "whenever I want".
Why I am having a hard time :
So far (see: I use Stateless Widget alongside BLoC almost everytime. Am I wrong?), I have almost always worked with Stateless Widget alongside the pattern BLoC and never used Stateful. Currently, having extremely long and complex Widgets, I am starting to sense the "limits" of not using the better of the two worlds. But I don't quite fully understand how the Widgets should be interacting between one another.
I really cannot find the solution to my problem anywhere (or am really bad at searching). However, surely, I cannot be the first person to want to have "control" over a Stateful Widget from a Stateless Widget, right ?
Thank you so much in advance for any help.
If I understand your question correctly, let me try to explain this using the most familiar app of all time, the beginning counter app.
This snippet contains a single StatefulWidget that controls its ability to rebuild using its setState method _incrementCounter. So, the value is incremented and the widget is rebuilt whenever the StatefulWidget calls the setState method inside itself.
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 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),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
A StatefulWidget can fully rebuild itself, and when doing so, it may also rebuild all its children downstream of it in the widget tree (but not always, as const widgets are not rebuilt). To get another widget to rebuild a parent widget (upstream of it in the widget tree), you need to have that StatefulWidget's setState function. This can be done using a callback function. A callback function is made by the parent widget and passed to a child widget. So, in the following example, I have made a StatelessWidget with a button, which controls its parent widget because it calls its parent's callback function; notice that I give:
ExampleStlessWidget(counter: _counter, fx: _incrementCounter),
and not:
ExampleStlessWidget(counter: _counter, fx: _incrementCounter()),
Passing _incrementCounter() with the parenthesis calls it at the moment it is passed, while _incrementCounter allows it to be called downstream in the widget tree.
Use the callback function in the child widget by calling it anywhere (notice the parentheses).
onPressed: () {
fx();
},
Here is the new code
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 Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ExampleStlessWidget(counter: _counter, fx: _incrementCounter),
);
}
}
class ExampleStlessWidget extends StatelessWidget {
const ExampleStlessWidget({
super.key,
required int counter,
required this.fx,
}) : _counter = counter;
final int _counter;
final Function fx;
#override
Widget build(BuildContext context) {
return Column(
children: [
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,
),
],
),
),
ElevatedButton(
onPressed: () {
fx();
},
child: const Text('Click me'),
),
],
);
}
}
A bloc involves an inherited widget, which allows for monitoring the state throughout the widget tree and rebuilds widgets depending on that state. So, a bloc doesn't need a StatefulWidget to change UI. It would help if you did not look at one tool's ability to rebuild widgets as bad or good. It would be best to look at StatefulWidgets and BLoC as different tools for different jobs.
I hope this helps. Happy coding.

Process of developing a custom widget in Flutter

I am learning Android app development using Dart/Flutter and I am trying to understand the general process of developing a custom widget for an app. For example, if I need a widget that has a TextField, an Image, and a checkbox, how do I test that widget individually?
I mean, there is no app to put that widget in as of now, so how do I "execute" it to see if that widget is getting laid out correctly and working properly as expected?
In the Java Swing world, I would just put a main method in my component class. In that main method, I would create a frame or something and add that component to that frame. Then run that class directly. That way, I can basically fine tune the component without worrying about running the actual application, and without having to go through the whole app flow to reach that component and check how it looks.
Is it similar in Flutter app as well? Create a dummy app with the widget as the only "screen" and then execute that dummy app?
There is an excellent online tool called "Dart Pad", the link here should get you started on basic boilerplate code that is the beginnings of your app.
You can then proceed to create a custom widget, I'll try and replicate your example widget below:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const Center(
child: MyStatefulWidget(),
),
),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool isChecked = false;
#override
Widget build(BuildContext context) {
Color getColor(Set<MaterialState> states) {
const Set<MaterialState> interactiveStates = <MaterialState>{
MaterialState.pressed,
MaterialState.hovered,
MaterialState.focused,
};
if (states.any(interactiveStates.contains)) {
return Colors.blue;
}
return Colors.red;
}
return Container(
child: Column(
children: [
Image.network("https://icatcare.org/app/uploads/2018/07/Thinking-of-getting-a-cat.png"),
TextField(decoration: InputDecoration(hintText: "Type something")),
Checkbox(
checkColor: Colors.white,
fillColor: MaterialStateProperty.resolveWith(getColor),
value: isChecked,
onChanged: (bool? value) {
setState(
() {
isChecked = value!;
},
);
},
),
],
),
);
}
}

Is loosing TextField text due to app focus out an expected behaviour?

I've created the simplest app: it's just a default project with added TextController and TextField. Nothing more. Here is a code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#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;
final TextEditingController _controller = new TextEditingController();
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>[
TextField(controller: _controller,),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
The actions I'm doing: tap on the text field, enter several symbols from the onscreen keyboard without committing, then press the switch app button. After that, I see the list of running apps with the current flutter app with the entered text. Then I click on the flutter app and entered text disappears.
I'm expecting that it's wrong behaviour. The text should not disappear.
Am I wrong? Or I have missed something? I'm new to flutter and everything here is very strange and it conflicts with practically all my previous experience.
yes it's expected because when you enter your widget it's state gets build, and you declared the variable in state class, so every time the state is build the _controller will be assigned to a new instance of TextEditingController and the previous instance of TextEditingController(which had your value inside of it) will be available for garbage collection.

Flutter - Pass state change function to child widget and update state

I am new to flutter and building a sample app to learn it. In the above screenshot, I have created multiple widgets. My main widget contains the following widget.
Boy Girl Selector
Common Card
CounterButton (Plus or Minus)
Calculate Button
My main widget has two counter - age & weight.
CommonCard has below property :
incrementFunction() : I am setting this value from MainWidget as below.
decrementFunction()
ageIncrement() {
setState(() {
age++;
});
}
ageDecrement() {
setState(() {
age--;
});
}
value : age declared in main widget is passed to this value.
CounterButton has below property.
onPressed: increment or decrement function from parent widget is passed here through card widget.
If I keep whole code in main widget then it is working properly. But if I create multiple widget and pass increment and decrement function as argument in child widget onPressed on plus and minus is not working propely. Please share your thoughts. I am missing some fundamental of communication between child and parent widget.
There are different ways to achieve what you like as there are a couple of different state management techniques such as Dependency Injection, ChangeNotifier, BLoC, and so on (search for Flutter State Management for more details).
Here's an example of how you can achieve this on the famous counter example. This example is using dependency injection (we are passing the increment function to the a child widget as callback function). You can copy the code and past it on DartPad to quickly test it and see how it works:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
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',
style: Theme.of(context).textTheme.display1,
),
SizedBox(height: 50),
MySecondButton(secondButtonIncrement: incrementCounter),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class MySecondButton extends StatelessWidget {
MySecondButton({Key key, this.secondButtonIncrement}) : super(key: key);
final VoidCallback secondButtonIncrement;
#override
Widget build(BuildContext context) {
return FlatButton(
child: Text("Second Button"),
onPressed: () {
secondButtonIncrement();
},
color: Colors.blue);
}
}
I hope that helps.

Stateful Widget child is not updated

There are two stateful widgets.
The state of MyHomePage contains the counter.
The MyHomePage wraps content in a second stateful widget SubPage.
The SubPage has a child widget with data from the MyHomePage.
To clarify the problem, the first textwidget which is inside of the SubPage child doesn't update when the counter changes.
The textwidget outside of the SubPage increments as expected.
What do we have to do if we want the content of the inner stateful widget updated?
We have to use a stateful widget there. In the real application this widget has a real use-case.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#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> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: Column(
children: <Widget>[
SubPage(
child: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'You have pushed the button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class SubPage extends StatefulWidget {
SubPage({Key key, this.child}) : super(key: key);
final Widget child;
#override
SubPageState createState() => new SubPageState(child);
}
class SubPageState extends State<SubPage> {
final Widget child;
SubPageState(this.child);
#override
Widget build(BuildContext context) {
print("subpage build");
return this.child;
}
}
You don't have to set child as field of state. Actually it is cause of this bug. Here working code
class SubPage extends StatefulWidget {
SubPage({Key key, this.child}) : super(key: key);
final Widget child;
#override
SubPageState createState() => new SubPageState();
}
class SubPageState extends State<SubPage> {
#override
Widget build(BuildContext context) {
return widget.child;
}
}