hunspell_spell_check 0.3.4 copy "hunspell_spell_check: ^0.3.4" to clipboard
hunspell_spell_check: ^0.3.4 copied to clipboard

Hunspell-backed spell checking for Flutter desktop.

hunspell_spell_check #

Hunspell-backed spell checking for Flutter desktop (Windows, Linux, macOS), powered by a Rust FFI backend.

Flutter's built-in DefaultSpellCheckService only exists on Android and iOS. This package brings spell checking to desktop by implementing Flutter's standard spell check API on top of Hunspell dictionaries, so it plugs into regular TextFields — and it also exposes a low-level SpellChecker facade for custom editors (e.g. appflowy_editor).

Setup #

  1. Add the dependency:

    flutter pub add hunspell_spell_check
    
  2. Bundle Hunspell dictionary files (.aff + .dic) as assets in your app:

    flutter:
      assets:
        - assets/hunspell/german/
    

    Dictionaries can be obtained e.g. from LibreOffice dictionaries.

  3. A Rust toolchain is required to build the native backend (see rust/rust-toolchain.toml).

Usage with a standard TextField #

HunspellSpellCheckConfiguration extends Flutter's SpellCheckConfiguration and wires in a HunspellSpellCheckService (an implementation of Flutter's SpellCheckService):

import 'package:hunspell_spell_check/hunspell_spell_check.dart';

TextField(
  spellCheckConfiguration: HunspellSpellCheckConfiguration(
    options: const HunspellSpellCheckOptions(
      affPath: 'assets/hunspell/german/de_DE.aff',
      dicPath: 'assets/hunspell/german/de_DE.dic',
    ),
  ),
)

The service ignores the locale passed by the framework — the language is determined by the dictionary files you configure.

You can also use HunspellSpellCheckService directly wherever a SpellCheckService is accepted:

SpellCheckConfiguration(
  spellCheckService: HunspellSpellCheckService(
    options: const HunspellSpellCheckOptions(
      affPath: 'assets/hunspell/german/de_DE.aff',
      dicPath: 'assets/hunspell/german/de_DE.dic',
    ),
  ),
  misspelledTextStyle: TextField.materialMisspelledTextStyle,
)

Low-level API #

For custom editors, use the SpellChecker singleton directly:

await SpellChecker.instance.initialize(
  config: const HunspellSpellCheckOptions(
    affPath: 'assets/hunspell/german/de_DE.aff',
    dicPath: 'assets/hunspell/german/de_DE.dic',
  ),
);

final correct = await SpellChecker.instance.checkWord('Haus');
final suggestions = SpellChecker.instance.suggest('Hauss');

HunspellSpellCheckOptions also carries filtering rules (minWordLength, excludePatterns, checkOnlyCompletedWords, debounceDelay) and UI hints (suggestionIcon, highlightColor) consumed by editor integrations.

Ignoring words (custom dictionary) #

Words the user marks as correct (e.g. via an "Add to dictionary" action) can be added to a custom dictionary. They're then treated as correctly spelled by both checkWord/suggest and the SpellCheckService integration, and persisted across app restarts:

await SpellChecker.instance.initialize(
  config: const HunspellSpellCheckOptions(
    affPath: 'assets/hunspell/german/de_DE.aff',
    dicPath: 'assets/hunspell/german/de_DE.dic',
  ),
);

// "Flutterismus" is misspelled according to the dictionary...
await SpellChecker.instance.checkWord('Flutterismus'); // false

// ...until the user ignores it. The word is persisted, so it stays
// ignored across restarts too.
await SpellChecker.instance.addCustomWord('Flutterismus');
await SpellChecker.instance.checkWord('Flutterismus'); // true

// Currently ignored words, and how to un-ignore one:
SpellChecker.instance.customWords; // {'flutterismus'}
await SpellChecker.instance.removeCustomWord('Flutterismus');

By default custom words are persisted to a file in the app's support directory via FileCustomDictionaryStore. Pass your own CustomDictionaryStore implementation to initialize() to plug in a different backend (e.g. shared_preferences or a database):

await SpellChecker.instance.initialize(
  config: const HunspellSpellCheckOptions(
    affPath: 'assets/hunspell/german/de_DE.aff',
    dicPath: 'assets/hunspell/german/de_DE.dic',
  ),
  customDictionaryStore: MyCustomDictionaryStore(),
);

HunspellSpellCheckOptions.customWords can also be used to seed the dictionary with words known upfront (e.g. product or brand names).

Clearing the underline in a live TextField #

addCustomWord takes effect immediately for checkWord/suggest, but Flutter's EditableText only reruns spell check when the text content itself changes — it won't notice a word was added to the dictionary, so an already-underlined word stays underlined until the user edits the text. Call HunspellSpellCheckService.refreshSpellCheck right after adding the word to force Flutter to recheck immediately:

await SpellChecker.instance.addCustomWord(word);
HunspellSpellCheckService.refreshSpellCheck(myTextEditingController);

This makes a no-op edit (appending then removing a character) so Flutter detects a change and reruns spell check, then restores the original value so the visible text and cursor position are unaffected.

Native backend #

The engine is a small Rust cdylib (rust/) wrapping hunspell-rs, built automatically:

  • via Dart native-assets build hooks (hook/build.dart, using native_toolchain_rust) on Windows, Linux, and macOS, and
  • via the Windows ffi-plugin CMake integration (windows/CMakeLists.txt), which compiles the crate with cargo and bundles hunspell_backend.dll next to the executable.

Words are NFC-normalized before lookup so decomposed input (e.g. macOS dead-key umlauts) still matches dictionary entries.