Fuzzy Bolt

An advanced Fuzzy Search Algorithm with intelligent typo correction, adaptive ranking, and lightning-fast performance.

pub package License: BSD-3-Clause

Table of Contents

Why Fuzzy Bolt ??

I've found many packages that just purely does the fuzzy search job but haven't encountered that deals with typo/error in query automatically.

  • Uses Jaro–Winkler Distance for ranking the results.
  • Uses Levenshtein Distance to handle the typo errors in the query if any.
  • Leverage host's Isolate mechanism if the dataset becomes huge.
  • Automatically switch to non-isolate fallback mechanim for Web platform.
  • Allow developers to set their threshold on results for better accuracy.

Use Case Applications

  • Local Database Search: Perfect for running fuzzy queries directly on local datasets like SQLite, Hive, or Isar.

  • Post-API Result Search: Enhance your UX by adding an extra layer of fuzzy search after fetching data from remote APIs.

  • In-Memory State Search: Great for filtering and ranking results from app state (e.g in-memory lists, BLoC/Cubit states, Provider data, etc.).

  • Search Bars & Autocomplete Fields: Supercharge your TextField or SearchDelegate with typo-tolerant and intent-aware results.

  • Offline-First Applications: Helpful in apps that prioritize offline functionality and require local, fast search.

  • Data Cleaning & Record Linking: Use it for fuzzy matching and deduplication tasks (e.g., merging similar records in datasets).

  • Command Palette / Quick Actions Search: Perfect for developer tools or admin dashboards where users trigger commands via text input.

Installation

Add FuzzyBolt to your pubspec.yaml:

dependencies:
  fuzzy_bolt: <latest_version>  

Then, run:

flutter pub get

Normal Search Usage

API Reference

Future<List<String>> search({
  required List<String> dataset,
  required String query,
  double? strictThreshold,
  double? typoThreshold,
  bool? kIsWeb,
})

Example

import 'package:fuzzy_bolt/fuzzy_bolt.dart';

void main() async {
  final results = await FuzzyBolt().search(
  dataset: ["encyclopedia", "phenomenon", "philosophy", "psychology"],
  query: "phsychology", // Typo: "phsychology" instead of "psychology"
  strictThreshold: 0.8,
  typoThreshold: 0.7,
  kIsWeb: false,
);

  results.map((e) => print(e)).toList();
}

Output Example:

psychology 

philosophy 

Normal Search with Ranks

API Reference

Future<List<Map<String, dynamic>>> searchWithRanks({
  required List<String> dataset,
  required String query,
  double? strictThreshold,
  double? typoThreshold,
  bool? kIsWeb,
  Function(Object, StackTrace)? onError,
})

Example

import 'package:fuzzy_bolt/fuzzy_bolt.dart';

void main() async {
  final results = await FuzzyBolt().searchWithRanks(
    dataset: ["encyclopedia", "phenomenon", "philosophy", "psychology"],
    query: "phsychology", // Typo: "phsychology" instead of "psychology"
    strictThreshold: 0.8,
    typoThreshold: 0.7,
    kIsWeb: false,
    Function(Object, StackTrace)? onError,
  );

  print("Results with ranks:");
  for (var result in results) {
    print("${result['value']} (Score: ${result['rank']})");
  }
}

Output Example:

psychology (Score: 0.92)  βœ…  (Fixes minor spelling mistake)
philosophy (Score: 0.75)  ❌  (Less relevant but somewhat similar)

API Reference

Stream<List<String>> streamSearch({
    required List<String> dataset,
    required Stream<String> query,
    double? strictThreshold,
    double? typoThreshold,
    bool? kIsWeb,
    Function(Object, StackTrace)? onError,
  });

Example

import 'package:fuzzy_bolt/fuzzy_bolt.dart';

void main() async {
  final queryController = StreamController<String>();
  final searchStream = fuzzyBolt.streamSearch(
    dataset: ["apple", "banana", "berry", "grape", "pineapple"],
    query: queryController.stream,
    Function(Object, StackTrace)? onError,
  );

  searchStream.listen((results) {
    print(results);
  });

  queryController.add("b");
  queryController.add("be");
  queryController.add("ber");
  queryController.add("berr");
  queryController.add("berry");
}

Output Example:

πŸš€ Running Stream-Based Search...

⌨️ Typing: 'b'
πŸ”„ Stream Update:
   πŸ”Ή banana (Score: 0.750)
   πŸ”Ή blueberry (Score: 0.733)
   πŸ”Ή blackberry (Score: 0.730)

⌨️ Typing: 'be'
πŸ”„ Stream Update:
   πŸ”Ή blueberry (Score: 0.767)

⌨️ Typing: 'ber'
πŸ”„ Stream Update:
   πŸ”Ή blueberry (Score: 0.667)
   πŸ”Ή tangerine (Score: 0.630)
   πŸ”Ή watermelon (Score: 0.622)
   πŸ”Ή pomegranate (Score: 0.616)

⌨️ Typing: 'berr'
πŸ”„ Stream Update:
   πŸ”Ή blueberry (Score: 0.725)
   πŸ”Ή blackberry (Score: 0.610)

⌨️ Typing: 'berry'
πŸ”„ Stream Update:
   πŸ”Ή blueberry (Score: 0.680)
   πŸ”Ή raspberry (Score: 0.444)
🏁 Stream-based search completed.

Stream-Based Search with Ranks

API Reference

Stream<List<Map<String, dynamic>>> streamSearchWithRanks({
    required List<String> dataset,
    required Stream<String> query,
    double? strictThreshold,
    double? typoThreshold,
    bool? kIsWeb,
    Function(Object, StackTrace)? onError,
  });

Example

import 'package:fuzzy_bolt/fuzzy_bolt.dart';

void main() async {
  final queryController = StreamController<String>();
  final searchStream = fuzzyBolt.streamSearchWithRanks(
    dataset: ["apple", "banana", "berry", "grape", "pineapple"],
    query: queryController.stream,
  );

  searchStream.listen((results) {
    print("Results with ranks:");
    for (var result in results) {
      print("${result['value']} (Score: ${result['rank']})");
    }
  });

  queryController.add("b");
  queryController.add("be");
  queryController.add("ber");
  queryController.add("berr");
  queryController.add("berry");
}

Output Example:

πŸš€ Running Stream-Based Search with Ranks...

⌨️ Typing: 'b'
πŸ”„ Stream Update:
   πŸ”Ή banana (Score: 0.750)
   πŸ”Ή blueberry (Score: 0.733)
   πŸ”Ή blackberry (Score: 0.730)

⌨️ Typing: 'be'
πŸ”„ Stream Update:
   πŸ”Ή blueberry (Score: 0.767)

⌨️ Typing: 'ber'
πŸ”„ Stream Update:
   πŸ”Ή blueberry (Score: 0.667)
   πŸ”Ή blackberry (Score: 0.610)

⌨️ Typing: 'berry'
πŸ”„ Stream Update:
   πŸ”Ή blueberry (Score: 0.680)
   πŸ”Ή raspberry (Score: 0.444)

Platform Support

Platform Supported
Android βœ… Yes
iOS βœ… Yes
macOS βœ… Yes
Windows βœ… Yes
Linux βœ… Yes
Web βœ… Yes

Web support?
I've added fallback mechanism to use search locally without the help of Isolate mechanism since Flutter web do not support Isolates...

Note on streamSearch Stability

The onError callback in streamSearch is designed to handle errors gracefully. However, in certain edge cases (e.g., invalid datasets or rapidly changing queries), the behavior may be inconsistent. It is recommended to test thoroughly for your specific use case and handle errors at the application level if needed.

Running Tests

To run tests, use:

dart test
test/fuzzy_bolt_test.dart