Kodokon kodokon.com

التخزين المحلي الدائم باستخدام shared_preferences

احفظ التفضيلات والبيانات الخفيفة بشكل دائم باستخدام shared_preferences مع معرفة حدوده الأمنية وحدود الحجم.

8 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

إن shared_preferences مخزن مفتاح/قيمة مدعوم بآليات أصلية (SharedPreferences على أندرويد، وUserDefaults على iOS). ومجاله المثالي: البيانات الصغيرة - السمة المختارة، والتعريف التمهيدي (onboarding) الذي شُوهد، وآخر تبويب نشط. وفيما وراء ذلك، غيِّر الأدوات: قاعدة بيانات sqflite أو drift للبيانات الكبيرة أو القابلة للاستعلام، وflutter_secure_storage لأيّ شيء حسّاس. ويُحمَّل الملف بأكمله في الذاكرة: فهو غير مُشفَّر ولا مبنيّ للنمو.

BASH
flutter pub add shared_preferences

إن getInstance() لاتزامني عند الاستدعاء الأول (قراءة الملف)، ثم يُرجِع نسخة مخزّنة مؤقتًا: فاستدعاءات get* اللاحقة تزامنية، تُقدَّم من الذاكرة. أما استدعاءات set* فتكتب على القرص بشكل لاتزامني. وعمليًا، لا تنثر سلاسل المفاتيح في أرجاء التطبيق كله: بل اجمعها في غلاف (wrapper) ذي أنواع، وقابل للحقن، وقابل للاختبار.

DART
import 'package:shared_preferences/shared_preferences.dart';

class SettingsStore {
  SettingsStore(this._prefs);

  static const _darkModeKey = 'settings.darkMode';

  final SharedPreferences _prefs;

  static Future<SettingsStore> load() async {
    final prefs = await SharedPreferences.getInstance();
    return SettingsStore(prefs);
  }

  bool get darkMode =>
      _prefs.getBool(_darkModeKey) ?? false;

  Future<void> setDarkMode(bool value) =>
      _prefs.setBool(_darkModeKey, value);
}
مفاتيح مُجمَّعة، وأنواع مضمونة، واعتمادية قابلة للحقن.

لا تخزّن الواجهة البرمجية سوى الأنواع الأولية: bool وint وdouble وString وList<String>. أما الكائن المُهيكَل، فسلسِله إلى JSON - وهنا تعود دالتاك toJson/fromJson من الدرس الأول لتنفعاك من جديد. ومقايضة مقبولة: كل حفظ يعيد كتابة القيمة بأكملها ولا يمكن إجراء أيّ استعلام. مثالي لمسودّة، وغير مناسب لسجلّ.

DART
import 'dart:convert';

import 'package:shared_preferences/shared_preferences.dart';

class Draft {
  const Draft({required this.title, required this.body});

  factory Draft.fromJson(Map<String, dynamic> json) =>
      Draft(
        title: json['title'] as String,
        body: json['body'] as String,
      );

  final String title;
  final String body;

  Map<String, dynamic> toJson() =>
      {'title': title, 'body': body};
}

Future<void> saveDraft(Draft draft) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('draft', jsonEncode(draft));
}

Future<Draft?> loadDraft() async {
  final prefs = await SharedPreferences.getInstance();
  final raw = prefs.getString('draft');
  if (raw == null) return null;
  return Draft.fromJson(
    jsonDecode(raw) as Map<String, dynamic>,
  );
}
كائن كامل مُخزَّن على شكل سلسلة JSON بسيطة.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. أيّ البيانات تنتمي إلى shared_preferences؟
    • رمز مصادقة المستخدم.
    • سجلّ المستخدم الكامل المؤلَّف من 50000 معاملة.
    • اختيار السمة الداكنة.
  2. بعد أول getInstance()، كيف تتصرّف قراءات get*؟
    • كل قراءة تعيد قراءة الملف من القرص.
    • إنها تزامنية: فالقيم تُقدَّم من مخزن مؤقت في الذاكرة.
    • تُرجِع كائنات Future يجب أن تنتظرها في كل مرة.
  3. كيف تحفظ كائن Draft مُهيكَلًا؟
    • سلسِله إلى JSON باستخدام jsonEncode وخزِّنه عبر setString.
    • استخدم prefs.setObject('draft', draft) المصمَّم لهذا الغرض.
    • قسِّمه حقلًا بحقل داخل setStringList.