Fuzzy Bolt
An advanced Fuzzy Search Algorithm with intelligent typo correction, adaptive ranking, and lightning-fast performance.
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)
Stream Based Search
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