التخزين المحلي للبيانات

SharedPreferences: تخزين البيانات البسيطة بنظام المفتاح والقيمة

15 دقيقة الدرس 2 من 12

SharedPreferences: تخزين البيانات البسيطة بنظام المفتاح والقيمة

تحتاج معظم التطبيقات إلى الاحتفاظ ببيانات صغيرة بين الجلسات: ما إذا كان المستخدم قد أكمل عملية التأهيل، والسمة المختارة، واسم مستخدم محفوظ، أو تبديل تفضيلات. SharedPreferences هو الحل المعتمد في Flutter لهذه الحالة. يستند إلى NSUserDefaults على iOS وSharedPreferences على Android، ويوفر مخزن مفاتيح وقيم بسيطاً وغير متزامن للأنواع الأولية.

إضافة الحزمة

حزمة shared_preferences غير مدمجة مع Flutter — يجب إضافتها إلى مشروعك. شغّل الأمر التالي في الطرفية من جذر المشروع:

flutter pub add shared_preferences

هذا يحدّث pubspec.yaml ويحمّل الحزمة. ثم قم باستيرادها في أي مكان تحتاجها:

import 'package:shared_preferences/shared_preferences.dart';

الحصول على نسخة

تحصل على نسخة SharedPreferences باستدعاء المصنع الثابت غير المتزامن SharedPreferences.getInstance(). لأنه يُعيد Future، يجب أن تنتظره (await) داخل دالة async. من الأنماط الشائعة تحميل التفضيلات داخل initState في StatefulWidget:

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

  @override
  State<SettingsPage> createState() => _SettingsPageState();
}

class _SettingsPageState extends State<SettingsPage> {
  bool _darkMode = false;
  String _username = '';

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

  Future<void> _loadPreferences() async {
    final prefs = await SharedPreferences.getInstance();
    setState(() {
      _darkMode = prefs.getBool('dark_mode') ?? false;
      _username = prefs.getString('username') ?? '';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('الإعدادات')),
      body: Column(
        children: [
          Text('مرحباً، $_username'),
          Switch(
            value: _darkMode,
            onChanged: (value) async {
              final prefs = await SharedPreferences.getInstance();
              await prefs.setBool('dark_mode', value);
              setState(() => _darkMode = value);
            },
          ),
        ],
      ),
    );
  }
}
ملاحظة: استدعاء SharedPreferences.getInstance() غير مكلف بعد المرة الأولى لأن المنصة تخزّن النسخة مؤقتاً. ومع ذلك لا يزال غير متزامن، لذا دائماً انتظره بـ await. لا تعطّل خيط واجهة المستخدم باستدعائه بشكل متزامن.

قراءة وكتابة القيم الأولية

يدعم SharedPreferences خمسة أنواع أولية. لكل نوع زوج مخصص من دوال القراءة والكتابة:

  • StringgetString(key) / setString(key, value)
  • intgetInt(key) / setInt(key, value)
  • doublegetDouble(key) / setDouble(key, value)
  • boolgetBool(key) / setBool(key, value)
  • List<String>getStringList(key) / setStringList(key, value)

تُعيد جميع دوال القراءة null إذا لم يكن المفتاح موجوداً، لذا دائماً طبّق عامل دمج الفراغ (??) لتوفير قيمة افتراضية. تُعيد جميع دوال الكتابة Future<bool> يشير إلى النجاح — انتظرها بـ await للأمان:

Future<void> saveUserData() async {
  final prefs = await SharedPreferences.getInstance();

  // الكتابة
  await prefs.setString('username', 'إدريس');
  await prefs.setInt('launch_count', 42);
  await prefs.setDouble('font_size', 16.5);
  await prefs.setBool('notifications_enabled', true);
  await prefs.setStringList('recent_searches', ['flutter', 'dart', 'firebase']);

  // القراءة مع قيم افتراضية آمنة من الفراغ
  final username = prefs.getString('username') ?? 'زائر';
  final launches = prefs.getInt('launch_count') ?? 0;
  final fontSize = prefs.getDouble('font_size') ?? 14.0;
  final notifs = prefs.getBool('notifications_enabled') ?? true;
  final searches = prefs.getStringList('recent_searches') ?? [];

  print('$username فتح التطبيق $launches مرة');
}

حذف المفاتيح ومسح كل البيانات

يمكنك حذف مفتاح محدد أو مسح مخزن التفضيلات بالكامل. استخدم remove(key) لحذف إدخال واحد وclear() لمسح كل شيء — عادةً عند تسجيل الخروج:

Future<void> handleLogout() async {
  final prefs = await SharedPreferences.getInstance();

  // حذف مفتاح واحد
  await prefs.remove('auth_token');

  // أو مسح كل شيء (استخدم بحذر)
  await prefs.clear();
}

// التحقق من وجود مفتاح قبل القراءة
Future<void> checkOnboarding() async {
  final prefs = await SharedPreferences.getInstance();
  final hasSeenOnboarding = prefs.containsKey('onboarding_complete');
  if (!hasSeenOnboarding) {
    // عرض تدفق التأهيل
  }
}

أفضل ممارسات أمان الفراغ (Null-Safety)

لأن كل دالة قراءة يمكن أن تُعيد null، دائماً احمِ القراءات بقيمة افتراضية. للقيم الافتراضية المعقدة أو عندما تُقرأ نفس التفضيلات في أماكن متعددة، غلّف الوصول إلى التفضيلات في فئة خدمة مخصصة بدلاً من نثر عوامل ?? عبر كود واجهة المستخدم:

class PreferencesService {
  static const _kDarkMode = 'dark_mode';
  static const _kUsername = 'username';
  static const _kLaunchCount = 'launch_count';

  Future<SharedPreferences> get _prefs => SharedPreferences.getInstance();

  Future<bool> getDarkMode() async =>
      (await _prefs).getBool(_kDarkMode) ?? false;

  Future<void> setDarkMode(bool value) async =>
      (await _prefs).setBool(_kDarkMode, value);

  Future<String> getUsername() async =>
      (await _prefs).getString(_kUsername) ?? 'زائر';

  Future<void> setUsername(String name) async =>
      (await _prefs).setString(_kUsername, name);

  Future<int> incrementLaunchCount() async {
    final p = await _prefs;
    final count = (p.getInt(_kLaunchCount) ?? 0) + 1;
    await p.setInt(_kLaunchCount, count);
    return count;
  }
}
نصيحة: خزّن جميع مفاتيح التفضيلات كسلاسل static const في مكان واحد. هذا يمنع الأخطاء الناجمة عن الأخطاء المطبعية حيث تكتب بمفتاح واحد وتقرأ بمفتاح مختلف قليلاً، مما يعيد null بصمت في كل مرة.
تحذير: لا يتم تشفير SharedPreferences على أي منصة. لا تخزن أبداً بيانات حساسة مثل كلمات المرور أو رموز API أو المعلومات الشخصية التعريفية (PII) في SharedPreferences. استخدم حزمة flutter_secure_storage للأسرار التي تتطلب التشفير أثناء التخزين.

ملخص

SharedPreferences هو الحل الأمثل لتخزين التفضيلات الصغيرة غير الحساسة وحالة التطبيق عبر الجلسات. النقاط الرئيسية التي يجب تذكرها:

  • أضف الحزمة بـ flutter pub add shared_preferences واستوردها.
  • احصل على النسخة بـ await SharedPreferences.getInstance().
  • استخدم دوال القراءة/الكتابة المخصصة لكل نوع: String وint وdouble وbool وList<String>.
  • تُعيد جميع دوال القراءة null عند غياب المفتاح — دائماً وفّر قيمة افتراضية بـ ??.
  • غلّف الوصول إلى التفضيلات في فئة خدمة لكود أنظف وآمن من الفراغ.
  • لا تخزن الأسرار أبداً؛ استخدم flutter_secure_storage للبيانات الحساسة.