Flutter for Windows Desktop: Getting Started
Learn how to set up a development environment and create a simple Flutter calculator app for Windows Desktop. By Karol Wrótniak.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Flutter for Windows Desktop: Getting Started
20 mins
- Getting Started
- Installing Visual Studio
- Enabling Desktop Support in Flutter
- Creating the Project
- Developing the Button
- Developing the Grid of Buttons
- Building the Business Logic
- Developing the Calculation Engine
- Binding UI with Business Logic
- Developing Features for Desktop
- Supporting Mouse Input
- Supporting Keyboard Input
- Handling Keystrokes
- Handling Key Events
- Managing Window Size
- Preventing Window Maximization
- Integrating with System Clipboard
- Polishing the Project
- Where to Go From Here
Binding UI with Business Logic
To connect the logic and UI, inject the Calculator
into a CalculatorBody
:
final _calculator = Calculator();
Prepend the GridView
in a column with ValueListenableBuilder
. Now the calculator has the ability to display the current state.
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 10),
child: ValueListenableBuilder<String>(
valueListenable: _calculator.displayNotifier,
//1
builder: (_, String value, __) => Text(
value,
//2
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Color(0xFF158443), //green
),
),
),
),
Note important code fragments:
- The
builder
callback fires on eachdisplayNotifier
change. - The
ellipsis
overflow causes the &hellip to display if the text is too long.
Go back to the build
method of a CalculatorBody
and bind the actions to the buttons:
Tile('7', _calculator.appendDigit),
Tile('7', _calculator.appendDigit),
//rest of the digits
Tile('+', (_) => _calculator.appendOperator(Operator.plus)),
Tile('-', (_) => _calculator.appendOperator(Operator.minus)),
Now you have wired all the actions but the clear AC (I’ll return to this later in the article).
Finally, create the entry point of the app in a main.dart
file. After that, you’re able to run the app (press Shift+F10 or a green play button in Android Studio).
Future<void> main() async {
runApp(const FCalcApp());
}
class FCalcApp extends StatelessWidget {
const FCalcApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) =>
MaterialApp(home: Scaffold(body: CalculatorBody()));
}
As a result, you have the simplest, working calculator:
Developing Features for Desktop
In the current state, the app works on desktops, just like on mobile platforms. On mobile, however, the default input device is a touchscreen; on desktop, it’s a keyboard and a mouse. You can also connect the keyboard and mouse to smartphones via USB-OTG or Bluetooth. Flutter supports them out of the box.
Supporting Mouse Input
Flutter treats left-button mouse clicks like taps on a touchscreen. You don’t need to do anything special to handle clicks. The onTap
and similar callbacks of various widgets will work. But, a feature is visible only when using a mouse. Look what happens with the color if you drag a pointer on the button:
That’s a hover event. The widgets from a standard Flutter library such as an InkWell support the hovers. If you’re implementing your own widgets or involving raw gesture detection, it should also handle hover events. If you need to track the pointer movement, use a special MouseRegion class.
Supporting Keyboard Input
There are three kinds of keyboard input in Flutter:
- Entering editable text in text fields.
- Focus movement, e.g., Tab to move forward and activate the focused element such as Enter.
- Direct keystrokes (not related to editable text input).
In the case of text fields such as TextField), Flutter handles all the low-level work related to keyboard input for you. Users can enter or paste text from the clipboard using the standard system key shortcuts such as Ctrl+C on Windows.
The focus is important in mobile and desktop apps. On the latter, users can move it to the adjacent focusable elements using Tab and Shift+Tab. On mobile, there can be an IME action on a soft keyboard. Also on desktop, the focused element gains the hovered state.
Handling Keystrokes
To intercept arbitrary keystrokes, use the KeyboardListner or Focus widgets. Also, the receiver widget has to be in the focused state (that one related to hover). The entire focus system in Flutter is a broad and advanced topic. You can read more in the official Understanding Flutter’s focus system documentation. The simplest solution is a Focus
widget. Add it as the outermost node in the CalculatorBody
(in a calculator_body.dart
file):
@override
Widget build(BuildContext context) => Focus(
autofocus: true,
onKey: _onKey,
child: Column(...), //existing code
);
KeyEventResult _onKey(FocusNode node, RawKeyEvent event) {
//TODO handle supported keys
return KeyEventResult.ignored;
}
Note the autofocus: true
, the widget will get focus without clicking it or pressing Tab. The onKey
callback has to return the status. Use KeyEventResult.handled
when you don’t want the default system behavior to happen. If you’re not interested in the particular keystroke, then return KeyEventResult.ignored
. The other consumers will receive it then. Create extension functions on RawKeyEvent
in the new keycodes.dart
file. Such extensions allow simplifying the code inside the onKey
callback.
import 'package:flutter/services.dart';
const _deleteKeyId = 0x10000007F;
const _backspaceKeyId = 0x100000008;
const _cKeyId = 0x00000063;
const _vKeyId = 0x00000076;
extension KeyCodes on RawKeyEvent {
bool isClear() =>
logicalKey.keyId == _deleteKeyId || logicalKey.keyId == _backspaceKeyId;
bool isDigit() {
//1
final codeUnit = character?.codeUnitAt(0) ?? 0;
return codeUnit >= 0x30 && codeUnit <= 0x39;
}
bool isCopy() =>
//2
(isMetaPressed || isControlPressed) && logicalKey.keyId == _cKeyId;
bool isPaste() =>
(isMetaPressed || isControlPressed) && logicalKey.keyId == _vKeyId;
}
Here are key points of the code:
- Extract the ASCII code of the character key (fall back to 0 for non-character keys).
- Check Control and Meta (aka Cmd) keys so macOS keymap will also work.
Handling Key Events
In the case of keys with a visual representation such as digits, you can use the character property to get the actual keystroke value. For other keys such as Backspace, use the logicalKey. Note the callback fires on both key press and release for each key. Although, a character
isn’t null only on press (down) events. For modifier keys, Shift, Control, Alt, Cmd there are special flags such as isControlPressed
. When using a Focus widget, you have to detect the key combinations for each operating system separately. There isn’t a built-in method for detecting the paste action. On Windows and Linux, it’s usually Ctrl+C (but also Ctrl+Insert), on macOS, it’s Cmd+C.
Use extensions you created before in _onKey
callback inside calculator_body.dart
file:
KeyEventResult _onKey(FocusNode node, RawKeyEvent event) {
if (event.isDigit()) {
_calculator.appendDigit(event.character!);
return KeyEventResult.handled;
} else if (event.isClear()) {
//TODO handle erasing
return KeyEventResult.handled;
} else if (event.character == '+') {
_calculator.appendOperator(Operator.plus);
return KeyEventResult.handled;
} else if (event.character == '-') {
_calculator.appendOperator(Operator.minus);
return KeyEventResult.handled;
} else if (event.isCopy()) {
//TODO handle copying from clipboard
return KeyEventResult.handled;
} else if (event.isPaste()) {
//TODO handle pasing from clipboard
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
}
Let’s run the app (Shift+F10) and watch it work! From now, you can enter digits and operands using the physical keyboard: