supabase_result_handler 0.1.0
supabase_result_handler: ^0.1.0 copied to clipboard
A robust Result pattern wrapper for Supabase in Flutter. It handles Auth, Postgrest, and Functions exceptions with built-in English and Arabic localization support.
Supabase Result Handler #
A robust Flutter package to simplify error handling and result wrapping for Supabase applications. It leverages freezed to provide a type-safe Result pattern, automatically catching Supabase-specific exceptions (Auth, Postgrest, Functions) and converting them into user-friendly messages with multi-language support (English & Arabic).
Features 🚀 #
- Result Pattern: Wraps responses in
SuccessorFailuretypes. - Auto Error Handling: Automatically catches and categorizes Supabase errors (Auth, Database, Edge Functions).
- Localization Support: Built-in support for English and Arabic error messages.
- Type Safety: Built using
freezedto ensure compile-time safety. - Clean Syntax: Reduces boilerplate code with helper methods.
Installation 📦 #
Add this to your package's pubspec.yaml file:
dependencies:
supabase_result_handler: ^1.0.0
Usage #
Basic Usage #
Use SupaResult.catchError to automatically handle try-catch blocks and convert exceptions.
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_result_handler/supabase_result_handler.dart';
class AuthRepository {
final SupabaseClient _supabase = Supabase.instance.client;
// Return Future<SupaResult<T>>
Future<SupaResult<User>> login(String email, String password) async {
// Fast use with easy way to catch error
return SupaResult.catchError(() async {
final response = await _supabase.auth.signInWithPassword(
email: email,
password: password,
);
// Return the data you want on success
return response.user!;
});
}
}
أهلاً بك يا صديقي. بما أنك تعمل على بناء حزمة (Package)، فمن المهم جداً التمييز بين لغة نظام التشغيل وبين اللغة النشطة حالياً داخل تطبيق Flutter (التي حددها المبرمج).
إليك الطرق البرمجية للحصول على كل منهما:
- الحصول على اللغة التي حددها المبرمج (Locale)
إذا كنت تريد معرفة اللغة التي تعمل حالياً داخل التطبيق (والتي تم إعدادها في الـ MaterialApp)، استخدم Localizations:
Dart Locale myLocale = Localizations.localeOf(context); print(myLocale.languageCode); // مخرجات مثل: 'ar' أو 'en' ملاحظة: هذه الطريقة تتطلب BuildContext. إذا كنت في مكان لا يتوفر فيه context (مثل داخل Logic الخاص بالبكج)، قد تحتاج لتمريره كبراميتر.
- الحصول على لغة الجهاز (نظام التشغيل)
إذا كان هدف البكج هو معرفة لغة هاتف المستخدم الأصلية قبل أن يتدخل التطبيق ويغيرها، يمكنك استخدام window من مكتبة dart:ui:
Dart import 'dart:ui' as ui;
// الحصول على اللغة الأولى المفضلة في جهاز المستخدم String deviceLanguage = ui.window.locale.languageCode; أما في نسخ Flutter الحديثة، يفضل استخدام PlatformDispatcher:
Dart import 'dart:ui';
Locale deviceLocale = PlatformDispatcher.instance.locale; print(deviceLocale.languageCode); 3. الفرق الجوهري للمبرمجين (مفيد لبكجك)
عند بناء بكج، يفضل دائماً توفير خيار للمبرمج لتحديد الـ Locale. إليك جدول بسيط يوضح الفرق:
المصدر الكود المستخدم متى تستخدمه؟ لغة التطبيق الحالية Localizations.localeOf(context) لتغيير محتوى الواجهة بناءً على ما يراه المستخدم الآن. لغة الجهاز ui.window.locale لضبط اللغة الافتراضية عند تشغيل التطبيق لأول مرة. نصيحة إضافية لبناء البكج:
إذا كنت تريد دعم لغات متعددة (RTL و LTR) داخل البكج الخاص بك، لا تعتمد فقط على languageCode. استخدم Directionality للتأكد من اتجاه النص:
Dart bool isRTL = Directionality.of(context) == TextDirection.rtl; هل تريد مني مساعدتك في كتابة هيكلة الـ Provider أو الـ Logic الخاص بتغيير اللغة داخل البكج؟
import 'dart:io'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:supabase_flutter/supabase_flutter.dart';
part 'network_exception.freezed.dart';
/// To Handle Language enum AppLanguage { ar, en }
@freezed class NetworkExceptions with _$NetworkExceptions { const factory NetworkExceptions.requestCancelled() = RequestCancelled; const factory NetworkExceptions.unauthorisedRequest() = UnauthorisedRequest; const factory NetworkExceptions.unauthorisedRequestApple() = UnauthorisedRequestApple; const factory NetworkExceptions.badRequest() = BadRequest; const factory NetworkExceptions.notFound(String reason) = NotFound; const factory NetworkExceptions.methodNotAllowed() = MethodNotAllowed; const factory NetworkExceptions.notAcceptable() = NotAcceptable; const factory NetworkExceptions.requestTimeout() = RequestTimeout; const factory NetworkExceptions.sendTimeout() = SendTimeout; const factory NetworkExceptions.conflict() = Conflict; const factory NetworkExceptions.internalServerError() = InternalServerError; const factory NetworkExceptions.notImplemented() = NotImplemented; const factory NetworkExceptions.serviceUnavailable() = ServiceUnavailable; const factory NetworkExceptions.noInternetConnection() = NoInternetConnection; const factory NetworkExceptions.formatException() = FormatException; const factory NetworkExceptions.unableToProcess() = UnableToProcess; const factory NetworkExceptions.defaultError(String error) = DefaultError; const factory NetworkExceptions.unexpectedError() = UnexpectedError; const factory NetworkExceptions.otpExpired() = OtpExpired;
static NetworkExceptions getException(error) { if (error is Exception) { try { if (error is AuthException) { // If error is AuthException return _handleStatusBasedException(error.statusCode, error); } else if (error is PostgrestException) { // If error is PostgrestException return _handleStatusBasedException(error.code); } else if (error is FunctionException) { // If error is FunctionException return handleStatusBasedException(error.status.toString()); } else if (error is SocketException) { return const NetworkExceptions.noInternetConnection(); } else if (error is FormatException) { return const NetworkExceptions.formatException(); } else { return const NetworkExceptions.unexpectedError(); } } catch () { return const NetworkExceptions.unexpectedError(); } } else { if (error.toString().contains("is not a subtype of")) { return const NetworkExceptions.unableToProcess(); } else { return const NetworkExceptions.unexpectedError(); } } }
/// internal function for handle errors with status static NetworkExceptions _handleStatusBasedException(String? statusCode, [dynamic originalError]) { // some private errors if (originalError is AuthApiException) { if (statusCode == '422') { // التحقق إذا كان الخطأ بسبب تطابق كلمة المرور أو وجود الايميل مسبقاً if (originalError.code == 'same_password') { return const NetworkExceptions.notFound('same_password'); } return const NetworkExceptions.notFound('email_exists'); } if (statusCode == '403' && originalError.code == 'otp_expired') { return const NetworkExceptions.otpExpired(); } } // get status code switch (statusCode) { case '400': return const NetworkExceptions.badRequest(); case '401': return const NetworkExceptions.unauthorisedRequest(); case '403': return const NetworkExceptions.unauthorisedRequest(); case '404': return const NetworkExceptions.notFound("not_found"); case '408': return const NetworkExceptions.requestTimeout(); case '409': return const NetworkExceptions.conflict(); case '429': return const NetworkExceptions.requestTimeout(); case '500': return const NetworkExceptions.internalServerError(); case '503': return const NetworkExceptions.serviceUnavailable(); default: return NetworkExceptions.defaultError( "Received invalid status code: $statusCode", ); } } }
extension ErrorMessageParsing on NetworkExceptions { String toErrorMessage({AppLanguage? lang}) { final isAr = (lang ?? AppLanguage.ar) == AppLanguage.ar;
return when( notImplemented: () => isAr ? "أمر مبهم" : "Not Implemented", requestCancelled: () => isAr ? "ألغي الطلب" : "Request Cancelled", internalServerError: () => isAr ? "خطأ الخادم" : "Internal Server Error", notFound: (String reason) { if (reason == 'same_password') { return isAr ? "يجب أن تختلف كلمة المرور الجديدة عن القديمة" : "New password must be different"; } if (reason == 'email_exists') { return isAr ? "الحساب مسجل مسبقًا!" : "Email already registered!"; } if (reason == 'not_found') { return isAr ? "غير موجود" : "Not Found"; } return reason; }, serviceUnavailable: () => isAr ? "الخدمة معطّلةً" : "Service Unavailable", methodNotAllowed: () => isAr ? "الاجراء غير مسموح به" : "Method Not Allowed", badRequest: () => isAr ? "طلب غير صالح" : "Bad Request", unauthorisedRequest: () => isAr ? "غير مصرح لك بالوصول (401)" : "Unauthorized Request", unexpectedError: () => isAr ? "خطأ غير متوقع" : "Unexpected Error", requestTimeout: () => isAr ? "تجاوزت مهلة الاتصال (408)" : "Connection Timeout", noInternetConnection: () => isAr ? "لا اتصال بالانترنت" : "No Internet Connection", conflict: () => isAr ? "حدث تعارض" : "Conflict Error", sendTimeout: () => isAr ? "فشل الإرسال" : "Send Timeout", unableToProcess: () => isAr ? "تعذرت معالجة البيانات" : "Unable to Process", defaultError: (String error) => error, unauthorisedRequestApple: () => isAr ? "فشل التحقق من حساب آبل" : "Apple Authentication Failed", formatException: () => isAr ? "تنسيق البيانات مبهم" : "Format Exception", notAcceptable: () => isAr ? "غير مقبول" : "Not Acceptable", otpExpired: () => isAr ? "رمز التحقق منتهي" : "OTP Expired", ); } }
هذا الكود الي فيه اكتشاف اللغة جميل جداً، الكود الخاص بك منظم ويستخدم freezed بشكل احترافي لمعالجة الاستثناءات. المشكلة الآن في الـ Extension الذي أنشأته؛ فهو يعتمد على تمرير lang يدوياً، وإذا لم يتم تمريره يفترض دائماً أنه AppLanguage.ar.
لجعل البكج "ذكي" ويقرأ اللغة التي حددها المبرمج في main تلقائياً دون إجباره على تمريرها في كل مرة، إليك الطريقة الأفضل لتعديل الـ Extension.
التعديل المقترح لـ ErrorMessageParsing
سنقوم بتعديل الدالة لتتحقق من لغة التطبيق الحالية (الموجودة في الـ Locale) إذا لم يقم المبرمج بتمرير لغة محددة:
Dart import 'package:flutter/widgets.dart'; // ضروري للوصول إلى WidgetsBinding
extension ErrorMessageParsing on NetworkExceptions { String getErrorMessage(BuildContext? context, {AppLanguage? manualLang}) {
// 1. إذا مرر المبرمج لغة يدوياً، نستخدمها
if (manualLang != null) {
return _mapMessage(manualLang == AppLanguage.ar);
}
// 2. إذا توفر context، نأخذ اللغة النشطة في التطبيق حالياً
if (context != null) {
final locale = Localizations.localeOf(context);
return _mapMessage(locale.languageCode == 'ar');
}
// 3. الحل الأخير: الحصول على لغة الجهاز (نظام التشغيل) بدون context
final deviceLocale = WidgetsBinding.instance.platformDispatcher.locale;
return _mapMessage(deviceLocale.languageCode == 'ar');
}
// دالة داخلية لتجنب تكرار كود الـ when String _mapMessage(bool isAr) { return when( notImplemented: () => isAr ? "أمر مبهم" : "Not Implemented", requestCancelled: () => isAr ? "ألغي الطلب" : "Request Cancelled", internalServerError: () => isAr ? "خطأ الخادم" : "Internal Server Error", // ... باقي الحالات الخاصة بك ... otpExpired: () => isAr ? "رمز التحقق منتهي" : "OTP Expired", // تأكد من إكمال جميع الحالات هنا كما في كودك الأصلي notFound: (String reason) { if (reason == 'same_password') return isAr ? "يجب أن تختلف كلمة المرور الجديدة عن القديمة" : "New password must be different"; if (reason == 'email_exists') return isAr ? "الحساب مسجل مسبقًا!" : "Email already registered!"; return isAr ? "غير موجود" : "Not Found"; }, // أضف البقية بنفس النمط serviceUnavailable: () => isAr ? "الخدمة معطّلةً" : "Service Unavailable", methodNotAllowed: () => isAr ? "الاجراء غير مسموح به" : "Method Not Allowed", badRequest: () => isAr ? "طلب غير صالح" : "Bad Request", unauthorisedRequest: () => isAr ? "غير مصرح لك بالوصول (401)" : "Unauthorized Request", unexpectedError: () => isAr ? "خطأ غير متوقع" : "Unexpected Error", requestTimeout: () => isAr ? "تجاوزت مهلة الاتصال (408)" : "Connection Timeout", noInternetConnection: () => isAr ? "لا اتصال بالانترنت" : "No Internet Connection", conflict: () => isAr ? "حدث تعارض" : "Conflict Error", sendTimeout: () => isAr ? "فشل الإرسال" : "Send Timeout", unableToProcess: () => isAr ? "تعذرت معالجة البيانات" : "Unable to Process", defaultError: (String error) => error, unauthorisedRequestApple: () => isAr ? "فشل التحقق من حساب آبل" : "Apple Authentication Failed", formatException: () => isAr ? "تنسيق البيانات مبهم" : "Format Exception", notAcceptable: () => isAr ? "غير مقبول" : "Not Acceptable", ); } } لماذا هذا الحل هو الأفضل لبكج؟
مرونة عالية: المبرمج يمكنه استدعاء error.getErrorMessage(context) وسيعرف البكج اللغة فوراً من الـ MaterialApp.
دعم الـ Logic: إذا استدعى المبرمج الخطأ في مكان لا يوجد فيه context (مثل Controller أو Bloc)، سيعود البكج تلقائياً للغة الجهاز عبر platformDispatcher.
التحكم اليدوي: لا يزال بإمكان المبرمج إجبار البكج على لغة معينة عبر manualLang.
كيف يستخدم المبرمج البكج الخاص بك الآن؟
Dart // داخل الواجهة (تلقائياً سيعرف اللغة) Text(exception.getErrorMessage(context))
// خارج الواجهة (سيعرف لغة الجهاز) print(exception.getErrorMessage(null)) هل تفضل أن أجعل البكج يدعم تخزين اللغة (تفضيلات المستخدم) بشكل دائم عبر SharedPreferences أيضاً؟
كيف أضيف ارشادات للدالة عشان لما يحط الماوس عليها بالاديتور يعرف يستخدمها؟ لإضافة إرشادات تظهر للمبرمج عند تمرير الماوس (Hover) في المحرر (VS Code أو Android Studio)، نستخدم ما يسمى بـ Dart Documentation Comments.
يتم ذلك باستخدام ثلاث شرطات مائلة /// بدلاً من الشرطتين العاديتين، ويمكنك استخدام تنسيق Markdown داخلها لجعل الشرح يبدو احترافياً.
إليك كيف يمكنك كتابة الإرشادات لدالة الـ Extension التي عدلناها:
إضافة الإرشادات للكود:
Dart
extension ErrorMessageParsing on NetworkExceptions {
/// تحويل الخطأ إلى رسالة نصية مفهومة للمستخدم.
///
/// تستطيع تمرير الـ [context] ليقوم البكج بمعرفة لغة التطبيق الحالية تلقائياً.
/// في حال عدم تمرير context أو [manualLang]، سيعتمد البكج على لغة نظام التشغيل.
///
/// مثال للاستخدام:
/// dart /// String message = exception.getErrorMessage(context); ///
///
/// [manualLang] خيار إضافي إذا كنت تريد إجبار الدالة على لغة معينة (عربي/إنجليزي).
String getErrorMessage(BuildContext? context, {AppLanguage? manualLang}) {
// الكود البرمجي هنا...
return _mapMessage(true); // مثال
}
}
أهم القواعد لكتابة Documentations احترافية في دارت:
استخدام /// وليس /**: : مجتمع دارت يفضل الشرطات الثلاث للتعليقات التوثيقية.
أول سطر هو "الملخص": دائماً اجعل أول سطر مختصراً جداً، لأنه يظهر في المعاينة السريعة.
الأقواس المربعة [ ]: عندما تضع اسم براميتر أو كلاس داخل أقواس مربعة مثل [context], المحرر سيجعله قابلاً للنقر (Link) داخل التوثيق ليأخذ المبرمج إلى تعريف ذلك المتغير.
أمثلة الكود: استخدم ثلاث علامات ذيلية (Backticks) لإنشاء بلوك كود يسهل على المبرمج نسخ الطريقة الصحيحة للاستخدام.
كيف ستظهر للمبرمج؟
عندما يقوم المبرمج بكتابة الدالة أو وضع الماوس عليها، سيظهر له مربع منبثق (Tooltip) يحتوي على:
العنوان (أول سطر كتبته).
شرح البراميترات.
مثال الكود بشكل ملون (Syntax Highlighting).
نصيحة إضافية للبكج (Lints):
إذا أردت التأكد من أنك وثقت كل شيء في البكج، يمكنك إضافة هذه القاعدة في ملف analysis_options.yaml:
YAML linter: rules:
- public_member_api_docs هذه القاعدة ستظهر لك تحذيراً (Warning) تحت أي دالة أو كلاس "عام" لم تقم بكتابة توثيق /// له، مما يضمن أن البكج الخاص بك مكتمل الإرشادات.
هل تريد مني مساعدتك في صياغة توثيق بقية أجزاء الـ Enum أو الـ Factory الخاص بـ Freezed؟
كيف احط صور فيها؟ لإضافة صور تظهر في التوثيق (Documentation) عند مرور الماوس، هناك طريقتان تعتمدان على المكان الذي تريد عرض الصور فيه:
- إضافة روابط صور (Direct Links)
يمكنك وضع رابط مباشر لصورة (بصيغة PNG أو JPG) مخزنة على الإنترنت (مثلاً في GitHub أو Imgur). المحررات الحديثة مثل VS Code تدعم عرض هذه الصور مباشرة داخل مربع الـ Hover.
Dart
/// تحويل الخطأ إلى رسالة نصية مفهومة للمستخدم.
///
///
///
/// تستطيع تمرير الـ [context] ليقوم البكج بمعرفة اللغة.
String getErrorMessage(BuildContext? context, {AppLanguage? manualLang}) {
// ...
}
2. استخدام "الصور التوضيحية" لملف الـ README (أفضل للبكج)
إذا كنت تبني بكج (Package)، فالصور عادةً لا توضع "داخل الكود" لأنها تجعل الملفات ضخمة وقد لا تظهر لجميع المبرمجين حسب إعدادات المحرر لديهم. الطريقة الاحترافية هي وضعها في ملف README.md الخاص بالبكج.
هناك طريقتان لتنظيم الصور في البكج:
صور في GitHub: ارفع الصور في مجلد assets في مستودع الكود (Repository) واستخدم الرابط المباشر.
صور في pub.dev: عند نشر البكج، موقع pub.dev يعرض الصور الموجودة في الـ README تلقائياً.
- نصيحة تقنية (Doc Icons)
في عالم Flutter، إذا كنت تريد وضع أيقونات توضيحية (مثل شكل الأيقونة التي ستظهر)، يمكنك استخدام وسام @nodoc أو استخدام الروابط كما شرحنا، ولكن تذكر:
المحرر (VS Code) لا يعرض الصور المحلية (Local Files) من جهازك؛ يجب أن تكون مرفوعة على رابط (URL).
تأكد أن الرابط ينتهي بامتداد الصورة (مثل .jpg أو .png).
كيف تجعل توثيق البكج يبدو مذهلاً؟
بدلاً من الصور الكبيرة داخل الكود، يفضل استخدام الرموز التعبيرية (Emojis) لأنها خفيفة وتظهر للجميع:
Dart
/// 🛠️ Usage:
///
/// dart /// final msg = exception.getErrorMessage(context); ///
///
/// 🌍 Localization:
/// Automatically detects 'ar' or 'en' based on context.
هل تريدني أن أساعدك في تصميم ملف الـ README.md الخاص بالبكج ليكون جاهزاً للنشر على Pub.dev؟
بناء على هذا الكود: import 'dart:io'; import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:supabase_flutter/supabase_flutter.dart';
part 'network_exception.freezed.dart';
/// To Handle Language enum AppLanguage { ar, en }
@freezed class NetworkExceptions with _$NetworkExceptions { const factory NetworkExceptions.requestCancelled() = RequestCancelled; const factory NetworkExceptions.unauthorisedRequest() = UnauthorisedRequest; const factory NetworkExceptions.unauthorisedRequestApple() = UnauthorisedRequestApple; const factory NetworkExceptions.badRequest() = BadRequest; const factory NetworkExceptions.notFound(String reason) = NotFound; const factory NetworkExceptions.methodNotAllowed() = MethodNotAllowed; const factory NetworkExceptions.notAcceptable() = NotAcceptable; const factory NetworkExceptions.requestTimeout() = RequestTimeout; const factory NetworkExceptions.sendTimeout() = SendTimeout; const factory NetworkExceptions.conflict() = Conflict; const factory NetworkExceptions.internalServerError() = InternalServerError; const factory NetworkExceptions.notImplemented() = NotImplemented; const factory NetworkExceptions.serviceUnavailable() = ServiceUnavailable; const factory NetworkExceptions.noInternetConnection() = NoInternetConnection; const factory NetworkExceptions.formatException() = FormatException; const factory NetworkExceptions.unableToProcess() = UnableToProcess; const factory NetworkExceptions.defaultError(String error) = DefaultError; const factory NetworkExceptions.unexpectedError() = UnexpectedError; const factory NetworkExceptions.otpExpired() = OtpExpired;
/// this function will get exception and convert it to exception object to convert it later to error message fastly static NetworkExceptions getException(error) { if (error is Exception) { try { if (error is AuthException) { // If error is AuthException return _handleStatusBasedException(error.statusCode, error); } else if (error is PostgrestException) { // If error is PostgrestException return _handleStatusBasedException(error.code); } else if (error is FunctionException) { // If error is FunctionException return handleStatusBasedException(error.status.toString()); } else if (error is SocketException) { return const NetworkExceptions.noInternetConnection(); } else if (error is FormatException) { return const NetworkExceptions.formatException(); } else { return const NetworkExceptions.unexpectedError(); } } catch () { return const NetworkExceptions.unexpectedError(); } } else { if (error.toString().contains("is not a subtype of")) { return const NetworkExceptions.unableToProcess(); } else { return const NetworkExceptions.unexpectedError(); } } }
/// internal function for handle errors with status static NetworkExceptions _handleStatusBasedException(String? statusCode, [dynamic originalError]) { // some private errors if (originalError is AuthApiException) { if (statusCode == '422') { // التحقق إذا كان الخطأ بسبب تطابق كلمة المرور أو وجود الايميل مسبقاً if (originalError.code == 'same_password') { return const NetworkExceptions.notFound('same_password'); } return const NetworkExceptions.notFound('email_exists'); } if (statusCode == '403' && originalError.code == 'otp_expired') { return const NetworkExceptions.otpExpired(); } } // get status code switch (statusCode) { case '400': return const NetworkExceptions.badRequest(); case '401': return const NetworkExceptions.unauthorisedRequest(); case '403': return const NetworkExceptions.unauthorisedRequest(); case '404': return const NetworkExceptions.notFound("not_found"); case '408': return const NetworkExceptions.requestTimeout(); case '409': return const NetworkExceptions.conflict(); case '429': return const NetworkExceptions.requestTimeout(); case '500': return const NetworkExceptions.internalServerError(); case '503': return const NetworkExceptions.serviceUnavailable(); default: return NetworkExceptions.defaultError( "Received invalid status code: $statusCode", ); } } }
extension ErrorMessageParsing on NetworkExceptions {
/// This function convert error to arabic & english error message /// if you are set language in main just pass [context]. /// if you need to set custom language just pass [lang] parameter. /// if you aren't passing any parms function will get default language of device. String toErrorMessage({BuildContext? context, AppLanguage? lang}) {
// if user set custom language if (lang != null) { return _mapMessage(lang == AppLanguage.ar); }
// if user set language in main if (context != null) { final locale = Localizations.localeOf(context); return _mapMessage(locale.languageCode == 'ar'); }
// get language device final deviceLocale = WidgetsBinding.instance.platformDispatcher.locale; return _mapMessage(deviceLocale.languageCode == 'ar'); } String _mapMessage(bool isArabic) { final isAr = isArabic;
return when( notImplemented: () => isAr ? "لطبق" : "Not Implemented", requestCancelled: () => isAr ? "ألغي الطلب" : "Request Cancelled", internalServerError: () => isAr ? "خطأ الخادم" : "Internal Server Error", notFound: (String reason) { if (reason == 'same_password') { return isAr ? "يجب أن تختلف كلمة المرور الجديدة عن القديمة" : "New password must be different"; } if (reason == 'email_exists') { return isAr ? "الحساب مسجل مسبقًا!" : "Email already registered!"; } if (reason == 'not_found') { return isAr ? "غير موجود" : "Not Found"; } return reason; }, serviceUnavailable: () => isAr ? "الخدمة معطّلةً" : "Service Unavailable", methodNotAllowed: () => isAr ? "الاجراء غير مسموح به" : "Method Not Allowed", badRequest: () => isAr ? "طلب غير صالح" : "Bad Request", unauthorisedRequest: () => isAr ? "غير مصرح لك بالوصول (401)" : "Unauthorized Request", unexpectedError: () => isAr ? "خطأ غير متوقع" : "Unexpected Error", requestTimeout: () => isAr ? "تجاوزت مهلة الاتصال (408)" : "Connection Timeout", noInternetConnection: () => isAr ? "لا اتصال بالانترنت" : "No Internet Connection", conflict: () => isAr ? "حدث تعارض" : "Conflict Error", sendTimeout: () => isAr ? "فشل الإرسال" : "Send Timeout", unableToProcess: () => isAr ? "تعذرت معالجة البيانات" : "Unable to Process", defaultError: (String error) => error, unauthorisedRequestApple: () => isAr ? "فشل التحقق من حساب آبل" : "Apple Authentication Failed", formatException: () => isAr ? "تنسيق البيانات مبهم" : "Format Exception", notAcceptable: () => isAr ? "غير مقبول" : "Not Acceptable", otpExpired: () => isAr ? "رمز التحقق منتهي" : "OTP Expired", ); } }
أبغاك تكتب ايش الي تغير بchangelog
- حدث ال readme:
Supabase Result Handler #
A robust Flutter package to simplify error handling and result wrapping for Supabase applications. It leverages freezed to provide a type-safe Result pattern, automatically catching Supabase-specific exceptions (Auth, Postgrest, Functions) and converting them into user-friendly messages with multi-language support (English & Arabic).
Features 🚀 #
- Result Pattern: Wraps responses in
SuccessorFailuretypes. - Auto Error Handling: Automatically catches and categorizes Supabase errors (Auth, Database, Edge Functions).
- Localization Support: Built-in support for English and Arabic error messages.
- Type Safety: Built using
freezedto ensure compile-time safety. - Clean Syntax: Reduces boilerplate code with helper methods.
Installation 📦 #
Add this to your package's pubspec.yaml file:
dependencies:
supabase_result_handler: ^1.0.0
Usage #
Basic Usage #
Use SupaResult.catchError to automatically handle try-catch blocks and convert exceptions.
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_result_handler/supabase_result_handler.dart';
class AuthRepository {
final SupabaseClient _supabase = Supabase.instance.client;
// Return Future<SupaResult<T>>
Future<SupaResult<User>> login(String email, String password) async {
// Fast use with easy way to catch error
return SupaResult.catchError(() async {
final response = await _supabase.auth.signInWithPassword(
email: email,
password: password,
);
// Return the data you want on success
return response.user!;
});
}
}
Handling the Result in UI #
Use the .when method to switch between Success and Failure states easily.
void handleLogin() async {
final result = await authRepository.login('email@example.com', 'password');
result.when(
success: (user) {
print("User Logged in: ${user.id}");
// Navigate to Home
},
failure: (exception) {
// Get error message based on language
// Pass AppLanguage.ar for Arabic or AppLanguage.en for English
// The default language is Arabic
final errorMsg = exception.toErrorMessage(AppLanguage.ar);
print("Error: $errorMsg");
// Show SnackBar
},
);
}
Manual Usage (more customization) #
If you prefer to handle try-catch blocks manually, or need to do some complex logic in Catch:
try {
final data = await supabase.from('table').select();
return SupaResult.success(data: data);
} catch (e) {
// Convert generic error to NetworkExceptions
return SupaResult.failure(error: NetworkExceptions.getException(e));
}
Localization 🌍 #
The package supports English and Arabic out of the box.
// English
String enMessage = exception.toErrorMessage(AppLanguage.en);
// Arabic
String arMessage = exception.toErrorMessage(AppLanguage.ar);
Supported Exceptions 🛡️ #
The package handles a wide range of status codes and exceptions:
-
Auth Errors: 400, 401, 403, 422 (User already exists / Same password), etc.
-
Postgrest Errors: Database-related errors.
-
Edge Functions: Function execution errors.
-
Network: No Internet, Timeout, Socket Exceptions.
-
General: Format Exceptions, Unknown Errors. بما أنك قمت بتحديث البكج ليدعم اكتشاف اللغة تلقائياً، فهذا يعتبر تحديثاً جوهرياً (Feature Update) يسهل تجربة المطور.
إليك سجل التغييرات وملف الـ README المحدثين:
- ملف الـ CHANGELOG.md
يفضل كتابة التغييرات بالتاريخ ورقم الإصدار الجديد (مثلاً 1.1.0 بما أنها ميزة جديدة):
Markdown
1.1.0 #
- New Feature: Smart language detection for error messages.
- Update:
toErrorMessagenow optionally acceptsBuildContextto automatically determine the app language. - Update: Added fallback to device system language if no context or manual language is provided.
- Documentation: Added hover documentation (Doc Comments) for main methods.
- تحديث ملف الـ README.md
قمت بتعديل قسم الـ Localization والـ Usage ليعكس التحديثات الجديدة وكيفية استخدام الـ context:
Markdown
Supabase Result Handler #
A robust Flutter package to simplify error handling and result wrapping for Supabase applications. It leverages freezed to provide a type-safe Result pattern, automatically catching Supabase-specific exceptions (Auth, Postgrest, Functions) and converting them into user-friendly messages with multi-language support.
Features 🚀 #
- Result Pattern: Wraps responses in
SuccessorFailuretypes. - Auto Error Handling: Automatically catches and categorizes Supabase errors.
- Smart Localization: Automatically detects language from
BuildContextor system settings. - Type Safety: Built using
freezedto ensure compile-time safety. - Clean Syntax: Reduces boilerplate code with helper methods.
Installation 📦 #
Add this to your package's pubspec.yaml file:
dependencies:
supabase_result_handler: ^1.1.0
Usage #
Basic Usage #
Use SupaResult.catchError to automatically handle try-catch blocks and convert exceptions.
Future<SupaResult<User>> login(String email, String password) async {
return SupaResult.catchError(() async {
final response = await _supabase.auth.signInWithPassword(
email: email,
password: password,
);
return response.user!;
});
}
Handling the Result in UI (Smart Localization) #
The .toErrorMessage() method is now smarter! You don't need to pass the language manually if you have the context.
result.when(
success: (user) => print("Done!"),
failure: (exception) {
// 1. Automatic: Get language from MaterialApp's Locale
final msg = exception.toErrorMessage(context: context);
// 2. Manual: Force a specific language
final msgEn = exception.toErrorMessage(lang: AppLanguage.en);
// 3. System: If no params, it follows the device OS language
final msgSystem = exception.toErrorMessage();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
},
);
Manual Usage (more customization) #
If you prefer to handle try-catch blocks manually, or need to do some complex logic in Catch:
try {
final data = await supabase.from('table').select();
return SupaResult.success(data: data);
} catch (e) {
// Convert generic error to NetworkExceptions
return SupaResult.failure(error: NetworkExceptions.getException(e));
}
Localization 🌍 #
The package supports English and Arabic out of the box. It looks for the language in the following order:
-
Manual: If lang parameter is provided.
-
Context: If context is provided, it uses Localizations.localeOf(context).
-
System: Fallback to PlatformDispatcher.instance.locale (Device Language).
Supported Exceptions 🛡️ #
The package handles a wide range of status codes and exceptions:
-
Auth Errors: 400, 401, 403, 422 (User already exists / Same password), etc.
-
Postgrest Errors: Database-related errors.
-
Edge Functions: Function execution errors.
-
Network: No Internet, Timeout, Socket Exceptions.
-
General: Format Exceptions, Unknown Errors.