dorar_hadith 0.5.0
dorar_hadith: ^0.5.0 copied to clipboard
A comprehensive Dart library for accessing Dorar.net hadith database. Search hadiths, get explanations (sharh), browse scholars, books, and narrators.
بسم الله الرحمن الرحيم #
Dorar Hadith #
تغيير اللغة: 🇸🇦 AR #
A Dart library to search and retrieve hadith and related data from Dorar Al-Sanniyah.
Inspired by and partially based on the repository dorar-hadith-api by Ahmed Al-Tabarani. Works with any Dart program without requiring Flutter.
Library Highlights #
- Fast hadith search with filters by narrator, book, grade, hadith scholar (mohdith), and more
- Retrieve detailed hadith information
- Search and fetch hadith explanations (Sharh)
- Search for all available sharh by query
- Find similar hadiths and alternate sahih versions
- Thematic categories (التصنيف الموضوعي) on detailed hadith results
- Offline browsing for books, narrators, and hadith scholars (mohdith) used for filtering
Search Capabilities #
- Search by hadith text
- Filter by hadith grade
- Filter by hadith scholars (mohdith)
- Filter by narrators
- Filter by books
- Filter by hadith type (Qudsi, Athar, etc.)
- Pagination metadata
Installation #
Run:
dart pub add dorar_hadith
Or using Flutter:
flutter pub add dorar_hadith_flutter
Pure Dart/CLI users should use dart pub add dorar_hadith. Flutter apps should depend on dorar_hadith_flutter, which pulls in dorar_hadith transitively.
Flutter Setup #
Call DorarHadithFlutter.ensureInitialized() once in main() before using offline reference data or the narrator database:
import 'package:dorar_hadith_flutter/dorar_hadith_flutter.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await DorarHadithFlutter.ensureInitialized();
runApp(const MyApp());
}
Pass databaseDirectory to override where the copied rawi.db is stored (defaults to the application support directory from path_provider).
See the dorar_hadith_flutter README for asset keys, failure modes, and advanced customization.
Offline Data, Assets & Platform Behavior #
Offline reference browsing uses bundled assets from the dorar_hadith package (declared in its pubspec.yaml):
| Asset | Path | Used by |
|---|---|---|
| Books | assets/data/book.json |
BookReferenceService |
| Scholars (mohdith) | assets/data/mohdith.json |
MohdithReferenceService |
| Narrators (rawi) | assets/database/rawi.db |
RawiDatabase / RawiReferenceService |
API response caching uses a separate SQLite database (cache.db on native CLI, WebAssembly storage on web). It is independent of the offline reference assets above.
Platform differences #
Dart CLI / pure Dart (native) — default when you depend on dorar_hadith only:
- JSON assets —
FileAssetLoaderresolves files viaIsolate.resolvePackageUri('package:dorar_hadith/…'), then falls back to paths relative to the current working directory. rawi.db—connection_native.dartopens the bundled database from the package URI, then tries several CWD-relative paths (see failure modes below).cache.db— created in the current working directory.
Flutter (Android, iOS, Linux, macOS, Windows) — use dorar_hadith_flutter:
- Call
DorarHadithFlutter.ensureInitialized()once before offline reference or narrator APIs. The core package registersFileAssetLoaderon native targets, which does not read Flutter asset bundles. - After initialization, JSON loads go through
rootBundlewith keys likepackages/dorar_hadith/assets/data/book.json, andrawi.dbis copied once into the application support directory (or a customdatabaseDirectory) before opening. - You do not need to re-declare
rawi.dbor the JSON files in your apppubspec.yaml; transitive package assets fromdorar_hadithare bundled automatically. dorar_hadith_flutterusesdart:ioand is intended for native Flutter targets, not Flutter Web.
Web (Dart web / Flutter Web with dorar_hadith only):
- JSON assets —
WebAssetLoaderfetches via HTTP relative toUri.base(non-200 responses becomeAssetLoaderException). rawi.dbandcache.db— DriftWasmDatabasewithsqlite3.wasmanddrift_worker.dart.jsserved from your app root (/sqlite3.wasm,/drift_worker.dart.js). The initialrawi.dbbytes are fetched from common Flutter Web asset URLs (see failure modes).- Flutter Web apps should configure the core package directly; do not rely on
dorar_hadith_flutter.ensureInitialized()on web.
Offline failure modes #
| Situation | When | What is thrown |
|---|---|---|
| JSON asset not found | BookReferenceService / MohdithReferenceService initialize() |
AssetLoaderException — CLI: Asset file not found. Tried package asset and local filesystem for: <path>; web: Failed to load asset: HTTP <status> or Failed to load asset via HTTP: <uri> |
| Asset read error | File exists but cannot be read (CLI) | AssetLoaderException with Failed to read asset file: <path> and optional cause |
| No asset loader configured | AssetLoader() before any platform registration |
UnsupportedError: No AssetLoader has been configured for this platform… |
rawi.db not found (native CLI) |
RawiDatabase query before DB is reachable |
Exception: Database file not found. Tried package asset and paths: followed by package:dorar_hadith/assets/database/rawi.db and CWD fallbacks |
rawi.db not fetchable (web) |
RawiDatabase open on web |
Exception: Failed to initialize web database: Could not fetch bundled rawi.db. with candidate asset URLs |
Flutter offline APIs before ensureInitialized() |
client.bookRef, client.mohdithRef, or client.rawiRef in a Flutter app |
JSON: AssetLoaderException (filesystem lookup misses bundle assets). Narrators: native Exception (Database file not found…) because RawiDatabase still uses the CLI connection factory |
| Missing Flutter bundle asset | ensureInitialized() or reference initialize() after setup, asset stripped from build |
FlutterError from rootBundle (typically Unable to load asset: packages/dorar_hadith/…). Not wrapped as AssetLoaderException |
ensureInitialized() called twice |
Second and later calls | No error — returns immediately (idempotent). The first databaseDirectory wins; later overrides are ignored |
| Invalid JSON after load | Corrupt book.json / mohdith.json |
FormatException from json.decode (not a DorarException) |
Reference lookups by unknown ID return null (books, scholars) or empty lists (get*ByIds skips missing keys). They do not throw.
Quick Start #
import 'package:dorar_hadith/dorar_hadith.dart';
void main() async {
await DorarClient.use((client) async {
// Search for hadiths about prayer
final results = await client.searchHadith(
HadithSearchParams(value: 'الصلاة', page: 1),
);
print('Found ${results.data.length} hadiths');
// Print first hadith
if (results.data.isNotEmpty) {
final h = results.data.first;
print('Hadith: ${h.hadith}');
print('Narrator: ${h.rawi}');
print('Scholar: ${h.mohdith}');
print('Verdict: ${h.hukm}');
}
// You can return the result to use else where
return results;
});
}
For a complete example covering all library features, see:
example/example.dart
Usage #
Important Note #
Search and list operations that support pagination return an ApiResponse<T> wrapper:
- The result:
data - Pagination metadata:
metadata(SearchMetadata)
Methods that return ApiResponse: searchHadith, searchHadithDetailed, searchSharh, and getUsulHadith / hadith.getUsul.
Single-item lookups return the model directly: getHadithById → DetailedHadith, getSharhById → Sharh, book.getById → BookInfo, mohdith.getById → MohdithInfo, getSimilarHadith → List<DetailedHadith>, getAlternateHadith → DetailedHadith?. Offline reference lookups return List<...> or nullable items and do not use ApiResponse.
Quick Hadith Search #
Using client.searchHadith is fast and returns lightweight Hadith objects
directly from the public API.
- Results are limited to ~15 hadiths, and filters can be used.
- Only the textual fields (
hadith,rawi,mohdith,book,numberOrPage,grade) are included in this response. - Call
client.searchHadithDetailedwhen you need IDs, sharh metadata, Dorar links, or any of the extended fields provided byDetailedHadith.
final results = await client.searchHadith(
HadithSearchParams(value: 'الإيمان'),
);
for (var hadith in results.data) {
print('${hadith.hadith}');
print('Grade: ${hadith.hukm}');
}
Detailed Search with Filters #
Note: Due to how Dorar works, detailed search populates
DetailedHadith.explainGrade instead of grade, to avoid these issues always use DetailedHadith.hukm.
final params = HadithSearchParams(
value: 'الصيام',
page: 1,
degrees: [HadithDegree.authenticHadith], // Only sahih
mohdith: [MohdithReference.bukhari],
searchMethod: SearchMethod.anyWord,
zone: SearchZone.qudsi,
);
final results = await client.searchHadithDetailed(params);
Get Hadith by ID #
Returns a DetailedHadith with complete metadata when available.
final hadith = await client.getHadithById('12345');
print('Hadith: ${hadith.hadith}');
print('Book: ${hadith.book}');
print('Grade: ${hadith.hukm}');
Similar, Usul (Sources), Alternate Sahih #
Note: The DetailedHadith model has flags to check availability:
hasAlternateHadithSahihfor alternate sahihhasSimilarHadithfor similar hadithshasUsulHadithfor sources
// Get similar
final similar = await client.hadith.getSimilar('12345');
// Or use the convenience method
final sameSimilar = await client.getSimilarHadith('12345');
// Get alternate sahih
final alternate = await client.hadith.getAlternate('12345');
// Or use the convenience method
final sameAlternate = await client.getAlternateHadith('12345');
// Get sources
final usul = await client.hadith.getUsul('12345');
print('Main hadith: ${usul.data.hadith.hadith}');
print('Sources: ${usul.data.count}');
// Or use the convenience method
final sameUsul = await client.getUsulHadith('12345');
Search for Sharh (Explanation) #
Note: When using client.searchHadithDetailed, if a hadith has a sharh, you will find its ID in DetailedHadith.sharhMetadata. Use it as follows:
// Get sharh by ID
final sharh = await client.sharh.getById('789');
// Search by sharh text
final sharhByText = await client.sharh.getByText('إنما الأعمال بالنيات');
// Or use the convenience method
final sameSharh = await client.getSharhByText('إنما الأعمال بالنيات');
// Search for all available sharh matching a query
final sharhResults = await client.searchSharh(
HadithSearchParams(value: 'الصلاة'),
);
for (var s in sharhResults.data) {
print('Sharh: ${s.sharhText}');
}
Reference Data (Offline) #
Reference data is used for filtering (hadith scholar [mohdith], book, narrator) and is available offline for speed and usability.
Flutter apps must call DorarHadithFlutter.ensureInitialized() first. See Offline Data, Assets & Platform Behavior for bundled assets, platform differences, and failure modes.
Note: Reference items contain only id and name (e.g., Sahih al-Bukhari). For full details, fetch via the API. See: Get Book or Hadith Scholar (Mohdith) Details.
Search Scholars (Hadith scholars – mohdith)
// By name
final bukhari = await client.mohdithRef.searchMohdith('البخاري');
// By ID
final scholar = await client.mohdithRef.getMohdithById('256');
// List paginated
final allScholars = await client.mohdithRef.getAllMohdith(limit: 50);
// Multiple by IDs
final scholars = await client.mohdithRef.getMohdithByIds(['256', '179']);
// Shortcut
final results = await client.searchMohdith('أحمد');
Search Books
// By name
final sahihBooks = await client.bookRef.searchBook('صحيح');
// By ID
final book = await client.bookRef.getBookById('6216');
// List paginated
final allBooks = await client.bookRef.getAllBooks(limit: 100, offset: 0);
// Shortcut
final results = await client.searchBooks('سنن');
Search Narrators
// By name
final narrators = await client.rawiRef.searchRawi('أبو هريرة', limit: 10);
// By ID
final abuHurayrah = await client.rawiRef.getRawiById(1416);
// Paginated listing
final page1 = await client.rawiRef.getAllRawi(limit: 50, offset: 0);
// Counts
final total = await client.rawiRef.countRawi();
final searchCount = await client.rawiRef.countRawi(query: 'عبد الله');
// Shortcut
final results = await client.searchRawi('عمر');
// Important: dispose after use to avoid warnings
await client.dispose();
Predefined Constants
To make filtering easier without repeatedly searching, popular scholars, books, and narrators are provided as ready-to-use constants. If you’d like to add more, please open an issue on GitHub.
// Sample scholar constants
MohdithReference.bukhari
MohdithReference.muslim
MohdithReference.abuDawud
// ... 20 total
// Sample book constants
BookReference.sahihBukhari
BookReference.sahihMuslim
// ... 21 total
// Sample narrator constants
RawiReference.abuHurayrah
RawiReference.aisha
// ... 21 total
// Using constants in filters
final params = HadithSearchParams(
value: 'الصلاة',
page: 1,
mohdith: [MohdithReference.bukhari],
books: [BookReference.sahihBukhari],
);
final results = await client.hadith.searchViaSite(params);
Get Book or Hadith Scholar (Mohdith) Details #
// Book details from API
final book = await client.book.getById('123');
print('Book: ${book.name}');
print('Author: ${book.author}');
// Hadith scholar (mohdith) details from API
final scholar = await client.mohdith.getById('456');
print('Name: ${scholar.name}');
print('Bio: ${scholar.info}');
Available Options #
This section describes all models and options provided by the library.
Hadith Models #
The package now exposes two layered models:
Hadith: Lightweight record returned by the official Dorar API. Contains the matn, narrator, scholar, source book, page/number, and grade.DetailedHadith: ExtendsHadithwith every extra bit of metadata collected from the Dorar website (IDs, sharh metadata, takhrij, related links, etc.).
class Hadith {
final String hadith; // Hadith text (matn)
final String rawi; // Narrator name
final String mohdith; // Scholar name
final String book; // Source book name
final String numberOrPage; // Page or hadith number in the source
final String grade; // Verdict shown by the API
}
class DetailedHadith extends Hadith {
final String? hadithId; // Unique hadith ID
final String? mohdithId; // Scholar ID
final String? bookId; // Book ID
final String? explainGrade; // Verdict text (detailed search)
final String? takhrij; // Takhrij / additional sources
final List<HadithCategory> categories; // Thematic categories (التصنيف الموضوعي)
final bool hasSimilarHadith; // Similar narrations exist?
final bool hasAlternateHadithSahih; // Alternate sahih available?
final bool hasUsulHadith; // Usul (sources) available?
final String? similarHadithDorar; // URL for similar narrations
final String? alternateHadithSahihDorar; // URL for alternate sahih
final String? usulHadithDorar; // URL for usul sources
final bool hasSharhMetadata; // Sharh metadata included?
final SharhMetadata? sharhMetadata; // Sharh metadata payload
}
client.searchHadith and the other API-backed endpoints return the lightweight
Hadith model. The site-powered endpoints (detailed search, similar,
alternate, usul) upgrade those results to DetailedHadith, populating IDs,
sharh metadata, related links, and the helper flags below.
Key helpers:
hasSimilarHadith⇒ callclient.hadith.getSimilar()hasAlternateHadithSahih⇒ callclient.hadith.getAlternate()hasUsulHadith⇒ callclient.hadith.getUsul()hasSharhMetadata⇒ inspectsharhMetadata.idorsharhMetadata.sharhhukmgetter ⇒ readsexplainGradewhen filled, otherwise falls back tograde(ideal for printing a single verdict string)
Sharh Model #
Represents a hadith with its explanation.
class Sharh {
// Base hadith info
final String hadith; // Hadith text
final String rawi; // Narrator
final String mohdith; // Scholar
final String book; // Source book
final String numberOrPage; // Page/hadith number
final String grade; // Grade
final String? takhrij; // Takhrij
// Sharh info
final bool hasSharhMetadata; // Has sharh?
final SharhMetadata? sharhMetadata; // Sharh metadata
// Helper to access sharh text directly
String? get sharhText => sharhMetadata?.sharh;
}
Usage:
final sharh = await client.sharh.getById('789');
if (sharh.hasSharhMetadata && sharh.sharhText != null) {
print('Sharh: ${sharh.sharhText}');
}
SharhMetadata #
class SharhMetadata {
final String id; // Sharh ID
final bool isContainSharh; // Whether sharh text is included
final String? sharh; // Sharh text (if any)
}
HadithCategory #
Represents a thematic category (التصنيف الموضوعي) extracted from Dorar.net search results. Each DetailedHadith may have zero or more categories.
class HadithCategory {
final String id; // Category ID (hash from URL)
final String name; // Arabic category name
}
Usage:
final results = await client.searchHadithDetailed(
HadithSearchParams(value: 'الصلاة'),
);
for (var hadith in results.data) {
if (hadith.categories.isNotEmpty) {
print('Categories:');
for (var cat in hadith.categories) {
print(' - ${cat.name} (${cat.id})');
}
}
}
UsulHadith (Sources) #
Represents a hadith with all its sources.
class UsulHadith {
final DetailedHadith hadith; // Detailed hadith with metadata
final List<UsulSource> sources; // All sources
final int count; // Sources count
}
// Single source entry
class UsulSource {
final String source; // Source name and page
final String chain; // Chain of narration
final String hadithText; // Hadith text in this source
}
Example:
final usulResponse = await client.hadith.getUsul('12345');
final usul = usulResponse.data;
print('Sources: ${usul.count}');
for (var source in usul.sources) {
print('Source: ${source.source}');
print('Chain: ${source.chain}');
}
BookInfo #
Full book details (via API).
class BookInfo {
final String name; // Book name
final String bookId; // Unique ID
final String author; // Author
final String reviewer; // Reviewer
final String publisher; // Publisher
final String edition; // Edition
final String editionYear; // Year of edition
}
Usage:
final book = await client.book.getById('6216');
print('${book.name} - ${book.author}');
print('Publisher: ${book.publisher}');
MohdithInfo #
Full hadith scholar (mohdith) details (via API).
class MohdithInfo {
final String name; // Scholar name
final String mohdithId; // Unique ID
final String info; // Biography and details
}
Usage:
final mohdith = await client.mohdith.getById('256');
print('Name: ${mohdith.name}');
print('Bio: ${mohdith.info}');
Reference Items #
Lightweight items for offline filtering. All extend ReferenceItem.
BookItem
class BookItem extends ReferenceItem {
final String id; // Book ID
final String name; // Book name
final String? author; // Author (if any)
final String? mohdithId; // Hadith scholar (mohdith) author ID
final String? category; // Category (if any)
}
Usage:
// Offline search in books
final books = await client.bookRef.searchBook('صحيح', limit: 10);
for (var book in books) {
print('${book.name} - ${book.author}');
// Get full details (online)
final fullInfo = await client.book.getById(book.id);
}
MohdithItem
class MohdithItem extends ReferenceItem {
final String id; // Scholar ID
final String name; // Scholar name
final int? deathYear; // Death year (Hijri)
final String? era; // Era (if any)
}
Usage:
// Offline search in scholars
final scholars = await client.mohdithRef.searchMohdith('البخاري', limit: 5);
for (var scholar in scholars) {
print('${scholar.name}');
if (scholar.deathYear != null) {
print('Died in: ${scholar.deathYear} AH');
}
}
RawiItem
class RawiItem extends ReferenceItem {
final String id; // Narrator ID
final String name; // Narrator name
}
Usage:
// Offline narrator search
final narrators = await client.rawiRef.searchRawi('أبو هريرة', limit: 3);
for (var narrator in narrators) {
print(narrator.name);
}
// Counts
final total = await client.rawiRef.countRawi();
final searchCount = await client.rawiRef.countRawi(query: 'عبد الله');
ApiResponse #
Paginated search operations return results inside ApiResponse to simplify pagination. Single-item lookups return the model directly (see Important Note).
class ApiResponse<T> {
final T data; // Actual data (hadith, list, etc.)
final SearchMetadata metadata; // Extra info about the result
}
Examples:
// Search returns ApiResponse<List<Hadith>>
final response = await client.searchHadith(
HadithSearchParams(value: 'الصلاة', page: 1),
);
print('Count: ${response.data.length}');
print('Current page: ${response.metadata.page}');
print('From cache: ${response.metadata.isCached}');
// Usul returns ApiResponse<UsulHadith>
final usulResponse = await client.hadith.getUsul('12345');
print('Sources: ${usulResponse.data.count}');
SearchMetadata #
Additional info about a search result.
class SearchMetadata {
final int length; // Number of returned results
final int? currentPageCount; // Number of results on this page
final int? total; // Total results across all pages
final int? page; // Current page number
final int? totalPages; // Total number of pages
final bool? hasNextPage; // Whether there is a next page
final bool? hasPrevPage; // Whether there is a previous page
final bool? removeHtml; // Whether HTML tags were removed
final bool? specialist; // Include specialist hadiths?
final int? numberOfNonSpecialist; // Non-specialist count
final int? numberOfSpecialist; // Specialist count
final bool isCached; // Result from cache?
final int? usulSourcesCount; // Sources count (for Usul requests)
// Create a modified copy
SearchMetadata copyWith({...});
}
Usage:
final response = await client.searchHadith(params);
final meta = response.metadata;
if (meta.isCached) {
print('Result is from cache - fast!');
}
print('Page ${meta.page} of ${meta.totalPages}');
print('Total results: ${meta.total}');
print('Results on this page: ${meta.currentPageCount}');
if (meta.hasNextPage == true) {
print('More results available on the next page');
}
HadithSearchParams #
All search filters and parameters.
class HadithSearchParams {
// Required
final String value; // Search text
// Optional - search options
final int page; // Page (default: 1)
final bool removeHtml; // Remove HTML (default: true)
final bool specialist; // Include specialist hadiths (default: false)
final String? exclude; // Words/phrases to exclude
// Optional - filters
final SearchMethod? searchMethod; // Search method (all/any/exact)
final SearchZone? zone; // Hadith type (Marfoo/Qudsi/Athar/Sharh)
final List<HadithDegree>? degrees; // Filter by grade
final List<MohdithReference>? mohdith; // Filter by scholars
final List<BookReference>? books; // Filter by books
final List<RawiReference>? rawi; // Filter by narrators
// Create a modified copy
HadithSearchParams copyWith({...});
}
Examples:
// Minimal search
final simple = HadithSearchParams(value: 'الصلاة', page: 1);
// With specific filters
final filtered = HadithSearchParams(
value: 'الإيمان',
page: 1,
degrees: [HadithDegree.authenticHadith], // Sahih only
mohdith: [MohdithReference.bukhari], // Al-Bukhari only
books: [BookReference.sahihBukhari], // Sahih al-Bukhari only
searchMethod: SearchMethod.allWords, // All words
zone: SearchZone.qudsi, // Qudsi hadiths
);
// Advanced with exclusions
final advanced = HadithSearchParams(
value: 'الجنة النار',
page: 1,
exclude: 'الدنيا', // Exclude the word "الدنيا"
degrees: [
HadithDegree.authenticHadith,
HadithDegree.authenticChain,
],
mohdith: [
MohdithReference.bukhari,
MohdithReference.muslim,
],
searchMethod: SearchMethod.anyWord, // Any word
removeHtml: true, // Remove HTML
);
// Modify existing params
final modified = simple.copyWith(
page: 2, // Go to page 2
degrees: [HadithDegree.authenticHadith], // Add filter
);
final results = await client.searchHadithDetailed(advanced);
HadithDegree #
Static values representing hadith grading according to scholars.
enum HadithDegree {
all, // All grades (no filter)
authenticHadith, // Scholars ruled the hadith itself as sahih
authenticChain, // Scholars ruled the chain as sahih
weakHadith, // Scholars ruled the hadith as weak
weakChain, // Scholars ruled the chain as weak
// Each value has:
final String id; // ID used in the API
final String label; // Arabic label
}
Usage:
// Filter sahih only
final params = HadithSearchParams(
value: 'الصدقة',
page: 1,
degrees: [HadithDegree.authenticHadith],
);
// Sahih (hadith or chain)
final params2 = HadithSearchParams(
value: 'الصدقة',
page: 1,
degrees: [
HadithDegree.authenticHadith,
HadithDegree.authenticChain,
],
);
// Helpers
print(HadithDegree.authenticHadith.toString()); // Arabic label
print(HadithDegree.authenticHadith.toQueryParam()); // API id
SearchMethod #
How the text search is performed.
enum SearchMethod {
allWords, // All words (AND)
anyWord, // Any word (OR)
exactMatch, // Exact phrase
// Each value has:
final String id; // API id
final String label; // Arabic label
}
Usage:
// All words (AND)
final allWords = HadithSearchParams(
value: 'الصلاة الزكاة',
page: 1,
searchMethod: SearchMethod.allWords, // Must contain both words
);
// Any word (OR)
final anyWord = HadithSearchParams(
value: 'الصلاة الزكاة',
page: 1,
searchMethod: SearchMethod.anyWord, // Contains either word
);
// Exact phrase
final exact = HadithSearchParams(
value: 'إنما الأعمال بالنيات',
page: 1,
searchMethod: SearchMethod.exactMatch, // Exact phrase
);
// Helpers
print(SearchMethod.allWords.toString()); // "جميع الكلمات"
print(SearchMethod.allWords.toQueryParam()); // "w"
SearchZone #
Filters by hadith type.
enum SearchZone {
all, // All hadiths (no filter)
marfoo, // Marfoo (attributed to the Prophet ﷺ)
qudsi, // Qudsi (from Allah)
sahabaAthar, // Companions' athar
sharh, // Explanations (shuruh)
// Each value has:
final String id; // API id
final String label; // Arabic label
}
Usage:
// Qudsi only
final qudsi = HadithSearchParams(
value: 'الجنة',
page: 1,
zone: SearchZone.qudsi,
);
// Marfoo only
final marfoo = HadithSearchParams(
value: 'الصلاة',
page: 1,
zone: SearchZone.marfoo,
);
// Sahaba athar
final athar = HadithSearchParams(
value: 'عمر بن الخطاب',
page: 1,
zone: SearchZone.sahabaAthar,
);
// Helpers
print(SearchZone.qudsi.toString()); // "الأحاديث القدسية"
print(SearchZone.qudsi.toQueryParam()); // "1"
Client Options #
You can customize DorarClient when creating it.
final client = DorarClient(
timeout: Duration(seconds: 15), // Request timeout (default: 15s)
// Caching is enabled by default using a persistent SQLite database
// (cache.db on native, WebAssembly on web)
);
Persistent Caching #
API response caching uses a persistent SQLite database (cache.db on native CLI, WebAssembly on web). It is separate from the offline reference assets (book.json, mohdith.json, rawi.db).
API responses are cached in a shared CacheService backed by SQLite (cache.db) plus an in-memory layer (default: 100 entries, 7-day TTL).
- Native Platforms (CLI):
cache.dbis created in the current working directory. - Flutter (native): When using
dorar_hadith_flutter, the offlinerawi.dbis copied into the application support directory and persists across restarts. API response caching follows the platform default (cache.dbin the CWD unless you customizeCacheDatabase.configureConnection). - Web: Uses
sqlite3.wasmanddrift_worker.dart.js(see Offline Data, Assets & Platform Behavior).
A cache miss is not an error — the client fetches from Dorar.net and stores the result. Cached hits set SearchMetadata.isCached to true on ApiResponse results. Expired entries are deleted and treated as a miss. Corrupt cached JSON throws FormatException from jsonDecode (not a DorarException); call client.clearCache() to recover. SQLite or WebAssembly storage failures propagate as platform/Drift errors and are not wrapped.
All services share one cache. client.clearCache() and every *.clearCache() on API services (hadith, sharh, book, mohdith) clear the entire shared cache, not an isolated per-service store.
// Clear all cached API responses
await client.clearCache();
// Equivalent — clears the same shared cache
await client.hadith.clearCache();
Resource Cleanup #
Always call dispose() when done to close database connections and avoid warnings.
void main() async {
final client = DorarClient();
try {
// Use the library
final results = await client.searchHadith(
HadithSearchParams(value: 'الصلاة', page: 1),
);
} finally {
// Mandatory cleanup
await client.dispose();
}
}
Error Handling #
The library uses a sealed class hierarchy for exceptions, enabling safer handling with pattern matching. All API/network failures throw a DorarException subclass — there is no separate DorarApiException type. Offline reference asset/database failures are documented in Offline failure modes; reference lookups by unknown ID return empty lists or null instead of throwing.
Exception naming
| What you catch | Notes |
|---|---|
DorarException |
Sealed base for all API/network errors listed below |
DorarTimeoutException |
Public timeout type; Dart's internal TimeoutException from .timeout() is caught inside DorarHttpClient and converted — callers never see it |
DorarValidationException |
Client-side input validation (DorarValidationException, not a generic ValidationException) |
FormatException |
Corrupt cached JSON or offline asset JSON after load — not a DorarException |
DorarException Types
All errors extend DorarException with the following types:
// Base — every subclass has:
sealed class DorarException {
final String message;
final dynamic details; // optional extra context
final int? statusCode; // set on HTTP-related subclasses
}
// 1. Network error — connectivity or unexpected HTTP status (not 200/404/429/5xx)
DorarNetworkException { final String message; final dynamic details; }
// 2. Timeout — request exceeded timeout after retries (default: 3 attempts)
DorarTimeoutException { final String message; final Duration timeout; final dynamic details; }
// 3. Not found — HTTP 404 or domain-specific missing resource
DorarNotFoundException { final String message; final String resource; final dynamic details; }
// 4. Validation error — invalid client-side input before the request is sent
DorarValidationException { final String message; final String? field; final String? rule; final dynamic details; }
// 5. Server error — HTTP 5xx or unexpected/empty Dorar response payload
DorarServerException { final String message; final int statusCode; final dynamic details; }
// 6. Parse error — HTML/JSON parsing failed after a successful HTTP response
DorarParseException { final String message; final String? rawData; final Type? expectedType; final dynamic details; }
// 7. Rate limit — HTTP 429
DorarRateLimitException { final String message; final int? limit; final DateTime? resetAt; final dynamic details; }
HTTP layer (DorarHttpClient)
Every online API call goes through DorarHttpClient (default timeout: 15 seconds, max retries: 3, exponential backoff starting at 1 second).
| Condition | Exception | Retried? |
|---|---|---|
TimeoutException on a request |
DorarTimeoutException (after final attempt) |
Yes |
http.ClientException (connectivity) |
DorarNetworkException (after final attempt) |
Yes |
| HTTP 404 | DorarNotFoundException |
No |
| HTTP 429 | DorarRateLimitException |
No |
| HTTP 5xx | DorarServerException |
No |
| Other HTTP status | DorarNetworkException |
No |
| Unexpected error in HTTP client | DorarNetworkException |
No |
DorarException subclasses raised by the HTTP layer are rethrown immediately without retry.
When each public API throws
| Method / service | DorarValidationException |
Other DorarException |
Returns empty/null instead |
|---|---|---|---|
searchHadith / hadith.searchViaApi |
— (no local validation) | Network/timeout/rate-limit; DorarServerException if response JSON is invalid or zero hadiths parse |
— |
searchHadithDetailed / hadith.searchViaSite |
— | Network/timeout/rate-limit; DorarServerException if expected HTML tab is missing |
Empty data list when page parses but has no hadiths |
getHadithById / hadith.getById |
Invalid hadithId |
Network/timeout/404; DorarServerException if page structure is unexpected |
— |
getSimilarHadith / hadith.getSimilar |
Invalid hadithId |
Network/timeout/404 | Empty list |
getAlternateHadith / hadith.getAlternate |
Invalid hadithId |
Network/timeout/404 | null when the page has no alternate block or parsing that block fails |
getUsulHadith / hadith.getUsul |
Invalid hadithId |
Network/timeout; DorarNotFoundException when usul section is missing |
— |
searchSharh / sharh.search |
Empty/too-long value; page not in 1–1000 |
Network/timeout; DorarServerException if expected HTML tab is missing |
Empty data when search finds no sharh IDs |
getSharhByText / sharh.getByText |
Empty/too-long text |
Network/timeout; DorarNotFoundException when no sharh ID is found in search results |
— |
getSharhById / sharh.getById |
Invalid numeric sharhId |
Network/timeout/404; DorarParseException on parse failure |
— |
book.getById |
Invalid numeric bookId |
Network/timeout/404; DorarParseException on parse failure |
— |
mohdith.getById |
Invalid numeric mohdithId |
Network/timeout/404; DorarParseException on parse failure |
— |
searchBooks, searchMohdith, searchRawi, *Ref.* |
— | bookRef/mohdithRef: AssetLoaderException on first load if assets missing (see Offline failure modes); rawiRef: generic Exception if rawi.db missing on native CLI |
Empty list on no match; get*ById returns null |
Malformed JSON in an HTTP 200 body throws FormatException from jsonDecode (not DorarException). HTML/body parsing failures inside services become DorarParseException.
Input validation rules (client-side, before HTTP):
- Search text (
sharh.getByText,sharh.searchonly): required, max 500 characters.searchHadithandsearchHadithDetaileddo not validatevalueorpagelocally — invalid values are sent to Dorar as-is. - Page (
sharh.searchonly): 1–1000. - Hadith ID (
getById,getSimilar,getAlternate,getUsul): non-empty alphanumeric plus-/_. - Sharh / book / mohdith IDs: non-empty numeric strings.
DorarClient(timeout: ...)/DorarClient.use(timeout: ...): positive duration, max 5 minutes (validated inDorarHttpClientconstructor).
Cache, disposal, and DorarClient.use()
- Cache miss: not an error; the client fetches and caches transparently.
- Cache hit: parsed response returned;
SearchMetadata.isCached = trueonApiResponseresults. - Expired entry: deleted and treated as a miss.
- Corrupt cached JSON:
FormatExceptionfromjsonDecode— clear withclient.clearCache(). - Storage failure: SQLite/Drift or WebAssembly errors propagate unwrapped (not
DorarException). dispose(): closes the HTTP client, cache database, and narrator database; does not throw under normal use. Do not reuse a disposed client — create a newDorarClientor useDorarClient.use().DorarClient.use(fn): creates a client, runsfn, and always callsdispose()in afinallyblock (even whenfnthrows). Accepts an optionaltimeout(default: 15 seconds).
Comprehensive Handling with Switch Expression
Pattern matching with a switch expression is the recommended approach; the compiler ensures exhaustive coverage:
try {
final results = await client.searchHadith(
HadithSearchParams(value: 'الصلاة', page: 1),
);
} on DorarException catch (e) {
// Pattern matching - compiler ensures coverage!
final message = switch (e) {
DorarNetworkException() =>
'🌐 Network error: ${e.message}\n'
'Please check your internet connection',
DorarTimeoutException() =>
'⏱️ Request timed out after ${e.timeout.inSeconds} seconds\n'
'Try again later',
DorarNotFoundException() =>
'🔍 Not found: ${e.resource}\n'
'Verify the identifier',
DorarValidationException() =>
'✋ Validation error: ${e.message}\n'
'${e.field != null ? "Field: ${e.field}" : ""}',
DorarServerException() =>
'🖥️ Server error (${e.statusCode}): ${e.message}',
DorarParseException() =>
'📄 Parse error: ${e.message}',
DorarRateLimitException() =>
'🚫 Rate limit exceeded\n'
'${e.resetAt != null ? "Retry after: ${e.resetAt}" : ""}',
};
print(message);
}
Helper Function for Error Messages
The library provides a helper to turn exceptions into readable messages:
import 'package:dorar_hadith/dorar_hadith.dart';
try {
final results = await client.searchHadith(params);
} on DorarException catch (e) {
// Use the helper
print(getExceptionMessage(e));
}
Available Services #
DorarClient exposes multiple focused services, each responsible for specific functionality.
Hadith Service #
The core service for searching and fetching hadiths.
final client = DorarClient();
// 1. Quick search (API - ~15 results)
final quickResults = await client.searchHadith(
HadithSearchParams(value: 'الصلاة', page: 1),
);
// 2. Detailed search (Site - ~30 results)
final detailedResults = await client.searchHadithDetailed(
HadithSearchParams(value: 'الصلاة', page: 1),
);
// 3. Get by ID
final hadith = await client.getHadithById('12345');
// Or use the service directly
final sameResults = await client.hadith.searchViaApi(params);
final sameDetailed = await client.hadith.searchViaSite(params);
final sameHadith = await client.hadith.getById('12345');
// 4. Similar hadiths
if (hadith.hasSimilarHadith && hadith.hadithId != null) {
final similar = await client.hadith.getSimilar(hadith.hadithId!);
print('Found ${similar.length} similar hadiths');
}
// 5. Alternate sahih
if (hadith.hasAlternateHadithSahih && hadith.hadithId != null) {
final alternate = await client.hadith.getAlternate(hadith.hadithId!);
if (alternate != null) {
print('Alternate sahih: ${alternate.hadith}');
}
}
// 6. Usul (sources)
if (hadith.hasUsulHadith && hadith.hadithId != null) {
final usulResponse = await client.hadith.getUsul(hadith.hadithId!);
final usul = usulResponse.data;
print('Sources: ${usul.count}');
for (var source in usul.sources) {
print('- ${source.source}: ${source.chain}');
}
}
// 7. Clear shared cache (all API services)
await client.hadith.clearCache();
Sharh Service #
Search and retrieve hadith explanations.
// 1. Search by hadith text
final sharh = await client.sharh.getByText('إنما الأعمال بالنيات');
// You can also search in specialist hadiths
final specialistSharh = await client.sharh.getByText(
'نص الحديث',
specialist: true,
);
// 2. Get by ID
// (ID comes from DetailedHadith.sharhMetadata.id)
final hadith = await client.getHadithById('12345');
if (hadith.hasSharhMetadata && hadith.sharhMetadata != null) {
final sharhId = hadith.sharhMetadata!.id;
final sharh = await client.sharh.getById(sharhId);
if (sharh.sharhText != null) {
print('Sharh: ${sharh.sharhText}');
}
}
// 3. Search for all sharh matching a query
final sharhResults = await client.searchSharh(
HadithSearchParams(value: 'الصلاة'),
);
for (var s in sharhResults.data) {
print('Hadith: ${s.hadith}');
print('Sharh: ${s.sharhText}');
}
// 4. Clear shared cache (all API services)
await client.sharh.clearCache();
Book Service (Detailed) #
Fetch detailed book info (requires internet).
// Get book by ID
final book = await client.book.getById('6216'); // Sahih al-Bukhari
print('Book: ${book.name}');
print('Author: ${book.author}');
print('Reviewer: ${book.reviewer}');
print('Publisher: ${book.publisher}');
print('Edition: ${book.edition}');
print('Edition Year: ${book.editionYear}');
// Clear shared cache (all API services)
await client.book.clearCache();
Mohdith Service (Detailed) #
Fetch detailed scholar info (requires internet).
// Get scholar by ID
final mohdith = await client.mohdith.getById('256'); // Imam al-Bukhari
print('Name: ${mohdith.name}');
print('Bio: ${mohdith.info}');
// Clear shared cache (all API services)
await client.mohdith.clearCache();
Book Reference Service (Offline) #
Search available books without internet.
// 1. Search by name
final books = await client.bookRef.searchBook('صحيح', limit: 10);
// Or shortcut
final sameBooks = await client.searchBooks('صحيح');
for (var book in books) {
print('${book.name} - ${book.author}');
}
// 2. Get by ID
final bukhari = await client.bookRef.getBookById('6216');
print(bukhari.name); // صحيح البخاري
// 3. Get multiple by IDs
final multipleBooks = await client.bookRef.getBooksByIds([
'6216', // Sahih al-Bukhari
'3088', // Sahih Muslim
]);
// 4. List all with pagination
final allBooks = await client.bookRef.getAllBooks(
limit: 50,
offset: 0,
);
// 5. Counts
final totalBooks = await client.bookRef.countBooks();
final sahihBooks = await client.bookRef.countBooks(query: 'صحيح');
print('Total books: $totalBooks');
print('"Sahih" books: $sahihBooks');
Mohdith Reference Service (Offline) #
Search available scholars without internet.
// 1. Search by name
final scholars = await client.mohdithRef.searchMohdith('البخاري', limit: 5);
// Or shortcut
final sameScholars = await client.searchMohdith('البخاري');
for (var scholar in scholars) {
print('${scholar.name}');
if (scholar.deathYear != null) {
print('Death year: ${scholar.deathYear} AH');
}
}
// 2. Get by ID
final bukhari = await client.mohdithRef.getMohdithById('256');
print(bukhari.name); // البخاري
// 3. Get multiple by IDs
final multipleScholars = await client.mohdithRef.getMohdithByIds([
'256', // al-Bukhari
'261', // Muslim
]);
// 4. List all with pagination
final allScholars = await client.mohdithRef.getAllMohdith(
limit: 50,
offset: 0,
);
// 5. Counts
final totalScholars = await client.mohdithRef.countMohdith();
final classicalScholars = await client.mohdithRef.countMohdith(
query: 'أحمد',
);
print('Total scholars: $totalScholars');
Rawi Reference Service (Offline) #
Search available narrators without internet.
// 1. Search by name
final narrators = await client.rawiRef.searchRawi('أبو هريرة', limit: 10);
// Or shortcut
final sameNarrators = await client.searchRawi('أبو هريرة');
for (var narrator in narrators) {
print(narrator.name);
}
// 2. Get by ID
final abuHurayrah = await client.rawiRef.getRawiById(1416);
print(abuHurayrah.name); // أبو هريرة عبد الرحمن بن صخر الدوسي
// 3. Get multiple by IDs
final multipleNarrators = await client.rawiRef.getRawiByIds([
1416, // Abu Hurayrah
5593, // Aishah
]);
// 4. List all with pagination
final allNarrators = await client.rawiRef.getAllRawi(
limit: 100,
offset: 0,
);
// 5. Counts
final totalNarrators = await client.rawiRef.countRawi();
final abdullahNarrators = await client.rawiRef.countRawi(query: 'عبد الله');
print('Total narrators: $totalNarrators');
print('"Abdullah" narrators: $abdullahNarrators');
Predefined Filter Constants #
The library provides predefined constants for common scholars, books, and narrators to simplify filtering.
MohdithReference (Scholars)
// 20 scholars
MohdithReference.all // All (no filter) - ID: 0
MohdithReference.malik // Imam Malik - ID: 179
MohdithReference.shafii // Imam al-Shafi'i - ID: 204
MohdithReference.ahmad // Imam Ahmad - ID: 241
MohdithReference.bukhari // al-Bukhari - ID: 256
MohdithReference.muslim // Muslim - ID: 261
MohdithReference.ibnMajah // Ibn Majah - ID: 273
MohdithReference.abuDawud // Abu Dawud - ID: 275
MohdithReference.tirmidhi // al-Tirmidhi - ID: 279
MohdithReference.nasai // al-Nasa'i - ID: 303
MohdithReference.sufyanThawri // Sufyan al-Thawri - ID: 161
MohdithReference.ibnMubarak // Abdullah b. al-Mubarak - ID: 181
MohdithReference.sufyanIbnUyaynah // Sufyan b. 'Uyaynah - ID: 198
MohdithReference.ishaqIbnRahawayh // Ishaq b. Rahawayh - ID: 238
MohdithReference.darimi // al-Darimi - ID: 250
MohdithReference.ibnKhuzaymah // Ibn Khuzaymah - ID: 311
MohdithReference.ibnHibban // Ibn Hibban - ID: 354
MohdithReference.hakim // al-Hakim - ID: 405
MohdithReference.bayhaqi // al-Bayhaqi - ID: 458
MohdithReference.tabarani // al-Tabarani - ID: 360
// Each value has id and name
final bukhari = MohdithReference.bukhari;
print(bukhari.id); // "256"
print(bukhari.name); // "البخاري"
// Use in filters
final params = HadithSearchParams(
value: 'الصلاة',
page: 1,
mohdith: [MohdithReference.bukhari],
);
// Get numeric id if needed
final bukhariId = int.parse(MohdithReference.bukhari.id);
BookReference (Books)
// 21 books
BookReference.all // All (no filter)
BookReference.sahihBukhari // Sahih al-Bukhari (6216)
BookReference.sahihMuslim // Sahih Muslim (3088)
BookReference.arbainNawawi // Al-Arba'in al-Nawawiyyah (13457)
BookReference.sahihMusnad // Al-Sahih al-Musnad (96)
BookReference.sunanAbuDawud // Sunan Abi Dawud (4549)
BookReference.jamiTirmidhi // Jami' al-Tirmidhi (3662)
BookReference.sunanNasai // Sunan al-Nasa'i (5766)
BookReference.sunanIbnMajah // Sunan Ibn Majah (5299)
BookReference.musnadAhmad // Musnad Ahmad (14)
BookReference.muwattaMalik // Muwatta' Malik (6453)
BookReference.musnadDarimi // Sunan al-Darimi (6277)
BookReference.sahihIbnKhuzaymah // Sahih Ibn Khuzaymah (3024)
BookReference.sahihIbnHibban // Sahih Ibn Hibban (5876)
BookReference.mustadrakHakim // Al-Mustadrak (2800)
BookReference.sunanBayhaqiKubra // Al-Sunan al-Kubra (7989)
BookReference.sunanDaraqutni // Sunan al-Daraqutni (3233)
BookReference.musannafIbnAbiShaybah // Musannaf Ibn Abi Shaybah (6598)
BookReference.musannafAbdRazzaq // Musannaf 'Abd al-Razzaq (7613)
BookReference.riyadSalihin // Riyad al-Salihin (10106)
BookReference.bulughMaram // Bulugh al-Maram (9927)
// Each value has id and name
final bukhari = BookReference.sahihBukhari;
print(bukhari.id); // "6216"
print(bukhari.name); // "صحيح البخاري"
// Use in filters
final params = HadithSearchParams(
value: 'الزكاة',
page: 1,
books: [
BookReference.sahihBukhari,
BookReference.sahihMuslim,
],
);
// Get numeric id if needed
final bukhariId = int.parse(BookReference.sahihBukhari.id);
RawiReference (Narrators)
Note: There are ~14,000 narrators in the database, so only some companions are provided as constants.
// 20 companions
RawiReference.all // All (no filter)
RawiReference.abuHurayrah // Abu Hurayrah (1416)
RawiReference.aisha // Aishah (6617)
RawiReference.ibnAbbas // Ibn Abbas (2664)
RawiReference.ibnUmar // Abdullah b. Umar (7687)
RawiReference.anasBinMalik // Anas b. Malik (2177)
RawiReference.jabirIbnAbdullah // Jabir b. Abdullah (3971)
RawiReference.abuSaidKhudri // Abu Sa'id al-Khudri (779)
RawiReference.ibnMasud // Abdullah b. Mas'ud (7918)
RawiReference.abuMusaAshari // Abu Musa al-Ash'ari (1342)
RawiReference.umarIbnKhattab // Umar b. al-Khattab (8918)
RawiReference.aliIbnAbiTalib // Ali b. Abi Talib (8637)
RawiReference.abuBakr // Abu Bakr al-Siddiq (455)
RawiReference.uthmanIbnAffan // Uthman b. Affan (8310)
RawiReference.salmanFarisi // Salman al-Farisi (5947)
RawiReference.muadhIbnJabal // Mu'adh b. Jabal (10349)
RawiReference.abuDharr // Abu Dharr al-Ghifari (667)
RawiReference.bilal // Bilal b. Rabah (3808)
RawiReference.zaydIbnThabit // Zayd b. Thabit (5545)
RawiReference.ubayyIbnKab // Ubayy b. Ka'b (1695)
RawiReference.abuAyyub // Abu Ayyub al-Ansari (129)
// Each value has id and name
final abuHurayrah = RawiReference.abuHurayrah;
print(abuHurayrah.id); // "1416"
print(abuHurayrah.name); // "أبو هريرة"
// Use in filters
final params = HadithSearchParams(
value: 'الجنة',
page: 1,
rawi: [RawiReference.abuHurayrah],
);
// Get numeric id if needed
final abuHurayrahId = int.parse(RawiReference.abuHurayrah.id);
// To find more narrators, use the search service
final narrators = await client.rawiRef.searchRawi('عبد الله', limit: 10);
Seeing "Unclosed database" Warning? #
Always call client.dispose():
final client = DorarClient();
try {
// Use the library
} finally {
await client.dispose(); // Mandatory
}
Or if you don't want to think about it, use DorarClient.use method instead, it will automatically dispose of the client after it finished:
final results = await DorarClient.use((client) async {
return await client.searchHadith(
HadithSearchParams(value: 'الصلاة', page: 1),
);
});
Migration (0.5.0) #
If you previously called configureFlutterAssetLoader or createFlutterConnectionFactory from dorar_hadith, switch to dorar_hadith_flutter:
await DorarHadithFlutter.ensureInitialized();
Those helpers were removed from the core package in 0.5.0.
Contributing #
All forms of contributions are welcome.
License #
MIT License - see LICENSE file.
Architecture #
DorarClient (Facade)
├── HadithService
├── SharhService
├── BookService
├── MohdithService
├── MohdithRefService
├── BookRefService
└── RawiRefService
└── HTTP Client + Cache
└── HTML Parsers
— May Allah reward us and you.