A global key given to a StatefulWidget
can give us access to its methods from anywhere.
GlobalKey
example
WidgetA
below has a method login()
.
To access login()
from other widgets, instantiate WidgetA
with a GlobalKey
.
Then use that key to access WidgetA
state to call login()
.
The structure below is:
ExamplePage
WidgetA
LoginDialog
LoginDialog
will call WidgetA.login()
using the global key.
login()
will update the AppBar
in WidgetA
with the user name.
WidgetA
Here is the StatefulWidget
WidgetA
:
class WidgetA extends StatefulWidget {
const WidgetA({Key? key}) : super(key: key);
@override
State<WidgetA> createState() => WidgetAState();
static GlobalKey<WidgetAState> createKey() => GlobalKey<WidgetAState>();
}
class WidgetAState extends State<WidgetA> {
String loginStatus = 'Signed Out';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('$loginStatus'),
),
body: Center(
child: ElevatedButton(
child: Text('Login'),
onPressed: () =>
showDialog(context: context,
builder: (context) => LoginDialog()),
),
),
);
}
void login(String msg) {
setState(() {
loginStatus = msg;
});
}
}
Notes
- static method
createKey()
is a convenience method to create the type of global key we need, a GlobalKey
with a Type of WidgetAState
- when we instantiate
WidgetA
, we'll need to give it a key of type GlobalKey<WidgetAState>
GlobalKey<WidgetAState>
Here's the global key we'll create of type WidgetAState
, using the convenience static method createKey()
we added to WidgetA
class.
final widgetA = WidgetA.createKey();
To make accessing WidgetAState
cleaner, we can create an optional extension class on GlobalKey<WidgetAState>
types that gives us direct access to WidgetAState
:
extension WidgetAKeyExt on GlobalKey<WidgetAState> {
void login(String user) => currentState?.login(user);
}
This allows us to make this call:
widgetA.login()
instead of this:
widgetA.currentState?.login()
LoginDialog
This login dialog will access WidgetA's
login()
method using the global key.
class LoginDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Login'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
child: Text('Login'),
onPressed: () => widgetA.login('Billy'), // WidgetA method call
),
ElevatedButton(
child: Text('Logout'),
onPressed: () => widgetA.login('Signed Out'), // WidgetA method call
),
],
),
);
}
}
Entire Code Sample
Here's the entire code sample in a single block to copy/paste. Also includes another page WidgetB
which also accesses WidgetA
state through the global key.
When a login is perfomed in the LoginDialog
, the app bar in WidgetA
will reflect the loginStatus
state. That state is also accessible in WidgetB
using the global key.
import 'package:flutter/material.dart';
/// Share access to a widget's methods & state by using a GlobalKey.
/// Create a GlobalKey<T> using that widget's state class as T.
/// For example below we use GlobalKey<WidgetAState>.
///
/// Give that key to WidgetA's constructor / instantiation.
///
/// Then use that key from anywhere to call methods on WidgetAState.
/// Create the key for [WidgetAState] globally so it can be accessed across your app.
/// i.e. do this outside of a Widget, such as in your main.dart or in a state
/// management solution that you access globally, etc.
///
/// For readability/ease we've created a static method [createKey] on [WidgetA] to
/// instantiate the global key we need.
///
/// This could also just be `final widgetA = GlobalKey<WidgetAState>();
final widgetA = WidgetA.createKey(); // see static method inside WidgetA
/// Just an empty page/route to hold [WidgetA] instantiation with global key.
class ExampleGlobalKeyPage extends StatelessWidget {
const ExampleGlobalKeyPage();
@override
Widget build(BuildContext context) {
return WidgetA(key: widgetA);
}
}
/// [WidgetA] takes a key argument. We'll give it the global key we created so
/// we can access its state object from anywhere.
class WidgetA extends StatefulWidget {
const WidgetA({Key? key}) : super(key: key);
@override
State<WidgetA> createState() => WidgetAState();
/// Convenience static method to create the [WidgetAState] key to provide to [WidgetA].
/// By being specific with the [Key] class as "GlobalKey<WidgetAState>" instead
/// of just "Key", we can use create & use an extension class [WidgetAKeyExt]
/// to make accessing the widget state cleaner & easier. See that extension
/// class below.
static GlobalKey<WidgetAState> createKey() => GlobalKey<WidgetAState>();
}
/// This is [WidgetA]'s state object. By default it's private, but we'll
/// make it public by removing '_' from the name, so [_WidgetAState]
/// becomes [WidgetAState].
///
/// Its [login] method will be available anywhere using the global key we gave
/// [WidgetA] constructor.
///
class WidgetAState extends State<WidgetA> {
String loginStatus = 'Signed Out'; // this state can be changed with the GlobalKey
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('$loginStatus'),
actions: [
IconButton(icon: Icon(Icons.arrow_circle_right_rounded), onPressed: gotoB,)
],
),
body: Center(
child: ElevatedButton(
child: Text('Login'),
onPressed: () => showDialog(context: context,
builder: (context) => LoginDialog()),
),
),
);
}
/// This method can be called from [LoginDialog] widget using the GlobalKey
void login(String msg) {
setState(() {
loginStatus = msg;
});
}
void gotoB() => Navigator.push(context, MaterialPageRoute(builder: (c) => WidgetB()));
}
/// This extension class is not needed, but makes access to the [login] method
/// a little cleaner.
///
/// This extension method on GlobalKey<WidgetAState> performs the null check
/// for [currentState] existence before calling [login],
/// wherever/whenever we use [widgetA] key.
///
/// Also it uses "currentState" for us.
/// So our call `widgetA.currentState?.login()` is now `widgetA.login()`.
///
extension WidgetAKeyExt on GlobalKey<WidgetAState> {
void login(String user) => currentState?.login(user);
String get loginStatus => currentState?.loginStatus ?? 'Signed Out';
}
/// This Dialog is a separate route & widget. It makes calls to [WidgetA] methods
/// using the global key [widgetA].
class LoginDialog extends StatelessWidget {
/// For visibility you may want to pass the global key as a constructor arg
/// rather than using it directly. If so, you'd do something like:
//final GlobalKey<WidgetAState> widgetA;
//const LoginDialog({super.key, required this.widgetA});
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Login'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
child: Text('Login'),
onPressed: () => widgetA.login('Billy'), // WidgetA method call
),
ElevatedButton(
child: Text('Logout'),
onPressed: () => widgetA.login('Signed Out'), // WidgetA method call
),
],
),
);
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('${widgetA.loginStatus}'),
),
body: Center(
child: Text('Some other page accessing WidgetA'),
),
);
}
}