hdd, computer, laptop, storage, data, pc, hard drive, hardware, technology, hdd, hdd, storage, storage, storage, storage, storage, data, data, data, data, hard drive, hard drive, hard drive, hard drive, hardware, hardware, hardware

كيف تحفظ بياناتك داخل الجهاز بسهولة؟ دليل شامل للمبتدئين

تخيل أنك فتحت تطبيق ملاحظات، أضفت بعض الأفكار ثم أغلقت التطبيق. في المرة التالية، هل تتوقع أن تختفي الملاحظات؟ 😅 طبعًا لا! يجب أن يظل التطبيق محتفظًا ببياناتك حتى بعد إغلاقه. هنا يأتي دور التخزين المحلي (Local Storage) في Flutter، الذي يسمح لتطبيقاتك بحفظ البيانات مباشرة داخل جهاز المستخدم دون الحاجة إلى اتصال بالإنترنت. في هذا الدليل الشامل، سنشرح كل شيء خطوة بخطوة للمبتدئين، مع أمثلة كود عملية وتفسيرات مفصلة. إذا كنت تبحث عن “التخزين المحلي في Flutter” أو “حفظ بيانات في Flutter باستخدام SharedPreferences” أو “SQLite في Flutter شرح”، فأنت في المكان الصحيح. سنغطي الطرق المختلفة، متى تستخدم كل واحدة، وأفضل الممارسات لجعل تطبيقك أكثر كفاءة وأمانًا.

في هذا المقال ستتعلم:

  • ما هو التخزين المحلي ولماذا هو ضروري في تطوير التطبيقات بـ Flutter.
  • كيفية استخدام SharedPreferences للبيانات البسيطة.
  • كيفية إعداد واستخدام SQLite للبيانات المعقدة والمنظمة.
  • خيارات أخرى مثل التخزين في الملفات للصور والمستندات.
  • أفضل الممارسات لتجنب الأخطاء الشائعة وتحسين الأداء.

دعونا نبدأ بفهم الأساسيات، ثم ننتقل إلى الأمثلة العملية!

🗂️ ما هو التخزين المحلي في Flutter ولماذا نحتاجه؟

للمبتدئين، دعونا نبسط المفهوم: التخزين المحلي (Local Storage) هو عملية حفظ البيانات داخل جهاز المستخدم نفسه، مثل الهاتف أو الجهاز اللوحي، بدلاً من الاعتماد على خوادم خارجية عبر الإنترنت. في Flutter، يمكنك حفظ أي شيء من إعدادات بسيطة إلى قواعد بيانات كاملة، مما يجعل تطبيقك يعمل بشكل أفضل حتى بدون اتصال.

لماذا نحتاج إلى التخزين المحلي في Flutter؟

  • الحفاظ على البيانات بعد إغلاق التطبيق: بدون تخزين، تختفي البيانات كلما أغلق المستخدم التطبيق.
  • دعم العمل دون إنترنت (Offline Mode): مثال: تطبيق قائمة مهام يحفظ المهام محليًا، ثم يزامنها مع الخادم عند الاتصال.
  • تحسين الأداء: بدل جلب البيانات من الإنترنت كل مرة، احفظها محليًا للوصول السريع.
  • خصوصية البيانات: بعض البيانات الحساسة (مثل إعدادات المستخدم) أفضل حفظها داخل الجهاز.

الأمثلة الشائعة كثيرة:

  • حفظ إعدادات التطبيق مثل الوضع الليلي، اللغة المفضلة، أو آخر شاشة مفتوحة.
  • تخزين بيانات تسجيل الدخول مثل Token أو Session لتجنب تسجيل الدخول كل مرة.
  • حفظ قوائم مهام، ملاحظات، أو حتى صور وفيديوهات للوصول إليها بدون إنترنت.
  • في التطبيقات المتقدمة، حفظ بيانات مترابطة مثل سجلات معاملات أو قوائم مشتريات.

بدون التخزين المحلي، سيكون تطبيقك محدودًا جدًا. الآن، دعونا نستعرض الخيارات الرئيسية في Flutter، مع تفسيرات مفصلة وأمثلة كود.

الخيار الأول: SharedPreferences في Flutter (للتخزين البسيط والسريع)

إذا كانت بياناتك بسيطة مثل نصوص أو أرقام، فإن SharedPreferences هو الخيار الأمثل. تخيلها كـ “دفتر ملاحظات صغير” داخل الجهاز يخزن قيمًا بسيطة مثل اسم المستخدم أو تفضيلات الإعدادات. هذه المكتبة سهلة الاستخدام ولا تتطلب قواعد بيانات معقدة.

كيفية تثبيت وإعداد SharedPreferences في Flutter

أضف الحزمة إلى pubspec.yaml:

dependencies:
  shared_preferences: ^2.2.0

ثم شغل flutter pub get. الآن، أنت جاهز للحفظ والاسترجاع!

مثال عملي: حفظ واسترجاع اسم المستخدم في Flutter

دعونا ننشئ فئة مساعدة لجعل الكود نظيفًا:

import 'package:shared_preferences/shared_preferences.dart';

class UserPreferences {
  // دالة لحفظ البيانات                       (async -  لأنها غير متزامنة)
  static Future<void> saveUsername(String username) async {
    final prefs = await SharedPreferences.getInstance();  // الحصول على مثيل SharedPreferences
    await prefs.setString('username', username);  // حفظ كـ String بمفتاح 'username'
  }

  // دالة لاسترجاع البيانات
  static Future<String?> getUsername() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getString('username');  // إرجاع القيمة، أو null إذا غير موجود
  }

  // مثال إضافي: حفظ قيمة Boolean (مثل الوضع الليلي)
  static Future<void> saveDarkMode(bool isDark) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setBool('dark_mode', isDark);
  }

  static Future<bool?> isDarkMode() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getBool('dark_mode');
  }
}

كيفية استخدامها في الواجهة

في صفحة تسجيل الدخول، على سبيل المثال:

import 'package:flutter/material.dart';

class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _usernameController = TextEditingController();

  Future<void> _saveAndNavigate() async {
    final username = _usernameController.text;
    if (username.isNotEmpty) {
      await UserPreferences.saveUsername(username);
      Navigator.pushReplacementNamed(context, '/home');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('تسجيل الدخول')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _usernameController,
              decoration: const InputDecoration(labelText: 'اسم المستخدم'),
            ),
            ElevatedButton(
              onPressed: _saveAndNavigate,
              child: const Text('حفظ ودخول'),
            ),
          ],
        ),
      ),
    );
  }
}

في الصفحة الرئيسية، استرجع الاسم:

Future<String?> _getSavedUsername() async {
  return await UserPreferences.getUsername();
}

متى تستخدم SharedPreferences في Flutter؟

  • للبيانات البسيطة مثل: نصوص (String)، أرقام (int/double)، Boolean (true/false)، أو قوائم نصوص (List).
  • إعدادات التطبيق مثل اللغة أو الثيم.
  • بيانات صغيرة الحجم لا تحتاج إلى بحث أو تصفية معقدة.

عيوبها: لا تناسب البيانات الكبيرة أو المترابطة (مثل جداول متعددة)، وهي غير مشفرة افتراضيًا (استخدم flutter_secure_storage للبيانات الحساسة).

📌 الخيار الثاني: SQLite في Flutter (للتخزين المعقد والمنظم)

إذا كانت بياناتك معقدة، مثل قائمة مهام مع تفاصيل (عنوان، تاريخ، حالة)، فإن SQLite هو قاعدة بيانات كاملة داخل الجهاز. تخيلها كـ “جدول Excel” يمكن البحث فيه، التصفية، والتعديل بكفاءة.

كيفية تثبيت وإعداد SQLite في Flutter

أضف الحزم:

dependencies:
  sqflite: ^2.3.0
  path: ^1.9.0  # للوصول إلى مسارات الملفات

ثم flutter pub get.

مثال عملي: تخزين قائمة مهام (To-Do List) باستخدام SQLite في Flutter

أنشئ فئة مساعدة لقاعدة البيانات:

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

class TodoDatabase {
  static Database? _db;

  // الحصول على قاعدة البيانات (singleton لتجنب فتح متعدد)
  static Future<Database> getDatabase() async {
    if (_db != null) return _db!;
    final path = join(await getDatabasesPath(), 'todo.db');  // مسار الملف
    _db = await openDatabase(
      path,
      onCreate: (db, version) async {
        // إنشاء الجدول عند أول تشغيل
        await db.execute(
          'CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, done INTEGER)',
        );
      },
      version: 1,  // رقم الإصدار للترقيات المستقبلية
    );
    return _db!;
  }

  // إضافة مهمة جديدة
  static Future<void> insertTask(String title) async {
    final db = await getDatabase();
    await db.insert('todos', {'title': title, 'done': 0});  // done: 0 = غير مكتمل
  }

  // جلب جميع المهام
  static Future<List<Map<String, dynamic>>> getTasks() async {
    final db = await getDatabase();
    return db.query('todos');  // إرجاع قائمة بالسجلات
  }

  // تحديث حالة مهمة (مكتملة أم لا)
  static Future<void> updateTask(int id, bool done) async {
    final db = await getDatabase();
    await db.update('todos', {'done': done ? 1 : 0}, where: 'id = ?', whereArgs: [id]);
  }

  // حذف مهمة
  static Future<void> deleteTask(int id) async {
    final db = await getDatabase();
    await db.delete('todos', where: 'id = ?', whereArgs: [id]);
  }
}

كيفية استخدام SQLite في الواجهة

في شاشة قائمة المهام:

import 'package:flutter/material.dart';

class TodoScreen extends StatefulWidget {
  const TodoScreen({super.key});

  @override
  State<TodoScreen> createState() => _TodoScreenState();
}

class _TodoScreenState extends State<TodoScreen> {
  final _taskController = TextEditingController();
  List<Map<String, dynamic>> _tasks = [];

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

  Future<void> _loadTasks() async {
    final tasks = await TodoDatabase.getTasks();
    setState(() {
      _tasks = tasks;
    });
  }

  Future<void> _addTask() async {
    final title = _taskController.text;
    if (title.isNotEmpty) {
      await TodoDatabase.insertTask(title);
      _taskController.clear();
      _loadTasks();  // إعادة تحميل القائمة
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('قائمة المهام')),
      body: Column(
        children: [
          TextField(
            controller: _taskController,
            decoration: const InputDecoration(labelText: 'أضف مهمة جديدة'),
          ),
          ElevatedButton(
            onPressed: _addTask,
            child: const Text('إضافة'),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: _tasks.length,
              itemBuilder: (context, index) {
                final task = _tasks[index];
                return ListTile(
                  title: Text(task['title']),
                  trailing: Checkbox(
                    value: task['done'] == 1,
                    onChanged: (value) async {
                      await TodoDatabase.updateTask(task['id'], value!);
                      _loadTasks();
                    },
                  ),
                  onLongPress: () async {
                    await TodoDatabase.deleteTask(task['id']);
                    _loadTasks();
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

للمبتدئين: هذا المثال يظهر إضافة، جلب، تحديث، وحذف بيانات. يمكن توسيعه لإضافة فلاتر أو بحث.

متى تستخدم SQLite في Flutter؟

  • لبيانات معقدة ومنظمة مثل جداول مترابطة (مثل مستخدمين وطلباتهم).
  • عند الحاجة إلى عمليات مثل البحث (query)، التصفية، أو الترتيب.
  • لتخزين كميات كبيرة من البيانات محليًا.

عيوبها: أكثر تعقيدًا من SharedPreferences، وتتطلب معرفة SQL الأساسية.

يوجد مكتبات اخرى لتخزين وادارة البيانات محليا مثل hive أو Isar وسوف نتاولهم بالشرح باذن الله.

الخيار الثالث: التخزين في الملفات (File Storage) في Flutter

إذا كنت بحاجة إلى حفظ ملفات مثل الصور، الفيديوهات، أو حتى بيانات JSON كاملة، فإن التخزين في الملفات هو الخيار الأمثل. Flutter يدعم ذلك عبر مكتبة path_provider للوصول إلى مسارات الجهاز.

كيفية تثبيت وإعداد File Storage

أضف الحزم:

dependencies:
  path_provider: ^2.1.0

مثال عملي: حفظ وتحميل ملف JSON في Flutter

أنشئ فئة مساعدة:

import 'dart:io';
import 'package:path_provider/path_provider.dart';

class FileStorage {
  // حفظ بيانات كـ JSON
  static Future<void> saveJson(Map<String, dynamic> data, String fileName) async {
    final directory = await getApplicationDocumentsDirectory();  // مسار آمن داخل التطبيق
    final file = File('${directory.path}/$fileName.json');
    await file.writeAsString(jsonEncode(data));  // تحويل إلى String وكتابة
  }

  // تحميل JSON
  static Future<Map<String, dynamic>?> loadJson(String fileName) async {
    final directory = await getApplicationDocumentsDirectory();
    final file = File('${directory.path}/$fileName.json');
    if (await file.exists()) {
      final content = await file.readAsString();
      return jsonDecode(content);
    }
    return null;  // إذا لم يكن موجودًا
  }
}

الاستخدام في التطبيق

Future<void> _saveData() async {
  final data = {'name': 'أحمد', 'age': 30};
  await FileStorage.saveJson(data, 'user_data');
}

Future<void> _loadData() async {
  final data = await FileStorage.loadJson('user_data');
  if (data != null) {
    print('الاسم: ${data['name']}');  // أحمد
  }
}

للمبتدئين: هذا مناسب للملفات الكبيرة أو التصدير/الاستيراد. للصور، استخدم file.writeAsBytes للبايتات.

متى تستخدم التخزين في الملفات؟

  • لحفظ ملفات المستخدم مثل الصور، PDF، أو ملفات JSON كبيرة.
  • عند الحاجة إلى قراءة/كتابة ملفات خارجية.
  • للتكامل مع مكتبات مثل image_picker لحفظ الصور المختارة.

عيوبها: لا تدعم البحث السريع مثل SQLite، وتكون أبطأ للبيانات الكبيرة. أفضل الممارسات للتخزين المحلي في Flutter

أفضل الممارسات للتخزين المحلي في Flutter

للمبتدئين، إليك نصائح عملية لتجنب الأخطاء وتحسين تطبيقك:

  • اختر الأداة المناسبة حسب الحاجة:
    • إعدادات بسيطة وصغيرة → SharedPreferences.
    • بيانات منظمة وكبيرة → SQLite.
    • ملفات (صور/مستندات) → التخزين في الملفات.
  • لا تحفظ بيانات حساسة بشكل عادي: استخدم flutter_secure_storage لتشفير كلمات المرور أو Tokens.
  • فصل التخزين في طبقة مستقلة: أنشئ مجلد services أو data وضمّن فيه الكود، بدلًا من خلطه مع الـ UI. هذا يسهل الاختبار والصيانة.
  • التعامل مع الأخطاء: استخدم try-catch في العمليات غير المتزامنة، وأظهر رسائل ودية للمستخدم مثل “فشل في الحفظ، جرب مرة أخرى”.
  • تجنّب التخزين المفرط: لا تحفظ بيانات ضخمة جدًا في SharedPreferences (حدود 1MB تقريبًا)، ولا تحفظ كل شيء في SQLite إن لم يكن ضروريًا لتجنب تباطؤ التطبيق.
  • الامتثال للخصوصية: في iOS وAndroid، احصل على إذن المستخدم إذا لزم الأمر، وتجنب حفظ بيانات شخصية دون سبب.
  • النسخ الاحتياطي: فكر في مزامنة البيانات مع Firebase أو خادم لتجنب فقدانها عند إعادة تثبيت التطبيق.

الأخطاء الشائعة في التخزين المحلي في Flutter وكيفية تجنبها

كمبتدئ، قد تواجه هذه المشكلات:

  • نسيان async/await: يؤدي إلى أخطاء في الوصول إلى البيانات غير الجاهزة. الحل: استخدم FutureBuilder أو await دائمًا.
  • حفظ بيانات كبيرة في SharedPreferences: يسبب تباطؤًا. الحل: استخدم SQLite أو ملفات.
  • عدم التعامل مع null: إذا كانت البيانات غير موجودة، قد يحدث خطأ. الحل: استخدم ?? لقيم افتراضية، مثل prefs.getString('key') ?? 'default'.
  • فقدان البيانات عند تحديث التطبيق: في SQLite، زد رقم الإصدار في onUpgrade لترقية الجداول.
  • أمان ضعيف: لا تحفظ كلمات مرور في SharedPreferences. الحل: flutter_secure_storage.

الخاتمة: ابدأ في تطبيق التخزين المحلي في مشاريع Flutter الخاصة بك

تعلمنا في هذا الدليل أن التخزين المحلي في Flutter ليس خيارًا واحدًا بل عدة خيارات متنوعة: SharedPreferences للبيانات البسيطة، SQLite للبيانات المعقدة والمنظمة، والتخزين في الملفات للصور والمستندات. الآن، أنت جاهز لجعل تطبيقاتك تحافظ على بيانات المستخدمين بكفاءة وأمان. جرب الأمثلة في مشروعك التالي، وستلاحظ الفرق في تجربة المستخدم!

في المقال القادم من سلسلة “البيانات والباك-إند في Flutter”، سنتحدث عن المعالجة في الخلفية (Background Processing) في Flutter: كيف تجعل تطبيقك يعمل حتى وهو مغلق أو في الخلفية. إذا أعجبك المقال، شاركه مع أصدقائك المطورين، وتابعنا للمزيد عن “التخزين المحلي في Flutter”!

By احمد علي

مطور تطبيقات هواتف ذكية باستخدام Flutter، وصانع محتوى تقني يكتب عن الذكاء الاصطناعي والبرمجة وتطورات التكنولوجيا الحديثة. أسعى لتبسيط الأفكار المعقدة ومشاركة خبرتي مع المهتمين بالمجال.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *