021-88881776

آموزش کار با دیتابیس و API در Flutter

در آموزش Flutter، کار با دیتابیس و API در Flutter یکی از مهارت‌های اساسی و ضروری برای ایجاد اپلیکیشن‌های قدرتمند و پویا محسوب می‌شود. این مقاله آموزشی جامع، تمامی جنبه‌های این موضوع را از سطح مبتدی تا پیشرفته پوشش می‌دهد و به زبان ساده و قابل فهم برای مبتدیان توضیح می‌دهد.

تعامل با وب‌سرویس‌ها

در کار با دیتابیس و API در Flutter، تعامل با وب‌سرویس‌ها یکی از بخش‌های اساسی و حیاتی است. وب‌سرویس‌ها به اپلیکیشن‌های شما امکان می‌دهند تا با سرورها و سرویس‌های خارجی ارتباط برقرار کرده، داده‌ها را دریافت و ارسال کنند و به این ترتیب عملکرد و قابلیت‌های اپلیکیشن خود را گسترش دهید. در این بخش به معرفی وب‌سرویس‌ها و انواع مختلف آن‌ها می‌پردازیم تا پایه‌ای محکم برای درک بهتر کار با دیتابیس و API در Flutter فراهم شود.

معرفی وب‌سرویس‌ها

وب‌سرویس‌ها (Web Services) مجموعه‌ای از استانداردها و پروتکل‌ها هستند که به اپلیکیشن‌ها اجازه می‌دهند تا از طریق اینترنت با یکدیگر ارتباط برقرار کرده و داده‌ها را تبادل نمایند. این ارتباط معمولاً از طریق پروتکل‌های HTTP یا HTTPS صورت می‌گیرد و به اپلیکیشن‌ها امکان می‌دهد تا بدون نیاز به دانستن جزئیات داخلی سرویس‌دهنده، از قابلیت‌های آن بهره‌مند شوند.

چرا وب‌سرویس‌ها مهم هستند؟

قابلیت انعطاف‌پذیری: وب‌سرویس‌ها به اپلیکیشن‌ها اجازه می‌دهند تا به منابع مختلف دسترسی پیدا کنند و از آن‌ها استفاده کنند بدون نیاز به یکپارچه‌سازی پیچیده.
مقیاس‌پذیری: با استفاده از وب‌سرویس‌ها، می‌توان قابلیت‌های اپلیکیشن را به راحتی گسترش داد و به سرویس‌های جدید اضافه کرد.
مستقل بودن پلتفرم: وب‌سرویس‌ها معمولاً مستقل از زبان برنامه‌نویسی و پلتفرم هستند، به این معنی که می‌توانند توسط اپلیکیشن‌های مختلف و بر روی سیستم‌عامل‌های متفاوت مصرف شوند.
به‌روزرسانی آسان: تغییرات در سرویس‌دهنده‌ها بدون نیاز به تغییرات گسترده در اپلیکیشن‌های مصرف‌کننده قابل اعمال است.

چگونه وب‌سرویس‌ها کار می‌کنند؟

وب‌سرویس‌ها از طریق درخواست‌ها و پاسخ‌ها عمل می‌کنند. اپلیکیشن کلاینت (مانند اپلیکیشن Flutter شما) درخواست‌هایی را به سرویس‌دهنده ارسال می‌کند و سرویس‌دهنده پاسخ‌هایی را بر اساس درخواست‌ها ارائه می‌دهد. این درخواست‌ها معمولاً شامل عملیات‌هایی مانند دریافت داده‌ها، ارسال داده‌ها، به‌روزرسانی داده‌ها یا حذف داده‌ها هستند.

انواع وب‌سرویس‌ها

وب‌سرویس‌ها به دو دسته اصلی RESTful و SOAP تقسیم می‌شوند. هر کدام از این نوع‌ها ویژگی‌ها و کاربردهای خاص خود را دارند که در ادامه به بررسی آن‌ها می‌پردازیم.

وب‌سرویس‌هایRESTful

RESTful مخفف Representational State Transfer است و یکی از محبوب‌ترین سبک‌های طراحی وب‌سرویس‌ها محسوب می‌شود. وب‌سرویس‌های RESTful از پروتکل HTTP برای ارتباط استفاده می‌کنند و به دلیل سادگی و کارایی بالا در توسعه اپلیکیشن‌های مدرن، به ویژه در Flutter بسیار مورد استفاده قرار می‌گیرند.

ویژگی‌های وب‌سرویس‌های RESTful:
استفاده از HTTP Methods: RESTful از متدهای HTTP مانند GET، POST، PUT، DELETE برای انجام عملیات‌های مختلف استفاده می‌کند. هر متد نقش خاصی در عملیات CRUD (ایجاد، خواندن، به‌روزرسانی، حذف) دارد.

GET: برای دریافت داده‌ها استفاده می‌شود.
POST: برای ارسال داده‌ها به سرور و ایجاد منابع جدید.
PUT: برای به‌روزرسانی منابع موجود.
DELETE: برای حذف منابع.
بدون حالت (Stateless): هر درخواست به صورت مستقل است و سرور هیچ اطلاعاتی از وضعیت قبلی درخواست‌ها نگه نمی‌دارد. این ویژگی باعث افزایش مقیاس‌پذیری و ساده‌سازی مدیریت سرور می‌شود.

پشتیبانی از فرمت‌های مختلف داده: بیشتر وب‌سرویس‌های RESTful از فرمت JSON برای تبادل داده‌ها استفاده می‌کنند که خواندن و نوشتن آن در Dart آسان است. همچنین می‌توان از فرمت‌های دیگری مانند XML نیز استفاده کرد.

ساختار منابعی: منابع (مانند کاربران، محصولات و …) به صورت URI‌های منحصر به فرد تعریف می‌شوند و دسترسی به آن‌ها از طریق این URI‌ها صورت می‌گیرد. به عنوان مثال:

GET https://api.example.com/users/1

مثال از یک درخواست RESTful:

فرض کنید می‌خواهید اطلاعات یک کاربر خاص را از سرور دریافت کنید. درخواست شما به صورت زیر خواهد بود:

GET https://api.example.com/users/1

و پاسخ سرور ممکن است به شکل زیر باشد:

{
  "id": 1,
  "name": "Ali Reza",
  "email": "ali.reza@example.com"
}

در این مثال، اپلیکیشن Flutter شما با ارسال یک درخواست GET به آدرس مشخص، اطلاعات کاربر با شناسه 1 را دریافت می‌کند.

وب‌سرویس‌های SOAP

SOAP مخفف Simple Object Access Protocol است و یک پروتکل مبتنی بر XML برای تبادل اطلاعات بین سیستم‌ها است. SOAP در مقایسه با REST پیچیده‌تر است و بیشتر در سیستم‌های سازمانی که نیاز به امنیت و قابلیت‌های پیچیده‌تر دارند، استفاده می‌شود.

ویژگی‌های وب‌سرویس‌های SOAP:

پایه XML: تمام پیام‌ها در قالب XML ارسال می‌شوند که خواندن و نوشتن آن‌ها پیچیده‌تر از JSON است. این موضوع باعث افزایش حجم داده‌ها و پیچیدگی پردازش می‌شود.

پشتیبانی از امنیت پیشرفته: SOAP از استانداردهای امنیتی پیچیده‌تری مانند WS-Security پشتیبانی می‌کند که برای سیستم‌های نیازمند امنیت بالا بسیار مناسب است.

قابلیت اطمینان بالا: SOAP قابلیت اطمینان بیشتری در انتقال پیام‌ها دارد و از استانداردهای پیچیده‌تر برای تضمین تحویل پیام‌ها استفاده می‌کند.

پروتکل‌های متنوع انتقال: SOAP می‌تواند از پروتکل‌های مختلفی مانند HTTP، SMTP و … برای انتقال پیام‌ها استفاده کند، که این امر انعطاف‌پذیری بیشتری را فراهم می‌کند.

مثال از یک درخواست SOAP:
یک درخواست SOAP برای دریافت اطلاعات یک کاربر ممکن است به شکل زیر باشد:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:usr="http://example.com/user">
   <soapenv:Header/>
   <soapenv:Body>
      <usr:GetUserRequest>
         <usr:UserId>1</usr:UserId>
      </usr:GetUserRequest>
   </soapenv:Body>
</soapenv:Envelope>

و پاسخ سرور به صورت زیر خواهد بود:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:usr="http://example.com/user">
   <soapenv:Header/>
   <soapenv:Body>
      <usr:GetUserResponse>
         <usr:User>
            <usr:Id>1</usr:Id>
            <usr:Name>Ali Reza</usr:Name>
            <usr:Email>ali.reza@example.com</usr:Email>
         </usr:User>
      </usr:GetUserResponse>
   </soapenv:Body>
</soapenv:Envelope>

در این مثال، اپلیکیشن Flutter شما با ارسال یک پیام SOAP، اطلاعات کاربر با شناسه 1 را دریافت می‌کند.

چرا بیشتر از RESTful استفاده می‌شود؟

در توسعه Flutter و بسیاری از اپلیکیشن‌های مدرن، وب‌سرویس‌های RESTful به دلیل سادگی، کارایی بالا و سازگاری با فرمت‌های داده‌ای محبوب مانند JSON بیشتر مورد استفاده قرار می‌گیرند. همچنین، کتابخانه‌ها و ابزارهای متعددی برای کار با RESTful در Flutter وجود دارد که توسعه‌دهندگان را قادر می‌سازد تا به راحتی و سریع‌تر با وب‌سرویس‌ها تعامل داشته باشند. در مقابل، SOAP به دلیل پیچیدگی و حجم بالای پیام‌ها، کمتر در پروژه‌های موبایل استفاده می‌شود مگر در موارد خاص که نیاز به امنیت و قابلیت‌های پیچیده‌تر دارند.

در این بخش، با مفهوم وب‌سرویس‌ها و انواع مختلف آن‌ها آشنا شدید. در ادامه مقاله، به نحوه ارسال درخواست‌های HTTP با استفاده از کتابخانه http در Flutter می‌پردازیم و نحوه مدیریت داده‌ها و خطاها را بررسی خواهیم کرد. این مبانی به شما کمک می‌کنند تا بتوانید به طور مؤثر با وب‌سرویس‌ها در اپلیکیشن‌های Flutter خود تعامل داشته باشید و داده‌ها را به صورت امن و کارآمد مدیریت کنید.

ارسال درخواست‌های HTTP با کتابخانه http

یکی از اصلی‌ترین روش‌ها برای کار با دیتابیس و API در Flutter، ارسال درخواست‌های HTTP به سرورها و دریافت پاسخ‌ها از آن‌ها است. در این بخش، به معرفی کتابخانه http که یکی از محبوب‌ترین کتابخانه‌ها برای انجام این کار در Flutter است، می‌پردازیم و نحوه نصب و استفاده از آن را با مثال‌های عملی توضیح می‌دهیم.

نصب کتابخانه http

برای ارسال درخواست‌های HTTP در Flutter، کتابخانه http یکی از بهترین و ساده‌ترین گزینه‌ها می‌باشد. برای استفاده از این کتابخانه ابتدا باید آن را به پروژه خود اضافه کنید. مراحل نصب به شرح زیر است:

باز کردن فایل pubspec.yaml:

این فایل در ریشه پروژه Flutter شما قرار دارد و برای مدیریت وابستگی‌ها (dependencies) استفاده می‌شود.

اضافه کردن کتابخانه http به بخش dependencies:

dependencies:
  http: ^0.13.5

نسخه‌ی http ممکن است در زمان‌های مختلف تغییر کند، بنابراین بهتر است همیشه آخرین نسخه‌ی موجود را از pub.dev بررسی کنید.

اجرای دستور flutter pub get:

پس از ذخیره‌ی تغییرات در فایل pubspec.yaml، باید دستور زیر را در ترمینال پروژه اجرا کنید تا کتابخانه دانلود و نصب شود:

flutter pub get

ارسال درخواست GET

درخواست GET یکی از ساده‌ترین انواع درخواست‌های HTTP است که برای دریافت داده‌ها از سرور استفاده می‌شود. در این مثال، نحوه‌ی ارسال یک درخواست GET و دریافت پاسخ را با استفاده از کتابخانه http در Flutter بررسی می‌کنیم.

مثال عملی:
فرض کنید می‌خواهید اطلاعات یک لیست از کاربران را از یک API دریافت کنید. مراحل به صورت زیر است:

وارد کردن کتابخانه‌های مورد نیاز:

import 'package:http/http.dart' as http;
import 'dart:convert';

تعریف تابعی برای ارسال درخواست GET:

Future<void> fetchData() async {
  final response = await http.get(Uri.parse('https://api.example.com/users'));

  if (response.statusCode == 200) {
    var data = json.decode(response.body);
    print(data);
  } else {
    throw Exception('Failed to load data');
  }
}

استفاده از تابع در اپلیکیشن:

این تابع را می‌توانید در متد initState یک ویجت یا هر جای دیگری که نیاز دارید، فراخوانی کنید:

@override
void initState() {
  super.initState();
  fetchData();
}

توضیحات:
ارسال درخواست GET: با استفاده از متد http.get و ارائه‌ی URI مورد نظر، درخواست GET به سرور ارسال می‌شود.
بررسی وضعیت پاسخ: با بررسی response.statusCode مطمئن می‌شویم که درخواست با موفقیت انجام شده است (کد وضعیت 200).
تبدیل پاسخ به JSON: با استفاده از json.decode، داده‌های دریافت شده از فرمت JSON به یک ساختار قابل استفاده در Dart تبدیل می‌شود.
مدیریت خطا: در صورتی که وضعیت پاسخ غیر از 200 باشد، یک استثناء ایجاد می‌کنیم تا خطا را مدیریت کنیم.

ارسال درخواست POST

درخواست POST برای ارسال داده‌ها به سرور و ایجاد منابع جدید استفاده می‌شود. در این بخش، نحوه‌ی ارسال داده‌ها به سرور با استفاده از درخواست POST را با کتابخانه http توضیح می‌دهیم.

مثال عملی:
فرض کنید می‌خواهید یک کاربر جدید را به پایگاه داده اضافه کنید. مراحل به صورت زیر است:

وارد کردن کتابخانه‌های مورد نیاز:

import 'package:http/http.dart' as http;
import 'dart:convert';

تعریف مدل داده (اختیاری):

برای مدیریت داده‌های ارسال شده، می‌توانید یک مدل ساده تعریف کنید:

class User {
  final String name;
  final String email;

  User({required this.name, required this.email});

  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'email': email,
    };
  }
}

تعریف تابعی برای ارسال درخواست POST:

Future<void> sendData(User user) async {
  final response = await http.post(
    Uri.parse('https://api.example.com/users'),
    headers: {'Content-Type': 'application/json'},
    body: json.encode(user.toJson()),
  );

  if (response.statusCode == 201) {
    print('Data successfully sent');
  } else {
    throw Exception('Failed to send data');
  }
}

استفاده از تابع در اپلیکیشن:

این تابع را می‌توانید در رویدادهای مختلف مانند فشردن دکمه‌ای در فرم ثبت‌نام کاربر فراخوانی کنید:

void registerUser() {
  User newUser = User(name: 'Ali Reza', email: 'ali.reza@example.com');
  sendData(newUser);
}

توضیحات:
ارسال درخواست POST: با استفاده از متد http.post و ارائه‌ی URI مورد نظر، درخواست POST به سرور ارسال می‌شود.
هدرها: با تنظیم هدر Content-Type به application/json، به سرور اعلام می‌کنیم که داده‌ها به فرمت JSON ارسال می‌شوند.
تبدیل داده به JSON: با استفاده از json.encode و متد toJson مدل داده، داده‌های کاربر به فرمت JSON تبدیل می‌شوند.
بررسی وضعیت پاسخ: با بررسی response.statusCode مطمئن می‌شویم که درخواست با موفقیت انجام شده است (کد وضعیت 201 که به معنای ایجاد موفقیت‌آمیز منبع جدید است).
مدیریت خطا: در صورتی که وضعیت پاسخ غیر از 201 باشد، یک استثناء ایجاد می‌کنیم تا خطا را مدیریت کنیم.

نکات مهم هنگام کار با کتابخانه http

مدیریت حالت بارگذاری:

هنگام ارسال درخواست‌های HTTP، معمولاً نیاز است که وضعیت بارگذاری (Loading State) به کاربر نمایش داده شود تا از انجام عملیات بدون وقفه مطمئن شود. می‌توانید از ویجت‌هایی مانند CircularProgressIndicator برای این منظور استفاده کنید.

مدیریت خطاها:

همیشه باید خطاهای احتمالی مانند عدم دسترسی به اینترنت، خطاهای سرور و غیره را مدیریت کنید تا اپلیکیشن شما پایدار باقی بماند و کاربر تجربه مناسبی داشته باشد.

استفاده از توابع غیرهمزمان (Asynchronous):

ارسال درخواست‌های HTTP معمولاً عملیات‌های زمان‌بر هستند، بنابراین باید از توابع async و await برای جلوگیری از مسدود شدن رابط کاربری استفاده کنید.

امنیت:

هنگام ارسال داده‌های حساس، اطمینان حاصل کنید که از پروتکل‌های امن مانند HTTPS استفاده می‌کنید و داده‌ها را به درستی رمزنگاری می‌کنید.

در این بخش، با نحوه‌ی نصب و استفاده از کتابخانه http برای ارسال درخواست‌های GET و POST در Flutter آشنا شدید. این مهارت پایه‌ای برای کار با دیتابیس و API در Flutter است و به شما امکان می‌دهد تا به راحتی با سرورهای خارجی ارتباط برقرار کرده و داده‌ها را مدیریت کنید. در بخش‌های بعدی، به بررسی فرمت JSON و تبدیل مدل داده‌ها در Dart، مدیریت خطاها و حالت بارگذاری، و کار با دیتابیس‌های محلی و ابری خواهیم پرداخت تا بتوانید اپلیکیشن‌های قدرتمند و کارآمدی بسازید.

فرمت JSON و تبدیل مدل داده در Dart

یکی از اجزای کلیدی کار با دیتابیس و API در Flutter، فهم و استفاده از فرمت JSON برای تبادل داده‌ها بین اپلیکیشن شما و سرورها است. در این بخش، با فرمت JSON آشنا می‌شویم و نحوه‌ی تبدیل داده‌های JSON به مدل‌های Dart را بررسی می‌کنیم.

آشنایی با JSON

JSON که مخفف JavaScript Object Notation است، یک فرمت سبک و متنی برای تبادل داده‌ها بین سیستم‌ها می‌باشد. JSON به دلیل سادگی و خوانایی بالا، به طور گسترده‌ای در وب‌سرویس‌ها و APIها استفاده می‌شود. ساختار JSON بر پایه‌ی جفت‌های کلید-مقدار است و می‌تواند شامل انواع داده‌ای مانند رشته‌ها، اعداد، آرایه‌ها و اشیاء تو در تو باشد.

ویژگی‌های JSON:
خوانایی بالا: JSON به راحتی توسط انسان‌ها قابل خواندن و نوشتن است.
سبک و کم حجم: JSON حجم کمی دارد که انتقال آن از طریق شبکه سریع‌تر است.
پشتیبانی گسترده: بیشتر زبان‌های برنامه‌نویسی از JSON پشتیبانی می‌کنند.
ساختار قابل انعطاف: JSON می‌تواند داده‌های پیچیده و تو در تو را به راحتی نمایش دهد.
مثال از JSON:
فرض کنید می‌خواهید اطلاعات یک کاربر را به فرمت JSON نمایش دهید. داده‌ی JSON ممکن است به شکل زیر باشد:

{
  "id": 1,
  "name": "علی رضا",
  "email": "ali.reza@example.com",
  "address": {
    "street": "خیابان انقلاب",
    "city": "تهران",
    "zipcode": "12345"
  },
  "phones": ["09123456789", "09876543210"]
}

در این مثال:

id, name, و email کلیدهایی هستند که به مقادیر مربوطه اشاره می‌کنند.
address یک شیء تو در تو است که شامل اطلاعات آدرس کاربر می‌باشد.
phones یک آرایه از شماره تلفن‌ها است.

ایجاد مدل‌های Dart

برای مدیریت و استفاده از داده‌های JSON در Flutter، باید مدل‌های Dart را تعریف کنید. مدل‌های Dart به شما کمک می‌کنند تا داده‌های دریافتی از APIها را به اشیاء قابل استفاده در کد خود تبدیل کنید.

تعریف کلاس مدل:
ابتدا باید یک کلاس Dart ایجاد کنید که ساختار داده‌های JSON را منعکس کند. به عنوان مثال، برای داده‌ی JSON کاربر قبلی، می‌توانیم کلاس User را به شکل زیر تعریف کنیم:

class User {
  final int id;
  final String name;
  final String email;
  final Address address;
  final List<String> phones;

  User({
    required this.id,
    required this.name,
    required this.email,
    required this.address,
    required this.phones,
  });

  // متد سازنده از JSON
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
      address: Address.fromJson(json['address']),
      phones: List<String>.from(json['phones']),
    );
  }

  // متد تبدیل به JSON
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'email': email,
      'address': address.toJson(),
      'phones': phones,
    };
  }
}

class Address {
  final String street;
  final String city;
  final String zipcode;

  Address({
    required this.street,
    required this.city,
    required this.zipcode,
  });

  factory Address.fromJson(Map<String, dynamic> json) {
    return Address(
      street: json['street'],
      city: json['city'],
      zipcode: json['zipcode'],
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'street': street,
      'city': city,
      'zipcode': zipcode,
    };
  }
}

توضیحات:
فیلدها (Fields): هر فیلد در کلاس مدل با یک کلید در JSON مطابقت دارد.
سازنده‌ی fromJson: این متد یک شیء از کلاس مدل را از یک نقشه (Map) JSON ایجاد می‌کند.
متد toJson: این متد شیء مدل را به یک نقشه‌ی JSON تبدیل می‌کند.

تبدیل JSON به مدل Dart

پس از تعریف مدل‌های Dart، باید داده‌های JSON دریافتی از API را به اشیاء Dart تبدیل کنید. این کار به شما امکان می‌دهد تا به راحتی با داده‌ها در اپلیکیشن Flutter خود کار کنید.

مثال عملی:
فرض کنید شما داده‌های کاربر را از یک API دریافت کرده‌اید و می‌خواهید آن‌ها را به یک شیء Dart تبدیل کنید.

دریافت داده‌های JSON از API:

import 'package:http/http.dart' as http;
import 'dart:convert';

Future<User> fetchUser(int userId) async {
  final response = await http.get(Uri.parse('https://api.example.com/users/$userId'));

  if (response.statusCode == 200) {
    // تبدیل داده‌های JSON به نقشه (Map)
    Map<String, dynamic> userJson = json.decode(response.body);
    // ایجاد شیء User از JSON
    return User.fromJson(userJson);
  } else {
    throw Exception('Failed to load user');
  }
}

استفاده از تابع در اپلیکیشن:

@override
void initState() {
  super.initState();
  fetchUser(1).then((user) {
    setState(() {
      // استفاده از داده‌های کاربر
      print('User Name: ${user.name}');
    });
  }).catchError((error) {
    print('Error: $error');
  });
}

توضیحات:
ارسال درخواست GET: با استفاده از کتابخانه http، یک درخواست GET به آدرس مشخص ارسال می‌شود.
بررسی وضعیت پاسخ: اگر statusCode برابر با 200 باشد، داده‌ها با استفاده از json.decode به یک نقشه‌ی Dart تبدیل می‌شوند.
تبدیل به مدل Dart: با استفاده از متد fromJson مدل User، داده‌های JSON به یک شیء Dart تبدیل می‌شوند.
مدیریت خطا: در صورتی که درخواست با موفقیت انجام نشود، یک استثناء پرتاب می‌شود که می‌تواند در بخش catchError مدیریت شود.

نکات مهم هنگام کار با JSON و مدل‌های Dart

دقت در تطبیق کلیدها: مطمئن شوید که کلیدهای تعریف شده در کلاس مدل با کلیدهای موجود در JSON مطابقت دارند. هرگونه اختلاف در نام کلیدها می‌تواند باعث ایجاد خطا شود.

مدیریت داده‌های تو در تو: اگر داده‌های JSON شامل اشیاء تو در تو هستند، باید مدل‌های جداگانه‌ای برای هر شیء تعریف کنید و در متد fromJson آن‌ها را به درستی تبدیل کنید.

تبدیل انواع داده‌ها: مطمئن شوید که انواع داده‌ها در مدل Dart با داده‌های JSON سازگار باشند. به عنوان مثال، اگر یک فیلد عددی در JSON وجود دارد، در Dart نیز باید به عنوان int یا double تعریف شود.

استفاده از ابزارهای تولید کد: برای مدل‌های پیچیده، می‌توانید از ابزارهایی مانند json_serializable استفاده کنید که فرآیند تبدیل JSON به مدل Dart را خودکار می‌کنند و خطاهای انسانی را کاهش می‌دهند.

مدیریت مقادیر اختیاری: اگر برخی از فیلدهای JSON ممکن است وجود نداشته باشند یا مقادیر خالی داشته باشند، باید در مدل Dart به آن‌ها به صورت اختیاری (nullable) اشاره کنید و در متد fromJson آن‌ها را به درستی مدیریت کنید.  در این بخش، با فرمت JSON و اهمیت آن در کار با دیتابیس و API در Flutter آشنا شدید. همچنین، نحوه‌ی تعریف مدل‌های Dart برای مدیریت داده‌های JSON و تبدیل داده‌ها از JSON به اشیاء Dart را یاد گرفتید. این مبانی به شما کمک می‌کنند تا بتوانید به طور مؤثر داده‌ها را از سرور دریافت کرده و در اپلیکیشن Flutter خود استفاده کنید. در بخش‌های بعدی، به مدیریت خطاها و حالت بارگذاری، و کار با دیتابیس‌های محلی و ابری خواهیم پرداخت تا بتوانید اپلیکیشن‌های قدرتمند و کارآمدی بسازید.

مدیریت خطاها و حالت بارگذاری (Loading State)

در کار با دیتابیس و API در Flutter، مدیریت خطاها و نمایش حالت بارگذاری به کاربران از جمله جنبه‌های حیاتی برای ایجاد اپلیکیشن‌های پایدار و کاربرپسند است. این بخش به شما نشان می‌دهد چگونه می‌توانید خطاها را مدیریت کنید و در حین انجام عملیات‌های زمان‌بر، وضعیت بارگذاری را به کاربران نمایش دهید.

مدیریت خطاها

مدیریت خطاها در توسعه اپلیکیشن‌های Flutter بسیار مهم است زیرا اپلیکیشن‌های شما ممکن است در طول ارتباط با سرورها و دیتابیس‌ها با مشکلات مختلفی مواجه شوند. بدون مدیریت مناسب خطاها، اپلیکیشن شما ممکن است ناگهانی قطع شود یا تجربه کاربری ضعیفی ارائه دهد. برای مدیریت خطاها، می‌توانید از ساختار try-catch استفاده کنید که به شما اجازه می‌دهد خطاهای احتمالی را شناسایی و واکنش مناسب نشان دهید.

مثال عملی:

فرض کنید می‌خواهید داده‌ها را از یک API دریافت کنید. ممکن است درخواست شما موفقیت‌آمیز نباشد و سرور خطایی بازگرداند یا اتصال اینترنت قطع شود. در اینجا نحوه‌ی مدیریت این خطاها با استفاده از try-catch آورده شده است:

import 'package:http/http.dart' as http;
import 'dart:convert';

Future<void> fetchData() async {
  try {
    final response = await http.get(Uri.parse('https://api.example.com/data'));
    
    if (response.statusCode == 200) {
      var data = json.decode(response.body);
      print(data);
    } else {
      throw Exception('Failed to load data');
    }
  } catch (e) {
    print('Error: $e');
    // می‌توانید در اینجا نمایش پیام خطا به کاربر را اضافه کنید
  }
}

توضیحات:
ساختار try-catch:

try: بخشی از کد که ممکن است خطا ایجاد کند را درون بلاک try قرار می‌دهید.
catch: بخشی از کد که خطاهای ایجاد شده در بلاک try را می‌گیرد و می‌توانید واکنش مناسب نشان دهید.
بررسی کد وضعیت (Status Code):

پس از ارسال درخواست HTTP، کد وضعیت پاسخ بررسی می‌شود.
اگر statusCode برابر با 200 باشد، به معنای موفقیت‌آمیز بودن درخواست است و داده‌ها پردازش می‌شوند.
در غیر این صورت، یک استثناء پرتاب می‌شود که توسط بلاک catch مدیریت می‌گردد.
مدیریت خطا:

در بلاک catch، خطا به صورت یک رشته چاپ می‌شود.
شما می‌توانید در اینجا پیام‌های خطا را به کاربر نمایش دهید یا اقداماتی دیگر انجام دهید.

مدیریت حالت بارگذاری

مدیریت حالت بارگذاری به کاربر نشان می‌دهد که اپلیکیشن در حال انجام یک عملیات است و باید منتظر بماند. این امر به بهبود تجربه کاربری کمک می‌کند و از ایجاد ابهام در مورد وضعیت عملیات جلوگیری می‌کند. برای مدیریت حالت بارگذاری، معمولاً از یک متغیر وضعیت (bool) استفاده می‌شود که نشان‌دهنده‌ی در حال بارگذاری بودن اپلیکیشن است.

مثال عملی:
در این مثال، هنگام ارسال درخواست HTTP، یک ویجت CircularProgressIndicator نمایش داده می‌شود تا کاربر بداند اپلیکیشن در حال بارگذاری است. پس از پایان عملیات، حالت بارگذاری غیرفعال می‌شود.

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

class DataFetcher extends StatefulWidget {
  @override
  _DataFetcherState createState() => _DataFetcherState();
}

class _DataFetcherState extends State<DataFetcher> {
  bool isLoading = false;
  String data = '';

  Future<void> fetchData() async {
    setState(() {
      isLoading = true;
    });

    try {
      final response = await http.get(Uri.parse('https://api.example.com/data'));
      
      if (response.statusCode == 200) {
        var jsonData = json.decode(response.body);
        setState(() {
          data = jsonData.toString();
        });
      } else {
        throw Exception('Failed to load data');
      }
    } catch (e) {
      print('Error: $e');
      // می‌توانید در اینجا نمایش پیام خطا به کاربر را اضافه کنید
    } finally {
      setState(() {
        isLoading = false;
      });
    }
  }

  @override
  void initState() {
    super.initState();
    fetchData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Data Fetcher'),
      ),
      body: Center(
        child: isLoading
            ? CircularProgressIndicator()
            : Text(data.isNotEmpty ? data : 'No Data'),
      ),
    );
  }
}

توضیحات:

تعریف متغیر وضعیت:

bool isLoading: نشان‌دهنده‌ی در حال بارگذاری بودن اپلیکیشن است.
String data: ذخیره‌سازی داده‌های دریافتی از API.

بلاک fetchData:

شروع عملیات بارگذاری: با استفاده از setState، متغیر isLoading به true تنظیم می‌شود تا ویجت CircularProgressIndicator نمایش داده شود.
ارسال درخواست GET: درخواست HTTP ارسال می‌شود و پاسخ بررسی می‌گردد.
پردازش داده‌ها: در صورت موفقیت‌آمیز بودن درخواست، داده‌ها به متغیر data اختصاص می‌یابند.
مدیریت خطا: در صورت بروز خطا، آن را چاپ می‌کنیم و می‌توانید پیام خطا را به کاربر نمایش دهید.
پایان عملیات بارگذاری: در نهایت، متغیر isLoading به false تغییر می‌کند تا ویجت CircularProgressIndicator مخفی شود و داده‌ها نمایش داده شوند.
ساختار ویجت:

در متد build، با بررسی مقدار isLoading تصمیم‌گیری می‌کنیم که آیا CircularProgressIndicator یا داده‌ها را نمایش دهیم.
اگر isLoading برابر با true باشد، یک ویجت CircularProgressIndicator نمایش داده می‌شود.
در غیر این صورت، داده‌های دریافتی نمایش داده می‌شوند. اگر داده‌ای وجود نداشته باشد، پیام No Data نمایش داده می‌شود.

نکات مهم هنگام مدیریت خطاها و حالت بارگذاری

استفاده از setState برای به‌روزرسانی UI:

هر بار که مقدار متغیرهای وضعیت تغییر می‌کند، باید از setState استفاده کنید تا UI به روز شود.
نمایش پیام‌های خطا به کاربر:

به جای فقط چاپ خطا در کنسول، می‌توانید پیام‌های خطا را به صورت ویجت‌هایی مانند SnackBar یا AlertDialog به کاربر نمایش دهید تا از مشکلات احتمالی مطلع شوند.

catch (e) {
  print('Error: $e');
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('خطا در دریافت داده‌ها')),
  );
}

مدیریت حالت بارگذاری در مکان‌های مختلف:

بسته به نوع اپلیکیشن و مکانیزم‌های بارگذاری، ممکن است نیاز به مدیریت حالت بارگذاری در قسمت‌های مختلف UI داشته باشید. به عنوان مثال، هنگام ارسال فرم‌ها، دریافت لیست‌ها و غیره.
استفاده از کتابخانه‌های مدیریت وضعیت:

برای اپلیکیشن‌های بزرگ‌تر، استفاده از کتابخانه‌هایی مانند Provider، Bloc یا Riverpod می‌تواند مدیریت خطاها و حالت بارگذاری را ساده‌تر و ساختارمندتر کند.
تست و بهینه‌سازی:

مطمئن شوید که تمامی مسیرهای ممکن (موفقیت، خطاهای سرور، خطاهای شبکه) را تست کرده‌اید تا اپلیکیشن شما به درستی واکنش نشان دهد.

در این بخش، با اهمیت مدیریت خطاها و نمایش حالت بارگذاری در کار با دیتابیس و API در Flutter آشنا شدید. یاد گرفتید چگونه با استفاده از ساختار try-catch خطاها را مدیریت کنید و با استفاده از متغیرهای وضعیت، وضعیت بارگذاری را به کاربران نمایش دهید. این مهارت‌ها به شما کمک می‌کنند تا اپلیکیشن‌های پایدارتر و کاربرپسندتری ایجاد کنید که در مواجهه با مشکلات احتمالی، به درستی واکنش نشان دهند و تجربه کاربری بهتری ارائه دهند.

دیتابیس محلی

یکی از جنبه‌های مهم در کار با دیتابیس و API در Flutter، استفاده از دیتابیس‌های محلی برای ذخیره‌سازی داده‌ها به صورت آفلاین و بهبود عملکرد اپلیکیشن است. در این بخش، به بررسی یکی از پرکاربردترین دیتابیس‌های محلی یعنی SQLite و کتابخانه‌ی مربوط به آن در Flutter، یعنی sqflite، می‌پردازیم. همچنین نحوه‌ی نصب و استفاده از این کتابخانه را با مثال‌های عملی توضیح می‌دهیم.

SQLite / sqflite

معرفی SQLite

SQLite یک سیستم مدیریت پایگاه داده رابطه‌ای سبک و قابل حمل است که به طور گسترده در اپلیکیشن‌های موبایل استفاده می‌شود. این دیتابیس به دلیل سادگی، کم‌حجم بودن و قابلیت استفاده بدون نیاز به سرور، انتخاب مناسبی برای ذخیره‌سازی داده‌های محلی در اپلیکیشن‌های Flutter است. کار با دیتابیس و API در Flutter اغلب نیازمند استفاده از SQLite برای ذخیره‌سازی داده‌ها به صورت محلی می‌باشد.

ویژگی‌های SQLite:

سبک و کم‌حجم: SQLite نیاز به منابع زیادی ندارد و به راحتی در اپلیکیشن‌های موبایل جاسازی می‌شود.
بدون نیاز به سرور: این دیتابیس به صورت فایل محلی روی دستگاه ذخیره می‌شود و نیازی به سرور مجزا ندارد.
پشتیبانی از SQL کامل: SQLite از زبان پرس‌وجوی SQL پشتیبانی می‌کند که امکان انجام عملیات‌های پیچیده را فراهم می‌آورد.
پایداری و قابلیت اطمینان بالا: SQLite به دلیل استفاده در بسیاری از سیستم‌ها و اپلیکیشن‌ها، پایدار و قابل اعتماد است.

نصب sqflite

برای استفاده از SQLite در Flutter، کتابخانه‌ی sqflite یکی از بهترین گزینه‌ها است. این کتابخانه امکان تعامل با SQLite را از طریق Dart فراهم می‌کند و ابزارهای لازم برای ایجاد و مدیریت دیتابیس را ارائه می‌دهد.

مراحل نصب sqflite:

باز کردن فایل pubspec.yaml:

این فایل در ریشه پروژه Flutter شما قرار دارد و برای مدیریت وابستگی‌ها (dependencies) استفاده می‌شود.

اضافه کردن کتابخانه‌های sqflite و path به بخش dependencies:

dependencies:
  sqflite: ^2.0.0+4
  path: ^1.8.0

sqflite: کتابخانه اصلی برای کار با SQLite در Flutter.
path: کتابخانه‌ای برای مدیریت مسیرهای فایل که برای تعیین مسیر دیتابیس استفاده می‌شود.

اجرای دستور flutter pub get:

پس از ذخیره‌ی تغییرات در فایل pubspec.yaml، باید دستور زیر را در ترمینال پروژه اجرا کنید تا کتابخانه‌ها دانلود و نصب شوند:

flutter pub get

استفاده از sqflite

پس از نصب کتابخانه‌های لازم، می‌توانید از آن‌ها برای ایجاد و مدیریت دیتابیس SQLite در اپلیکیشن Flutter خود استفاده کنید. در ادامه، یک مثال ساده از ایجاد دیتابیس، ایجاد جداول، درج داده‌ها و بازیابی آن‌ها را بررسی می‌کنیم.

مثال عملی:
فرض کنید می‌خواهید یک دیتابیس برای ذخیره‌سازی اطلاعات کاربران ایجاد کنید. مراحل به صورت زیر است:

وارد کردن کتابخانه‌های مورد نیاز:

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

تعریف مدل داده:

ابتدا باید یک کلاس مدل برای مدیریت داده‌های کاربران تعریف کنید:

class User {
  final int? id;
  final String name;
  final String email;

  User({this.id, required this.name, required this.email});

  // تبدیل شیء User به نقشه (Map)
  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'name': name,
      'email': email,
    };
  }

  // ایجاد شیء User از نقشه (Map)
  factory User.fromMap(Map<String, dynamic> map) {
    return User(
      id: map['id'],
      name: map['name'],
      email: map['email'],
    );
  }
}

ایجاد و راه‌اندازی دیتابیس:

یک تابع برای ایجاد و باز کردن دیتابیس و ایجاد جداول مورد نیاز تعریف می‌کنیم:

Future<Database> initializeDB() async {
  // دریافت مسیر دیتابیس
  String path = join(await getDatabasesPath(), 'app_database.db');

  // باز کردن دیتابیس و ایجاد جدول در صورت عدم وجود
  return openDatabase(
    path,
    version: 1,
    onCreate: (Database db, int version) async {
      await db.execute(
        "CREATE TABLE users(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, email TEXT)",
      );
    },
  );
}

getDatabasesPath(): تابعی برای دریافت مسیر پیش‌فرض ذخیره‌سازی دیتابیس‌ها.
openDatabase: باز کردن دیتابیس و ایجاد جدول در صورت عدم وجود.
درج داده‌ها در دیتابیس:

یک تابع برای درج کاربران جدید در جدول users تعریف می‌کنیم:

Future<void> insertUser(User user) async {
  final Database db = await initializeDB();
  await db.insert(
    'users',
    user.toMap(),
    conflictAlgorithm: ConflictAlgorithm.replace,
  );
}

insert: متدی برای درج داده‌ها در جدول مشخص شده.
conflictAlgorithm: الگوریتم مدیریت تداخل داده‌ها. در اینجا از replace استفاده شده است که در صورت تداخل داده‌ها، رکورد جدید جایگزین رکورد قبلی می‌شود.
بازیابی داده‌ها از دیتابیس:

یک تابع برای دریافت لیست کاربران از دیتابیس تعریف می‌کنیم:

Future<List<User>> retrieveUsers() async {
  final Database db = await initializeDB();
  final List<Map<String, dynamic>> maps = await db.query('users');

  return List.generate(maps.length, (i) {
    return User.fromMap(maps[i]);
  });
}

query: متدی برای اجرای پرس‌وجوی SQL و دریافت داده‌ها از جدول مشخص شده.
List.generate: ایجاد لیستی از اشیاء User با استفاده از داده‌های دریافت شده.

استفاده از توابع در اپلیکیشن:

برای استفاده از توابع فوق در اپلیکیشن، می‌توانید آن‌ها را در متد initState یک ویجت یا هر جای دیگری که نیاز دارید، فراخوانی کنید:

@override
void initState() {
  super.initState();
  addUser();
  getUsers();
}

Future<void> addUser() async {
  User user = User(name: 'علی رضا', email: 'ali.reza@example.com');
  await insertUser(user);
}

Future<void> getUsers() async {
  List<User> users = await retrieveUsers();
  print(users);
}

addUser: تابعی برای ایجاد و درج یک کاربر جدید.
getUsers: تابعی برای دریافت لیست کاربران و چاپ آن‌ها.

توضیحات کد:

تعریف کلاس User:

کلاس User شامل فیلدهای id, name و email است.
متد toMap برای تبدیل شیء User به یک نقشه‌ی کلید-مقدار استفاده می‌شود.
متد fromMap برای ایجاد یک شیء User از یک نقشه‌ی کلید-مقدار استفاده می‌شود.

تابع initializeDB:

این تابع مسیر دیتابیس را تعیین می‌کند و دیتابیس را باز می‌کند.
اگر دیتابیس وجود نداشته باشد، جدول users ایجاد می‌شود.

تابع insertUser:

این تابع یک شیء User را به دیتابیس اضافه می‌کند.
از conflictAlgorithm.replace برای جایگزینی رکورد در صورت تداخل استفاده می‌شود

تابع retrieveUsers:

این تابع همه‌ی کاربران را از دیتابیس بازیابی می‌کند و به صورت یک لیست از اشیاء User برمی‌گرداند.

نکات مهم هنگام کار با sqflite

مدیریت نسخه دیتابیس:

هنگام تغییر ساختار دیتابیس (مثلاً افزودن جدول جدید یا تغییر ساختار جدول موجود)، باید نسخه دیتابیس را افزایش دهید و عملیات‌های مورد نیاز را در متد onUpgrade تعریف کنید.

return openDatabase(
  path,
  version: 2,
  onCreate: (Database db, int version) async {
    await db.execute(
      "CREATE TABLE users(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, email TEXT)",
    );
  },
  onUpgrade: (Database db, int oldVersion, int newVersion) async {
    if (oldVersion < newVersion) {
      // عملیات‌های مورد نیاز برای آپگرید دیتابیس
    }
  },
);

استفاده از توابع غیرهمزمان (Asynchronous):

تمامی عملیات‌های دیتابیس در sqflite به صورت غیرهمزمان انجام می‌شوند. بنابراین باید از async و await برای مدیریت این عملیات‌ها استفاده کنید تا از مسدود شدن رابط کاربری جلوگیری شود.

مدیریت ارتباط با دیتابیس:

بهتر است یک نمونه‌ی یکتا از دیتابیس را در اپلیکیشن خود مدیریت کنید تا از باز و بسته شدن مکرر دیتابیس جلوگیری شود.

class DatabaseHelper {
  static final DatabaseHelper _instance = DatabaseHelper._internal();
  factory DatabaseHelper() => _instance;
  DatabaseHelper._internal();

  static Database? _database;

  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await initializeDB();
    return _database!;
  }

  // توابع initializeDB، insertUser و retrieveUsers در اینجا قرار می‌گیرند
}

امنیت داده‌ها:

اگر داده‌های حساس را در دیتابیس ذخیره می‌کنید، از روش‌های رمزنگاری برای محافظت از داده‌ها استفاده کنید.
بهینه‌سازی عملکرد:

برای بهبود عملکرد دیتابیس، از اندیس‌ها (Indexes) برای فیلدهای پرتکرار استفاده کنید.
از عملیات‌های دسته‌ای (Batch Operations) برای اجرای چندین عملیات به صورت همزمان بهره ببرید.
پشتیبان‌گیری و بازیابی:

برای جلوگیری از از دست رفتن داده‌ها، مکانیزم‌های پشتیبان‌گیری و بازیابی مناسب را پیاده‌سازی کنید.

در این بخش، با استفاده از کتابخانه‌ی sqflite، نحوه‌ی ایجاد و مدیریت دیتابیس SQLite در Flutter را بررسی کردیم. یاد گرفتید چگونه دیتابیس را راه‌اندازی کرده، جداول را ایجاد کرده، داده‌ها را درج و بازیابی کنید. همچنین با نکات مهم مربوط به مدیریت نسخه دیتابیس، امنیت داده‌ها و بهینه‌سازی عملکرد آشنا شدید. این مهارت‌ها پایه‌ای برای کار با دیتابیس و API در Flutter هستند و به شما کمک می‌کنند تا اپلیکیشن‌های قدرتمند و پایدارتری بسازید.

Hive یا Moor/Drift (NoSQL / ORM در Flutter)

در کار با دیتابیس و API در Flutter، انتخاب دیتابیس مناسب برای نیازهای اپلیکیشن شما اهمیت زیادی دارد. در این بخش، به بررسی دو گزینه محبوب برای مدیریت دیتابیس محلی در Flutter می‌پردازیم: Hive و Moor/Drift. هر یک از این ابزارها ویژگی‌ها و مزایای خاص خود را دارند که بسته به نیاز پروژه می‌توانید یکی را انتخاب کنید.

Hive

Hive یک دیتابیس NoSQL سریع و سبک برای Flutter است که به دلیل سادگی و کارایی بالا مورد توجه بسیاری از توسعه‌دهندگان قرار گرفته است. Hive به شما امکان می‌دهد داده‌ها را به صورت محلی و بدون نیاز به سرور ذخیره و مدیریت کنید.

ویژگی‌های Hive:
سرعت بالا: Hive به دلیل طراحی بهینه و استفاده از بایت‌های بومی، سرعت بالایی در خواندن و نوشتن داده‌ها دارد.
بدون نیاز به SQL: Hive از زبان پرس‌وجو SQL استفاده نمی‌کند و به جای آن از نقشه‌های کلید-مقدار برای ذخیره‌سازی داده‌ها بهره می‌برد.
پشتیبانی از داده‌های پیچیده: Hive می‌تواند انواع داده‌ای پیچیده مانند اشیاء تو در تو را به راحتی مدیریت کند.
سازگاری با Flutter: Hive به طور کامل با Flutter سازگار است و کتابخانه‌ی hive_flutter امکانات اضافی برای استفاده در Flutter فراهم می‌کند.
پشتیبانی از Encryption: Hive امکان رمزنگاری داده‌ها را فراهم می‌کند تا امنیت اطلاعات کاربران تضمین شود.

نصب Hive

برای استفاده از Hive در پروژه Flutter خود، ابتدا باید کتابخانه‌های مورد نیاز را به فایل pubspec.yaml اضافه کنید:

dependencies:
  hive: ^2.0.0
  hive_flutter: ^1.1.0

پس از اضافه کردن این وابستگی‌ها، با اجرای دستور زیر در ترمینال پروژه، کتابخانه‌ها نصب می‌شوند:

flutter pub get

استفاده از Hive

پس از نصب کتابخانه‌های مورد نیاز، می‌توانید Hive را در پروژه خود استفاده کنید. در ادامه یک مثال ساده از نحوه‌ی استفاده Hive برای ذخیره و بازیابی داده‌ها آورده شده است:

import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';

void main() async {
  // راه‌اندازی Hive با استفاده از Hive Flutter
  await Hive.initFlutter();

  // باز کردن یک جعبه (Box) برای ذخیره‌سازی داده‌ها
  var box = await Hive.openBox('myBox');

  // ذخیره یک مقدار در جعبه
  await box.put('key', 'value');

  // بازیابی مقدار ذخیره شده
  var retrievedValue = box.get('key');
  print(retrievedValue); // خروجی: value
}

توضیحات کد:

راه‌اندازی Hive: با استفاده از Hive.initFlutter()، Hive را برای استفاده در Flutter راه‌اندازی می‌کنیم.
باز کردن Box: با استفاده از Hive.openBox(‘myBox’)، یک جعبه (Box) با نام myBox باز می‌کنیم که محل ذخیره‌سازی داده‌ها خواهد بود.
ذخیره‌سازی داده: با استفاده از box.put(‘key’, ‘value’)، یک کلید به نام key و مقدار value در جعبه ذخیره می‌شود.
بازیابی داده: با استفاده از box.get(‘key’)، مقدار مرتبط با کلید key را از جعبه بازیابی می‌کنیم و آن را چاپ می‌کنیم.

Moor/Drift

Moor که اکنون با نام Drift شناخته می‌شود، یک ORM قدرتمند برای Flutter است که امکان استفاده از قابلیت‌های SQLite را با استفاده از زبان Dart فراهم می‌کند. Drift به شما اجازه می‌دهد تا با استفاده از زبان Dart به صورت نوع‌مند (type-safe) با دیتابیس SQLite تعامل داشته باشید.

ویژگی‌های Drift:

ORM قدرتمند: Drift امکانات پیشرفته‌ای برای کار با دیتابیس‌های رابطه‌ای فراهم می‌کند، از جمله کوئری‌های پیچیده، Joinها و غیره.
نوع‌مند بودن: Drift به شما امکان می‌دهد تا کوئری‌های SQL را به صورت نوع‌مند و بدون نیاز به نوشتن رشته‌های SQL به صورت دستی بنویسید.
پشتیبانی از Migration: Drift امکان مدیریت تغییرات ساختاری دیتابیس را با استفاده از Migrationها فراهم می‌کند.
پشتیبانی از Streams: Drift از Streams برای دریافت به‌روزرسانی‌های بلادرنگ دیتابیس پشتیبانی می‌کند.
سازگاری با Flutter: Drift به طور کامل با Flutter سازگار است و ابزارهای لازم برای استفاده در اپلیکیشن‌های Flutter را فراهم می‌کند.

نصب Drift

برای استفاده از Drift در پروژه Flutter خود، ابتدا باید کتابخانه‌های مورد نیاز را به فایل pubspec.yaml اضافه کنید:

dependencies:
  drift: ^1.7.0
  drift_flutter: ^1.7.0
  sqlite3_flutter_libs: ^0.5.0

پس از اضافه کردن این وابستگی‌ها، با اجرای دستور زیر در ترمینال پروژه، کتابخانه‌ها نصب می‌شوند:

flutter pub get

استفاده از Drift

پس از نصب کتابخانه‌های مورد نیاز، می‌توانید Drift را در پروژه خود استفاده کنید. در ادامه یک مثال ساده از نحوه‌ی استفاده Drift برای ایجاد دیتابیس، تعریف جداول، درج داده‌ها و بازیابی آن‌ها آورده شده است:

import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';

part 'database.g.dart';

// تعریف جدول Users
class Users extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get name => text()();
  TextColumn get email => text()();
}

// تعریف دیتابیس
@DriftDatabase(tables: [Users])
class AppDatabase extends _$AppDatabase {
  // تعیین محل ذخیره‌سازی دیتابیس
  AppDatabase() : super(FlutterQueryExecutor.inDatabaseFolder(path: 'app.db'));

  @override
  int get schemaVersion => 1;

  // تعریف عملیات‌ها
  Future<List<User>> getAllUsers() => select(users).get();
  Future insertUser(Insertable<User> user) => into(users).insert(user);
}

توضیحات کد:

تعریف جدول Users:

کلاس Users که از Table ارث‌بری می‌کند، شامل ستون‌های id، name و email است.
ستون id به صورت خودکار افزایش می‌یابد و به عنوان کلید اصلی جدول تعریف شده است.

تعریف دیتابیس:

کلاس AppDatabase که از _$AppDatabase ارث‌بری می‌کند، دیتابیس را با استفاده از FlutterQueryExecutor.inDatabaseFolder در مسیر مشخص شده باز می‌کند.
schemaVersion نسخه‌ی دیتابیس را مشخص می‌کند که برای مدیریت تغییرات ساختاری دیتابیس استفاده می‌شود.

تعریف عملیات‌ها:

getAllUsers یک کوئری ساده برای دریافت همه‌ی کاربران از جدول users است.
insertUser یک تابع برای درج یک کاربر جدید در جدول users می‌باشد.

اجرای مثال Drift:

برای استفاده از دیتابیس Drift در اپلیکیشن Flutter، می‌توانید توابع تعریف شده را در ویجت‌های خود فراخوانی کنید:

import 'package:flutter/material.dart';
import 'database.dart'; // فایل دیتابیس

class UserPage extends StatefulWidget {
  @override
  _UserPageState createState() => _UserPageState();
}

class _UserPageState extends State<UserPage> {
  final AppDatabase _db = AppDatabase();

  @override
  void initState() {
    super.initState();
    _addUser();
    _getUsers();
  }

  Future<void> _addUser() async {
    User user = UsersCompanion(
      name: Value('علی رضا'),
      email: Value('ali.reza@example.com'),
    );
    await _db.insertUser(user);
  }

  Future<void> _getUsers() async {
    List<User> users = await _db.getAllUsers();
    for (var user in users) {
      print('User Name: ${user.name}, Email: ${user.email}');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Drift Example'),
      ),
      body: Center(
        child: Text('Check console for output'),
      ),
    );
  }
}

توضیحات کد:
تعریف ویجت UserPage:

یک ویجت Stateful ایجاد می‌کنیم که در initState توابع _addUser و _getUsers را فراخوانی می‌کند.

تابع _addUser:

یک شیء User جدید ایجاد کرده و آن را به دیتابیس اضافه می‌کنیم.

تابع _getUsers:

لیست کاربران را از دیتابیس بازیابی کرده و آن‌ها را در کنسول چاپ می‌کنیم.

نکات مهم هنگام کار با Drift:

استفاده از ابزارهای تولید کد:

 از ابزارهای  Drift

Moor/Drift
Moor که اکنون با نام Drift شناخته می‌شود، یک ORM (Object-Relational Mapping) قدرتمند برای Flutter است که امکان استفاده از قابلیت‌های SQLite را با استفاده از زبان Dart فراهم می‌کند. Drift به شما اجازه می‌دهد تا با استفاده از زبان Dart به صورت نوع‌مند (type-safe) با دیتابیس SQLite تعامل داشته باشید و عملیات‌های پیچیده‌ای مانند Joinها، کوئری‌های پیشرفته و Migrationها را به راحتی انجام دهید.

ویژگی‌های Drift:
نوع‌مند بودن: Drift از قابلیت‌های نوع‌مند Dart استفاده می‌کند تا کوئری‌ها را به صورت ایمن و بدون خطاهای نوع‌مکان مدیریت کند.
پشتیبانی از Migration: Drift امکان مدیریت تغییرات ساختاری دیتابیس با استفاده از Migrationها را فراهم می‌کند که به شما اجازه می‌دهد ساختار دیتابیس را به صورت تدریجی به‌روزرسانی کنید.
پشتیبانی از Streams: Drift از Streams برای دریافت به‌روزرسانی‌های بلادرنگ دیتابیس پشتیبانی می‌کند، که این امکان را به شما می‌دهد تا UI خود را بر اساس تغییرات دیتابیس به روز کنید.
پشتیبانی از Query Builder: Drift ابزارهای پیشرفته‌ای برای ساخت کوئری‌های SQL ارائه می‌دهد که به شما اجازه می‌دهد کوئری‌های پیچیده را به راحتی بنویسید.
سازگاری با Flutter: Drift به طور کامل با Flutter سازگار است و ابزارهای لازم برای استفاده در اپلیکیشن‌های Flutter را فراهم می‌کند.

نصب Drift

برای استفاده از Drift در پروژه Flutter خود، ابتدا باید کتابخانه‌های مورد نیاز را به فایل pubspec.yaml اضافه کنید:

dependencies:
  drift: ^1.7.0
  drift_flutter: ^1.7.0
  sqlite3_flutter_libs: ^0.5.0

پس از اضافه کردن این وابستگی‌ها، با اجرای دستور زیر در ترمینال پروژه، کتابخانه‌ها نصب می‌شوند:

flutter pub get

استفاده از Drift

پس از نصب کتابخانه‌های مورد نیاز، می‌توانید Drift را در پروژه خود استفاده کنید. در ادامه یک مثال ساده از نحوه‌ی استفاده Drift برای ایجاد دیتابیس، تعریف جداول، درج داده‌ها و بازیابی آن‌ها آورده شده است:

import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';

part 'database.g.dart';

// تعریف جدول Users
class Users extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get name => text()();
  TextColumn get email => text()();
}

// تعریف دیتابیس
@DriftDatabase(tables: [Users])
class AppDatabase extends _$AppDatabase {
  // تعیین محل ذخیره‌سازی دیتابیس
  AppDatabase() : super(FlutterQueryExecutor.inDatabaseFolder(path: 'app.db'));

  @override
  int get schemaVersion => 1;

  // تعریف عملیات‌ها
  Future<List<User>> getAllUsers() => select(users).get();
  Future insertUser(Insertable<User> user) => into(users).insert(user);
}

توضیحات کد:

تعریف جدول Users:

کلاس Users که از Table ارث‌بری می‌کند، شامل ستون‌های id، name و email است.
ستون id به صورت خودکار افزایش می‌یابد و به عنوان کلید اصلی جدول تعریف شده است.

تعریف دیتابیس:

کلاس AppDatabase که از _$AppDatabase ارث‌بری می‌کند، دیتابیس را با استفاده از FlutterQueryExecutor.inDatabaseFolder در مسیر مشخص شده باز می‌کند.
schemaVersion نسخه‌ی دیتابیس را مشخص می‌کند که برای مدیریت تغییرات ساختاری دیتابیس استفاده می‌شود.

تعریف عملیات‌ها:

getAllUsers یک کوئری ساده برای دریافت همه‌ی کاربران از جدول users است.
insertUser یک تابع برای درج یک کاربر جدید در جدول users می‌باشد.

اجرای مثال Drift:

برای استفاده از دیتابیس Drift در اپلیکیشن Flutter، می‌توانید توابع تعریف شده را در ویجت‌های خود فراخوانی کنید:

import 'package:flutter/material.dart';
import 'database.dart'; // فایل دیتابیس

class UserPage extends StatefulWidget {
  @override
  _UserPageState createState() => _UserPageState();
}

class _UserPageState extends State<UserPage> {
  final AppDatabase _db = AppDatabase();

  @override
  void initState() {
    super.initState();
    _addUser();
    _getUsers();
  }

  Future<void> _addUser() async {
    User user = UsersCompanion(
      name: Value('علی رضا'),
      email: Value('ali.reza@example.com'),
    );
    await _db.insertUser(user);
  }

  Future<void> _getUsers() async {
    List<User> users = await _db.getAllUsers();
    for (var user in users) {
      print('User Name: ${user.name}, Email: ${user.email}');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Drift Example'),
      ),
      body: Center(
        child: Text('Check console for output'),
      ),
    );
  }
}

توضیحات کد:

تعریف ویجت UserPage:

یک ویجت Stateful ایجاد می‌کنیم که در initState توابع _addUser و _getUsers را فراخوانی می‌کند.

تابع _addUser:

یک شیء User جدید ایجاد کرده و آن را به دیتابیس اضافه می‌کنیم.

تابع _getUsers:

لیست کاربران را از دیتابیس بازیابی کرده و آن‌ها را در کنسول چاپ می‌کنیم.

نکات مهم هنگام کار با Drift:

استفاده از ابزارهای تولید کد:

Drift از ابزارهایی مانند build_runner برای تولید کدهای مورد نیاز استفاده می‌کند. اطمینان حاصل کنید که این ابزارها را نصب کرده و دستورات لازم را اجرا کنید:

flutter pub run build_runner build

مدیریت نسخه دیتابیس:

هنگام تغییر ساختار دیتابیس (مثلاً افزودن جدول جدید یا تغییر ساختار جدول موجود)، باید نسخه دیتابیس را افزایش دهید و عملیات‌های مورد نیاز را در متد onUpgrade تعریف کنید.

@DriftDatabase(tables: [Users])
class AppDatabase extends _$AppDatabase {
  AppDatabase() : super(FlutterQueryExecutor.inDatabaseFolder(path: 'app.db'));

  @override
  int get schemaVersion => 2;

  @override
  MigrationStrategy get migration => MigrationStrategy(
    onUpgrade: (db, from, to) async {
      if (from < 2) {
        await db.schema.createTable(users);
        // عملیات‌های آپگرید دیگر
      }
    },
  );

  Future<List<User>> getAllUsers() => select(users).get();
  Future insertUser(Insertable<User> user) => into(users).insert(user);
}

استفاده از Streams برای به‌روزرسانی‌های بلادرنگ:

Drift از Streams برای دریافت به‌روزرسانی‌های بلادرنگ دیتابیس پشتیبانی می‌کند. این امکان را به شما می‌دهد تا UI خود را بر اساس تغییرات دیتابیس به روز کنید.

Stream<List<User>> watchAllUsers() => select(users).watch();

مدیریت ارتباط با دیتابیس:

بهتر است یک نمونه‌ی یکتا از دیتابیس را در اپلیکیشن خود مدیریت کنید تا از باز و بسته شدن مکرر دیتابیس جلوگیری شود.

class DatabaseHelper {
  static final DatabaseHelper _instance = DatabaseHelper._internal();
  factory DatabaseHelper() => _instance;
  DatabaseHelper._internal();

  static AppDatabase? _database;

  Future<AppDatabase> get database async {
    if (_database != null) return _database!;
    _database = AppDatabase();
    return _database!;
  }
}

امنیت داده‌ها:

اگر داده‌های حساس را در دیتابیس ذخیره می‌کنید، از روش‌های رمزنگاری برای محافظت از داده‌ها استفاده کنید.

بهینه‌سازی عملکرد:

برای بهبود عملکرد دیتابیس، از اندیس‌ها (Indexes) برای فیلدهای پرتکرار استفاده کنید.
از عملیات‌های دسته‌ای (Batch Operations) برای اجرای چندین عملیات به صورت همزمان بهره ببرید.

در این بخش، با دو گزینه محبوب برای مدیریت دیتابیس محلی در Flutter یعنی Hive و Drift آشنا شدید. یاد گرفتید چگونه این دیتابیس‌ها را نصب و پیکربندی کنید و با استفاده از مثال‌های عملی، نحوه‌ی ذخیره‌سازی و بازیابی داده‌ها را بررسی کردید. هر یک از این ابزارها دارای ویژگی‌ها و مزایای خاص خود هستند که بسته به نیاز پروژه می‌توانید یکی را انتخاب کنید. در بخش‌های بعدی، به بررسی Firebase و سایر ابزارهای مرتبط با کار با دیتابیس و API در Flutter خواهیم پرداخت تا گزینه‌های بیشتری برای مدیریت داده‌ها در اختیار داشته باشید.

Firebase

در کار با دیتابیس و API در Flutter، Firebase یکی از قدرتمندترین و جامع‌ترین پلتفرم‌های موجود برای توسعه اپلیکیشن‌های موبایل و وب است. Firebase توسط گوگل ارائه شده و ابزارها و سرویس‌های متنوعی را برای مدیریت دیتابیس، احراز هویت، ذخیره‌سازی فایل‌ها، ارسال اعلان‌ها و بسیاری از قابلیت‌های دیگر در اختیار توسعه‌دهندگان قرار می‌دهد. در این بخش، به معرفی Firebase و یکی از مهم‌ترین سرویس‌های آن یعنی احراز هویت (Authentication) می‌پردازیم.

معرفی Firebase

Firebase یک پلتفرم توسعه اپلیکیشن‌های موبایل و وب است که توسط گوگل پشتیبانی می‌شود. این پلتفرم ابزارهای متنوعی را برای توسعه‌دهندگان فراهم می‌کند تا بتوانند به سرعت و به آسانی اپلیکیشن‌های قدرتمند و مقیاس‌پذیر ایجاد کنند. کار با دیتابیس و API در Flutter با استفاده از Firebase، توسعه‌دهندگان می‌توانند به راحتی از قابلیت‌های ابری مانند ذخیره‌سازی داده‌ها، احراز هویت کاربران، ارسال اعلان‌ها و بسیاری دیگر بهره‌مند شوند.

ویژگی‌های کلیدی Firebase:

پشتیبانی از چندین پلتفرم: Firebase به طور کامل با Flutter و دیگر پلتفرم‌های توسعه موبایل و وب سازگار است.
دیتابیس‌های ابری: Firebase دو نوع دیتابیس ابری ارائه می‌دهد: Realtime Database و Firestore که هر کدام ویژگی‌ها و مزایای خاص خود را دارند.
احراز هویت آسان: Firebase Authentication امکان پیاده‌سازی سریع و ساده سیستم‌های احراز هویت متنوع را فراهم می‌کند.
ذخیره‌سازی فایل‌ها: Firebase Storage امکان ذخیره‌سازی و مدیریت فایل‌های بزرگ مانند تصاویر و ویدیوها را فراهم می‌کند.
ارسال اعلان‌ها: Firebase Cloud Messaging (FCM) امکان ارسال اعلان‌های بلادرنگ به کاربران را فراهم می‌کند.
تحلیل و مانیتورینگ: Firebase Analytics و Crashlytics ابزارهایی برای تحلیل رفتار کاربران و مانیتورینگ خطاهای اپلیکیشن ارائه می‌دهند.
Cloud Functions: امکان اجرای کدهای سمت سرور به صورت خودکار و بدون نیاز به مدیریت سرورهای جداگانه را فراهم می‌کند.

احراز هویت (Authentication)

Firebase Authentication یکی از سرویس‌های کلیدی Firebase است که به شما امکان می‌دهد تا سیستم احراز هویت مطمئنی را برای اپلیکیشن خود پیاده‌سازی کنید. این سرویس از روش‌های مختلف احراز هویت مانند ایمیل و رمز عبور، ورود با گوگل، فیسبوک، توئیتر و سایر ارائه‌دهندگان احراز هویت پشتیبانی می‌کند. استفاده از Firebase Authentication نه تنها روند توسعه را ساده‌تر می‌کند بلکه امنیت بالایی را نیز فراهم می‌آورد.

نصب Firebase Authentication

برای استفاده از Firebase Authentication در پروژه Flutter خود، ابتدا باید کتابخانه‌های مورد نیاز را به فایل pubspec.yaml اضافه کنید. مراحل نصب به شرح زیر است:

باز کردن فایل pubspec.yaml:

این فایل در ریشه پروژه Flutter شما قرار دارد و برای مدیریت وابستگی‌ها (dependencies) استفاده می‌شود.

اضافه کردن کتابخانه‌های firebase_auth و firebase_core به بخش dependencies:

dependencies:
  firebase_auth: ^4.0.0
  firebase_core: ^2.0.0

firebase_core: کتابخانه‌ی اصلی برای راه‌اندازی Firebase در Flutter.
firebase_auth: کتابخانه‌ی مربوط به احراز هویت Firebase.
اجرای دستور flutter pub get:

پس از اضافه کردن این وابستگی‌ها، باید دستور زیر را در ترمینال پروژه اجرا کنید تا کتابخانه‌ها دانلود و نصب شوند:

flutter pub get

راه‌اندازی Firebase در اپلیکیشن:

قبل از استفاده از Firebase Authentication، باید Firebase را در اپلیکیشن خود راه‌اندازی کنید. برای این کار، معمولاً در فایل main.dart، تابع initializeApp را فراخوانی می‌کنید:

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart'; // فایل تنظیمات Firebase

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Firebase Demo',
      home: HomePage(),
    );
  }
}

توجه: فایل firebase_options.dart شامل تنظیمات مربوط به پروژه Firebase شما است و معمولاً توسط ابزار Firebase CLI تولید می‌شود.

استفاده از Firebase Authentication

پس از نصب و راه‌اندازی Firebase Authentication، می‌توانید از آن برای پیاده‌سازی سیستم‌های احراز هویت مختلف استفاده کنید. در این بخش، یک مثال ساده از ثبت‌نام کاربر با استفاده از ایمیل و رمز عبور را بررسی می‌کنیم.

مثال عملی: ثبت‌نام کاربر

در این مثال، نحوه‌ی استفاده از Firebase Authentication برای ثبت‌نام یک کاربر جدید با ایمیل و رمز عبور را نشان می‌دهیم.

وارد کردن کتابخانه‌های مورد نیاز:

import 'package:firebase_auth/firebase_auth.dart';

تعریف تابع ثبت‌نام کاربر:

Future<void> registerUser(String email, String password) async {
  try {
    UserCredential userCredential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
      email: email,
      password: password,
    );
    print('User registered: ${userCredential.user?.email}');
  } on FirebaseAuthException catch (e) {
    if (e.code == 'weak-password') {
      print('The password provided is too weak.');
    } else if (e.code == 'email-already-in-use') {
      print('The account already exists for that email.');
    } else {
      print('Error: $e');
    }
  } catch (e) {
    print('Error: $e');
  }
}

توضیحات:
createUserWithEmailAndPassword: متدی برای ایجاد یک کاربر جدید با ایمیل و رمز عبور.

مدیریت خطاها:
FirebaseAuthException: خطاهای خاص مربوط به احراز هویت مانند رمز عبور ضعیف یا ایمیل موجود.
catch (e): مدیریت سایر خطاهای احتمالی.

استفاده از تابع ثبت‌نام در اپلیکیشن:

این تابع را می‌توانید در رویدادهایی مانند فشردن دکمه ثبت‌نام در فرم ثبت‌نام کاربر فراخوانی کنید:

void handleRegister() {
  String email = 'ali.reza@example.com';
  String password = 'securePassword123';
  registerUser(email, password);
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('Register User'),
    ),
    body: Center(
      child: ElevatedButton(
        onPressed: handleRegister,
        child: Text('Register'),
      ),
    ),
  );
}

توضیحات:
handleRegister: تابعی که ایمیل و رمز عبور را تعریف کرده و تابع registerUser را فراخوانی می‌کند.
ویجت ElevatedButton: دکمه‌ای برای ثبت‌نام کاربر که با فشردن آن تابع handleRegister اجرا می‌شود.

مدیریت ورود و خروج کاربر

علاوه بر ثبت‌نام، Firebase Authentication امکاناتی برای ورود و خروج کاربران نیز فراهم می‌کند. در ادامه نحوه‌ی پیاده‌سازی ورود و خروج کاربران را بررسی می‌کنیم.

ورود کاربر

Future<void> signInUser(String email, String password) async {
  try {
    UserCredential userCredential = await FirebaseAuth.instance.signInWithEmailAndPassword(
      email: email,
      password: password,
    );
    print('User signed in: ${userCredential.user?.email}');
  } on FirebaseAuthException catch (e) {
    if (e.code == 'user-not-found') {
      print('No user found for that email.');
    } else if (e.code == 'wrong-password') {
      print('Wrong password provided.');
    } else {
      print('Error: $e');
    }
  } catch (e) {
    print('Error: $e');
  }
}

خروج کاربر

Future<void> signOutUser() async {
  try {
    await FirebaseAuth.instance.signOut();
    print('User signed out');
  } catch (e) {
    print('Error: $e');
  }
}

توضیحات کلی درباره Firebase Authentication

پشتیبانی از روش‌های احراز هویت مختلف: Firebase Authentication از روش‌های مختلفی مانند احراز هویت با ایمیل و رمز عبور، ورود با گوگل، فیسبوک، توئیتر و … پشتیبانی می‌کند. این امر به شما امکان می‌دهد تا سیستم احراز هویت متنوعی را بر اساس نیازهای اپلیکیشن خود پیاده‌سازی کنید.

امنیت بالا: Firebase Authentication از استانداردهای امنیتی بالایی برخوردار است که تضمین می‌کند داده‌های کاربران به صورت امن ذخیره و مدیریت شوند.

یکپارچگی با دیگر سرویس‌های Firebase: Firebase Authentication به طور یکپارچه با دیگر سرویس‌های Firebase مانند Firestore، Realtime Database و Cloud Functions کار می‌کند، که این امر امکان ایجاد اپلیکیشن‌های پیچیده‌تر و قابلیت‌های بیشتر را فراهم می‌آورد.

مدیریت آسان کاربران: Firebase کنسولی برای مدیریت کاربران فراهم می‌کند که در آن می‌توانید اطلاعات کاربران را مشاهده کرده، حساب‌های کاربری را مدیریت کنید و تنظیمات مربوط به احراز هویت را انجام دهید.

پشتیبانی از زبان‌های مختلف: Firebase Authentication با زبان‌های برنامه‌نویسی مختلفی مانند Dart، JavaScript، Swift و … سازگار است که این امر امکان استفاده از آن در پروژه‌های مختلف را فراهم می‌کند.

Cloud Functions و ارسال Push Notification

در کار با دیتابیس و API در Flutter، Cloud Functions و Firebase Cloud Messaging (FCM) دو سرویس حیاتی هستند که امکانات پیشرفته‌ای را برای توسعه‌دهندگان فراهم می‌کنند. در این بخش، به بررسی هر یک از این سرویس‌ها، نحوه نصب و استفاده از آن‌ها در پروژه‌های Flutter می‌پردازیم و با مثال‌های عملی آن‌ها را توضیح می‌دهیم.

Cloud Functions

Cloud Functions یک سرویس بدون سرور (Serverless) است که توسط Firebase ارائه شده و به شما اجازه می‌دهد تا کدهای سمت سرور را به طور خودکار و در پاسخ به رویدادهای مختلف اجرا کنید. این قابلیت به شما امکان می‌دهد تا پردازش‌های پیچیده‌تر، عملیات‌های پس‌زمینه، و افزایش امنیت اپلیکیشن خود را بدون نیاز به مدیریت سرورهای جداگانه انجام دهید.

ویژگی‌های Cloud Functions:

بدون نیاز به مدیریت سرور:
شما نیازی به مدیریت زیرساخت‌های سرور ندارید. Firebase به طور خودکار منابع لازم را فراهم می‌کند.
پاسخ به رویدادها:
Cloud Functions می‌تواند به رویدادهای مختلفی مانند تغییرات در Firestore، احراز هویت کاربران، درخواست‌های HTTP و غیره پاسخ دهد.
امنیت و مقیاس‌پذیری:
کدهای شما به صورت ایمن اجرا می‌شوند و Firebase به طور خودکار مقیاس‌پذیری را مدیریت می‌کند تا با افزایش بار سرویس سازگار باشد.
یکپارچگی با سرویس‌های Firebase:
Cloud Functions به راحتی با سایر سرویس‌های Firebase مانند Firestore، Authentication، و Firebase Storage یکپارچه می‌شود.
پشتیبانی از زبان‌های مختلف:
Cloud Functions از زبان‌های مختلفی مانند JavaScript، TypeScript و Python پشتیبانی می‌کند. در اینجا به استفاده از Dart از طریق Flutter اشاره نمی‌کنیم، بلکه برای فراخوانی توابع از سمت Flutter توضیح می‌دهیم.

نصب Cloud Functions

برای استفاده از Cloud Functions در پروژه Flutter خود، ابتدا باید کتابخانه‌ی cloud_functions را به پروژه اضافه کنید.

مراحل نصب Cloud Functions:

باز کردن فایل pubspec.yaml:

این فایل در ریشه پروژه Flutter شما قرار دارد و برای مدیریت وابستگی‌ها (dependencies) استفاده می‌شود.

اضافه کردن کتابخانه cloud_functions به بخش dependencies:

dependencies:
  cloud_functions: ^4.0.0

اجرای دستور flutter pub get:

پس از اضافه کردن این وابستگی‌ها، باید دستور زیر را در ترمینال پروژه اجرا کنید تا کتابخانه دانلود و نصب شود:

flutter pub get

راه‌اندازی Cloud Functions در Firebase Console:

به Firebase Console بروید و پروژه خود را انتخاب کنید.
به بخش Functions بروید و مراحل راه‌اندازی اولیه را دنبال کنید.
اطمینان حاصل کنید که Cloud Functions به درستی پیکربندی شده و پروژه شما آماده استفاده است.

استفاده از Cloud Functions

پس از نصب کتابخانه و راه‌اندازی Cloud Functions، می‌توانید توابع را از اپلیکیشن Flutter خود فراخوانی کنید. در اینجا یک مثال ساده از نحوه‌ی فراخوانی یک Cloud Function آورده شده است.

مثال عملی: فراخوانی یک Cloud Function

فرض کنید شما یک تابع در Cloud Functions با نام myFunction دارید که برخی پردازش‌ها را انجام می‌دهد و نتیجه‌ای را بازمی‌گرداند.

وارد کردن کتابخانه‌های مورد نیاز:

import 'package:cloud_functions/cloud_functions.dart';

تعریف تابع فراخوانی Cloud Function:

Future<void> callFunction() async {
  try {
    // ایجاد مرجع به Cloud Function
    HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('myFunction');
    
    // فراخوانی تابع با پارامترهای مورد نیاز (اختیاری)
    final result = await callable.call(<String, dynamic>{
      'param1': 'value1',
      'param2': 'value2',
    });
    
    // دریافت نتیجه از تابع
    print('Function Result: ${result.data}');
  } on FirebaseFunctionsException catch (e) {
    print('Function error: ${e.code} - ${e.message}');
  } catch (e) {
    print('Error: $e');
  }
}

استفاده از تابع در اپلیکیشن:

شما می‌توانید این تابع را در رویدادهایی مانند فشردن دکمه یا بارگذاری صفحه فراخوانی کنید:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('Cloud Functions Example'),
    ),
    body: Center(
      child: ElevatedButton(
        onPressed: callFunction,
        child: Text('Call Function'),
      ),
    ),
  );
}

توضیحات:
HttpsCallable: این کلاس برای ایجاد مرجع به Cloud Function استفاده می‌شود.
call: متد call تابع را فراخوانی می‌کند و می‌توانید پارامترهای مورد نیاز را به آن ارسال کنید.
مدیریت خطاها: از بلوک try-catch برای مدیریت خطاهای احتمالی استفاده می‌شود. FirebaseFunctionsException خطاهای خاص مربوط به Cloud Functions را مدیریت می‌کند.

ارسال Push Notification

Firebase Cloud Messaging (FCM) یک سرویس پیام‌رسانی ابری است که به شما امکان می‌دهد تا اعلان‌های Push Notification را به کاربران اپلیکیشن خود ارسال کنید. FCM برای ارسال اعلان‌های بلادرنگ، پیام‌های اطلاع‌رسانی و به‌روزرسانی‌های داده به کاربران استفاده می‌شود.

ویژگی‌های Firebase Cloud Messaging:

ارسال اعلان‌های بلادرنگ:
اعلان‌ها به صورت مستقیم و بلادرنگ به دستگاه‌های کاربران ارسال می‌شوند.
پشتیبانی از چندین پلتفرم:
FCM از پلتفرم‌های مختلفی مانند Android، iOS و وب پشتیبانی می‌کند.
سفارشی‌سازی اعلان‌ها:
امکان سفارشی‌سازی اعلان‌ها با استفاده از تصاویر، صداها، و سایر عناصر موجود است.
دریافت و مدیریت اعلان‌ها:
اپلیکیشن‌های شما می‌توانند اعلان‌های دریافت شده را مدیریت کرده و واکنش‌های مناسبی نشان دهند.
تحلیل و گزارش‌گیری:
FCM امکاناتی برای تحلیل و گزارش‌گیری از عملکرد اعلان‌ها فراهم می‌کند.

نصب Firebase Messaging

برای استفاده از FCM در پروژه Flutter خود، ابتدا باید کتابخانه‌ی firebase_messaging را به پروژه اضافه کنید.

مراحل نصب Firebase Messaging:

باز کردن فایل pubspec.yaml:

این فایل در ریشه پروژه Flutter شما قرار دارد و برای مدیریت وابستگی‌ها (dependencies) استفاده می‌شود.

اضافه کردن کتابخانه firebase_messaging به بخش dependencies:

dependencies:
  firebase_messaging: ^14.0.0

اجرای دستور flutter pub get:

پس از اضافه کردن این وابستگی‌ها، باید دستور زیر را در ترمینال پروژه اجرا کنید تا کتابخانه دانلود و نصب شود:

flutter pub get

راه‌اندازی Firebase Messaging در Firebase Console:

به Firebase Console بروید و پروژه خود را انتخاب کنید.
به بخش Cloud Messaging بروید و تنظیمات لازم را انجام دهید.
برای iOS، باید فایل‌های پیکربندی مربوط به APNs را آپلود کنید.
پیکربندی AndroidManifest.xml و Info.plist:

برای Android:

<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.yourapp">

    <application
        ...
        >
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_icon"
            android:resource="@drawable/ic_notification" />
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_channel_id"
            android:value="high_importance_channel" />
        ...
    </application>
</manifest>

برای iOS:

<!-- Info.plist -->
<key>FirebaseAppDelegateProxyEnabled</key>
<false/>
<key>UIBackgroundModes</key>
<array>
    <string>remote-notification</string>
</array>

 

استفاده از Firebase Messaging

پس از نصب و پیکربندی Firebase Messaging، می‌توانید اعلان‌ها را دریافت و مدیریت کنید. در اینجا یک مثال ساده از نحوه‌ی دریافت پیام‌های Push آورده شده است.

مثال عملی: دریافت پیام‌های Push

وارد کردن کتابخانه‌های مورد نیاز:

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';

تعریف تابع تنظیم Firebase Messaging:

Future<void> setupFirebaseMessaging() async {
  FirebaseMessaging messaging = FirebaseMessaging.instance;

  // درخواست اجازه از کاربر برای دریافت اعلان‌ها
  NotificationSettings settings = await messaging.requestPermission(
    alert: true,
    badge: true,
    sound: true,
  );

  if (settings.authorizationStatus == AuthorizationStatus.authorized) {
    print('User granted permission');

    // دریافت توکن دستگاه
    String? token = await messaging.getToken();
    print('FCM Token: $token');

    // دریافت پیام‌ها زمانی که اپلیکیشن در foreground است
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      print('Message received: ${message.notification?.title}');
      // می‌توانید اعلان را نمایش دهید یا داده‌ها را پردازش کنید
    });

    // دریافت پیام‌ها زمانی که اپلیکیشن در background یا terminated است
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      print('Message clicked!');
      // می‌توانید به صفحه مورد نظر بروید یا عملیات خاصی انجام دهید
    });
  } else {
    print('User declined or has not accepted permission');
  }
}

استفاده از تابع تنظیم در اپلیکیشن:

معمولاً این تابع را در متد initState یک ویجت Stateful فراخوانی می‌کنید:

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    setupFirebaseMessaging();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter FCM Demo',
      home: HomePage(),
    );
  }
}

توضیحات:
درخواست اجازه برای اعلان‌ها: با استفاده از متد requestPermission، اجازه دریافت اعلان‌ها از کاربر درخواست می‌شود. بسته به پاسخ کاربر، اعلان‌ها فعال یا غیر فعال می‌شوند.

دریافت توکن دستگاه: توکن FCM که به دستگاه شما اختصاص داده شده است، با استفاده از متد getToken دریافت می‌شود. این توکن برای ارسال اعلان‌ها به دستگاه خاص استفاده می‌شود.

دریافت پیام‌ها در foreground: با استفاده از FirebaseMessaging.onMessage.listen می‌توانید پیام‌های دریافت شده زمانی که اپلیکیشن در foreground است را مدیریت کنید.

دریافت پیام‌ها در background یا terminated: با استفاده از FirebaseMessaging.onMessageOpenedApp.listen می‌توانید پیام‌های کلیک شده توسط کاربر را مدیریت کنید و به صفحات مورد نظر هدایت شوید.

ارسال اعلان‌ها از Firebase Console:

برای ارسال اعلان‌ها به کاربران، می‌توانید از Firebase Console به راحتی اعلان‌ها را ارسال کنید:

به Firebase Console بروید و پروژه خود را انتخاب کنید.
به بخش Cloud Messaging بروید.
روی دکمه Send your first message کلیک کنید.
عنوان و متن اعلان را وارد کنید.
هدف (Target) را مشخص کنید، به عنوان مثال تمامی کاربران یا کاربران خاص با استفاده از توکن‌ها.
تنظیمات اضافی مانند تصویر، صدا و زمان ارسال را انجام دهید.
روی Send Message کلیک کنید تا اعلان ارسال شود.

نکات مهم هنگام کار با Cloud Functions و FCM:

مدیریت توکن‌ها:
توکن‌های FCM ممکن است تغییر کنند، بنابراین باید مکانیزمی برای بروزرسانی توکن‌ها در دیتابیس خود پیاده‌سازی کنید.
امنیت Cloud Functions:
اطمینان حاصل کنید که توابع شما به درستی از دسترسی‌های غیرمجاز محافظت شده‌اند. از احراز هویت و مجوزهای مناسب استفاده کنید.
بهینه‌سازی اعلان‌ها:
برای بهبود تجربه کاربری، اعلان‌ها را به صورت هوشمندانه و با محتوای مرتبط ارسال کنید. از ارسال اعلان‌های غیرضروری خودداری کنید تا کاربران از اپلیکیشن شما ناامید نشوند.

مدیریت خطاها در Cloud Functions:

در Cloud Functions، خطاها را به درستی مدیریت کنید و از ثبت لاگ‌ها (Logging) برای تشخیص و رفع مشکلات استفاده کنید.
پشتیبانی از چندین پلتفرم:
اطمینان حاصل کنید که اعلان‌ها در تمامی پلتفرم‌های مورد نظر (Android، iOS، وب) به درستی نمایش داده می‌شوند و با ویژگی‌های هر پلتفرم سازگار هستند.
تحلیل و بهبود عملکرد:
از ابزارهای تحلیل Firebase برای پیگیری عملکرد اعلان‌ها و Cloud Functions استفاده کنید تا بتوانید به بهینه‌سازی و بهبود مداوم سیستم‌ها بپردازید.

نتیجه‌گیری

کار با دیتابیس و API در Flutter ابزاری قدرتمند برای توسعه‌دهندگان است که امکان ایجاد اپلیکیشن‌های پیشرفته، مقیاس‌پذیر و امن را فراهم می‌کند. با استفاده از تکنیک‌ها و ابزارهای معرفی شده در این مقاله، می‌توانید اپلیکیشن‌هایی بسازید که نیازهای کاربران را به بهترین شکل برآورده سازند و تجربه کاربری بی‌نظیری ارائه دهند. ادامه دادن به یادگیری و به‌روزرسانی دانش خود در این حوزه، کلید موفقیت در توسعه اپلیکیشن‌های Flutter است.

با تمرین مستمر و بهره‌گیری از منابع آموزشی معتبر، می‌توانید مهارت‌های خود را در کار با دیتابیس و API در Flutter به سطح بالاتری برسانید و اپلیکیشن‌های قدرتمند و کارآمدی ایجاد کنید که به نیازهای کاربران پاسخگوی باشند.

آموزش کار با دیتابیس و API در Flutter

دیدگاه های شما

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *