DEV Community

Tianya School
Tianya School

Posted on

In-Depth Exploration of Flutter Desktop Application Development A Comprehensive Analysis from Theory to Practice

Today, we’re diving into a trending topic in cross-platform development—Flutter for Desktop. Flutter, Google’s open-source UI toolkit, initially gained fame for mobile development, but its desktop support enables developers to create applications for Windows, macOS, and Linux using a single codebase. If you’ve used Flutter for mobile or web apps, you might be curious about its potential for desktop development.

Unique Advantages of Flutter Desktop Development

Flutter’s desktop support allows developers to build multi-platform applications with a single codebase, offering several advantages over traditional desktop frameworks like Electron or Qt:

  • Single Codebase: Supports mobile, web, and desktop (Windows, macOS, Linux), reducing redundant development.
  • High Performance: Dart compiles to native machine code, delivering near-native performance.
  • Rich UI Components: Flutter’s Widget system enables highly customizable UIs tailored to different platform styles.
  • Hot Reload: Real-time previews of code changes accelerate development and debugging.
  • Ecosystem Support: A robust plugin and community ecosystem simplifies integration with file systems, databases, and more.

Typical desktop development scenarios include:

  • Enterprise Tools: Internal management systems running across platforms.
  • Productivity Apps: Note-taking, task management, or editors.
  • Cross-Platform Extensions: Extending existing mobile apps to desktop.
  • Prototyping: Rapidly building MVPs (Minimum Viable Products).

We’ll demonstrate Flutter desktop development by building a task management application.


Setting Up the Development Environment

Before coding, you need to configure the Flutter desktop development environment. Below are the steps for Windows, macOS, and Linux.

Environment Requirements

  • Flutter SDK: Version 2.10 or higher (stable desktop support). Ref
  • IDE: Visual Studio Code or Android Studio with Flutter and Dart plugins installed.
  • Platform-Specific Tools:
    • Windows: Visual Studio 2022 (with “Desktop development with C++” workload). Ref
    • macOS: Xcode (latest version). Ref
    • Linux: GCC, G++, CMake, Ninja, GTK development libraries. Ref

Configuration Steps

  1. Install Flutter SDK:
    • Download the latest stable version from flutter.dev.
    • Extract to a suitable directory (e.g., C:\dev\flutter) and add flutter/bin to your environment variables. Ref
  2. Enable Desktop Support:

    flutter channel stable
    flutter upgrade
    flutter config --enable-windows-desktop
    flutter config --enable-macos-desktop
    flutter config --enable-linux-desktop
    
- Run `flutter doctor` to verify the environment and confirm desktop support is enabled. [Ref](https://docs.flutter.dev/platform-integration/desktop)
Enter fullscreen mode Exit fullscreen mode
  1. Verify Devices:

    flutter devices
    
- Should list target platforms (e.g., `windows`, `macos`, `linux`).
Enter fullscreen mode Exit fullscreen mode

Note: Build on the target platform (e.g., Windows for Windows apps, macOS for macOS apps). Ref


Creating a Task Management Application

We’ll build a task management desktop application with the following features:

  • Display a task list (supporting add, edit, delete).
  • Save tasks to a local file.
  • Adapt to desktop UI (window size, keyboard shortcuts).
  • Real-time updates (hot reload support).

Project Initialization

Create a new project:

flutter create task_manager
cd task_manager
flutter run -d windows
Enter fullscreen mode Exit fullscreen mode

Analysis:

  • flutter create generates a project supporting mobile, web, and desktop.
  • -d windows specifies running on Windows (replace with macos or linux as needed).

The default project includes lib/main.dart, which we’ll modify to implement task management.

Basic UI Implementation

Implement the task list and task addition UI:

// lib/main.dart
import 'package:flutter/material.dart';

void main() {
  runApp(const TaskManagerApp());
}

class TaskManagerApp extends StatelessWidget {
  const TaskManagerApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Task Manager',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const TaskListScreen(),
    );
  }
}

class TaskListScreen extends StatefulWidget {
  const TaskListScreen({Key? key}) : super(key: key);

  @override
  _TaskListScreenState createState() => _TaskListScreenState();
}

class _TaskListScreenState extends State<TaskListScreen> {
  final List<Task> _tasks = [];
  final TextEditingController _controller = TextEditingController();

  void _addTask(String title) {
    setState(() {
      _tasks.add(Task(id: _tasks.length, title: title));
    });
    _controller.clear();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Task Manager')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: const InputDecoration(labelText: 'New Task'),
                    onSubmitted: _addTask,
                  ),
                ),
                IconButton(
                  icon: const Icon(Icons.add),
                  onPressed: () => _addTask(_controller.text),
                ),
              ],
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: _tasks.length,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text(_tasks[index].title),
                  trailing: IconButton(
                    icon: const Icon(Icons.delete),
                    onPressed: () {
                      setState(() {
                        _tasks.removeAt(index);
                      });
                    },
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

class Task {
  final int id;
  final String title;

  Task({required this.id, required this.title});
}
Enter fullscreen mode Exit fullscreen mode

Code Analysis:

  • Structure: TaskManagerApp is the root Widget, using MaterialApp for Material Design styling.
  • State Management: TaskListScreen uses StatefulWidget, managing tasks via the _tasks list.
  • UI:
    • TextField and IconButton for adding tasks.
    • ListView.builder dynamically renders the task list with delete functionality.
  • Hot Reload: Press r to see UI updates in real-time. Ref

Run:

flutter run -d windows
Enter fullscreen mode Exit fullscreen mode
  • The app launches a window where you can add and delete tasks.

Advanced Feature Implementation

With the basic UI complete, we’ll add advanced features: local file storage, keyboard shortcuts, and window management.

Local File Storage

Use path_provider and dart:io to save tasks to a file:

flutter pub add path_provider
Enter fullscreen mode Exit fullscreen mode

Update the code:

// lib/main.dart
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';

class Task {
  final int id;
  final String title;

  Task({required this.id, required this.title});

  Map<String, dynamic> toJson() => {'id': id, 'title': title};
  factory Task.fromJson(Map<String, dynamic> json) => Task(id: json['id'], title: json['title']);
}

class _TaskListScreenState extends State<TaskListScreen> {
  final List<Task> _tasks = [];
  final TextEditingController _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    _loadTasks();
  }

  Future<String> get _localPath async {
    final directory = await getApplicationDocumentsDirectory();
    return directory.path;
  }

  Future<File> get _localFile async {
    final path = await _localPath;
    return File('$path/tasks.json');
  }

  Future<void> _loadTasks() async {
    try {
      final file = await _localFile;
      if (await file.exists()) {
        final contents = await file.readAsString();
        final List<dynamic> json = jsonDecode(contents);
        setState(() {
          _tasks.addAll(json.map((e) => Task.fromJson(e)).toList());
        });
      }
    } catch (e) {
      print('Error loading tasks: $e');
    }
  }

  Future<void> _saveTasks() async {
    try {
      final file = await _localFile;
      final json = jsonEncode(_tasks.map((e) => e.toJson()).toList());
      await file.writeAsString(json);
    } catch (e) {
      print('Error saving tasks: $e');
    }
  }

  void _addTask(String title) {
    if (title.isEmpty) return;
    setState(() {
      _tasks.add(Task(id: _tasks.length, title: title));
    });
    _controller.clear();
    _saveTasks();
  }

  void _deleteTask(int index) {
    setState(() {
      _tasks.removeAt(index);
    });
    _saveTasks();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Task Manager')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: const InputDecoration(labelText: 'New Task'),
                    onSubmitted: _addTask,
                  ),
                ),
                IconButton(
                  icon: const Icon(Icons.add),
                  onPressed: () => _addTask(_controller.text),
                ),
              ],
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: _tasks.length,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text(_tasks[index].title),
                  trailing: IconButton(
                    icon: const Icon(Icons.delete),
                    onPressed: () => _deleteTask(index),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Code Analysis:

  • Dependencies: path_provider retrieves the app’s documents directory; dart:io handles file operations.
  • Data Persistence:
    • _loadTasks reads tasks from tasks.json.
    • _saveTasks saves tasks as JSON.
  • JSON Serialization: Task class includes toJson and fromJson methods.
  • Error Handling: Catches file operation exceptions and logs errors.

Note: File operations access the local file system directly on desktop; mobile apps require additional permissions. Ref

Keyboard Shortcuts

Add keyboard shortcuts (e.g., Ctrl+S to save):

flutter pub add keyboard_shortcuts
Enter fullscreen mode Exit fullscreen mode

Update the code:

// lib/main.dart
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';

class _TaskListScreenState extends State<TaskListScreen> {
  // ... other code unchanged

  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: {
        LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyS): const SaveIntent(),
      },
      actions: {
        SaveIntent: CallbackAction<SaveIntent>(onInvoke: (intent) => _saveTasks()),
      },
      child: Scaffold(
        // ... rest of the code
      ),
    );
  }
}

class SaveIntent extends Intent {
  const SaveIntent();
}
Enter fullscreen mode Exit fullscreen mode

Code Analysis:

  • keyboard_shortcuts: Enables defining shortcuts like Ctrl+S.
  • Shortcuts: Flutter’s built-in Widget maps shortcuts to actions.
  • Desktop Experience: Shortcuts enhance desktop app interactivity. Ref

Window Management

Use window_manager to control window size and title:

flutter pub add window_manager
Enter fullscreen mode Exit fullscreen mode

Update the code:

// lib/main.dart
import 'package:window_manager/window_manager.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await windowManager.ensureInitialized();
  windowManager.setTitle('Task Manager');
  windowManager.setMinimumSize(const Size(800, 600));
  windowManager.setMaximumSize(const Size(1200, 800));
  runApp(const TaskManagerApp());
}
Enter fullscreen mode Exit fullscreen mode

Code Analysis:

  • window_manager: Sets window title and minimum/maximum sizes.
  • Async Initialization: Ensures windowManager initializes before the app starts.
  • Desktop Fit: Fixed window sizes enhance professionalism. Ref

Performance Optimization

Optimizing UI Rendering

  • ListView.builder: Already used for lazy loading to reduce memory usage.
  • const Widgets: Used in TaskManagerApp and Scaffold to minimize rebuilds.
  • RepaintBoundary: Add boundaries for complex Widgets like ListTile:
ListTile(
  title: Text(_tasks[index].title),
  trailing: RepaintBoundary(
    child: IconButton(
      icon: const Icon(Icons.delete),
      onPressed: () => _deleteTask(index),
    ),
  ),
)
Enter fullscreen mode Exit fullscreen mode

Analysis: RepaintBoundary isolates painting, reducing redraw costs.

Optimizing File Operations

  • Debouncing Saves: Frequent saves can degrade performance, so add debouncing:
// lib/main.dart
import 'dart:async';

class _TaskListScreenState extends State<TaskListScreen> {
  Timer? _debounce;

  Future<void> _saveTasks() async {
    if (_debounce?.isActive ?? false) _debounce!.cancel();
    _debounce = Timer(const Duration(milliseconds: 500), () async {
      try {
        final file = await _localFile;
        final json = jsonEncode(_tasks.map((e) => e.toJson()).toList());
        await file.writeAsString(json);
      } catch (e) {
        print('Error saving tasks: $e');
      }
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Analysis: 500ms debouncing reduces file write frequency, improving performance.

Cache Management

  • In-Memory Cache: _tasks list avoids frequent file reads.
  • Async Loading: _loadTasks runs asynchronously in initState, preventing UI blocking.

Summary

This article explored Flutter desktop development, from its advantages to building a task management app. We covered environment setup, UI design, local file storage, keyboard shortcuts, window management, and performance optimization, sharing lessons from common pitfalls. Flutter’s single codebase and high performance make it a powerful choice for desktop development, especially for cross-platform needs. Try running the example in VS Code and experiment with adding task editing or multi-window support!

Top comments (0)