Flutter widgets are built using a modern framework that takes inspiration from React.
-
Use the Positioned widget on children of a Stack to position them relative to the top, right, bottom, or left edge of the stack.
-
Have a
uses-material-design: true
entry in the flutter section of yourpubspec.yaml
file to use the predefined set of Material icons.
name: my_app
flutter:
uses-material-design: true
-
Navigator, which manages a stack of widgets identified by strings, also known as “routes” and allows smooth transition between screens of the application.
-
Using the MaterialApp widget is optional but a good practice.
-
AppBar widget lets you pass in widgets for the leading widget, and the actions of the title widget.
-
Material is one of the 2 bundled designs included with Flutter. To create an iOS-centric design, check out the Cupertino components package, which has its own versions of CupertinoApp, and CupertinoNavigationBar.
The GestureDetector widget doesn't have a visual representation but instead detects gestures made by the user. When the user taps the Container, the GestureDetector
calls its onTap()
callback, in this case printing a message to the console. You can use GestureDetector
to detect a variety of input gestures, including taps, drags, and scales.
import 'package:flutter/material.dart';
class MyButton extends StatelessWidget {
const MyButton({super.key});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
print('MyButton was tapped!');
},
child: Container(
height: 50,
padding: const EdgeInsets.all(8),
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Colors.lightGreen[500],
),
child: const Center(
child: Text('Engage'),
),
),
);
}
}
-
Stateless widgets receive arguments from their parent widget, which they store in final member variables. When a widget is asked to build(), it uses these stored values to derive new arguments for the widgets it creates.
-
Stateful widgets know how to generate
State
objects, which are then used to hold state.
import 'package:flutter/material.dart';
class CounterDisplay extends StatelessWidget {
const CounterDisplay({required this.count, super.key});
final int count;
@override
Widget build(BuildContext context) {
return Text('Count: $count');
}
}
class CounterIncrementor extends StatelessWidget {
const CounterIncrementor({required this.onPressed, super.key});
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: const Text('Increment'),
);
}
}
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CounterIncrementor(onPressed: _increment),
const SizedBox(width: 16),
CounterDisplay(count: _counter),
],
);
}
}
// Main execution
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: Counter(),
),
),
),
);
}
-
StatefulWidget
andState
are separate objects:Widgets
are temporary objects, used to construct a presentation of the application in its current state.State
objects, on the other hand, are persistent between calls to build(), allowing them to remember information.
-
In Flutter, change notifications flow “up” the widget hierarchy by way of callbacks, while current state flows “down” to the stateless widgets that do presentation.
-
After calling createState() on the
StatefulWidget
, the framework inserts the new state object into the tree and then calls initState() on the state object. -
A subclass of State can override
initState
to do work that needs to happen just once, e.g., configure animations or to subscribe to platform services.- Implementations of
initState
are required to start by callingsuper.initState
.
- Implementations of
-
When a state object is no longer needed, the framework calls dispose() on the state object:
- Override the
dispose
function to do cleanup work, e.g., cancel timers or to unsubscribe from platform services. - Implementations of dispose typically end by calling
super.dispose
.
- Override the
-
Use keys to control which widgets the framework matches up with other widgets when a widget rebuilds. The framework requires that the two widgets have the same key as well as the same runtimeType.
-
Keys are most useful in widgets that build many instances of the same type of widget e.g., list of widgets instances (
ShoppingListItem
). -
Without keys, the first entry in the current build would always sync with the first entry in the previous build, even if, semantically, the first entry in the list just scrolled off screen and is no longer visible in the viewport.
-
By assigning each entry in the list a “semantic” key, the infinite list can be more efficient because the framework syncs entries with matching semantic keys and therefore similar (or identical) visual appearances -> Syncing the entries semantically means that state retained in stateful child widgets remains attached to the same semantic entry rather than the entry in the same numerical position in the viewport.
-
Use global keys to uniquely identify child widgets.
-
Global keys must be globally unique across the entire widget hierarchy, unlike local keys which need only be unique among siblings.
-
A global key can be used to retrieve the state associated with a widget.