Null Safety وعلامة التعجب !

🔹 Null Safety وعلامة التعجب ! في Dart 🚀

في Dart Null Safety، يتم استخدام علامة التعجب ! (وتسمى Null Assertion Operator) لإخبار المترجم أن المتغير لن يكون null، وبالتالي، يمكن التعامل معه على أنه غير قابل لأن يكون null.

⚠️ يجب الحذر عند استخدامها! إذا كان المتغير null بالفعل، فسيؤدي ذلك إلى خطأ وقت التشغيل (Null Pointer Exception).


🔹 استخدام ! مع المتغيرات Nullable

عند وجود متغير nullable (int?، String?، إلخ)، إذا كنت متأكدًا 100% من أنه لن يكون null عند استخدامه، يمكنك استخدام ! لتحويله إلى non-nullable.

📌 مثال: بدون !

dartنسختحريرvoid main() {
  String? name;
  print(name.length); // ❌ خطأ: name يمكن أن يكون null
}

🔹 التفسير:

  • لا يمكننا استدعاء length مباشرة لأن name من النوع String?، والذي يمكن أن يكون null.

📌 الحل باستخدام !

dartنسختحريرvoid main() {
  String? name = "Dart";
  print(name!.length); // ✅ Output: 4
}

🔹 التفسير:

  • name! تخبر Dart بأن name لن يكون null، لذا يمكن استدعاء length بأمان.

🔹 مشكلة ! إذا كان المتغير null

⚠️ إذا استخدمت ! على متغير يحتوي على null، فسيؤدي ذلك إلى خطأ وقت التشغيل.

dartنسختحريرvoid main() {
  String? name;
  print(name!.length); // ❌ خطأ: Unhandled exception
}

التفسير:

  • name! تقول لـ Dart أن name ليس null، ولكن في الحقيقة هو null، مما يسبب خطأ تنفيذ (Unhandled Exception).

الحل: تأكد من أن المتغير ليس null قبل استخدام !:

dartنسختحريرvoid main() {
  String? name;
  if (name != null) {
    print(name.length); // ✅ يعمل بدون مشاكل
  } else {
    print("Name is null");
  }
}

🔹 استخدام ! مع List و Map

📌 مثال مع List

dartنسختحريرvoid main() {
  List<int?> numbers = [10, null, 30];

  print(numbers[0]! + numbers[2]!); // ✅ Output: 40
}

🔹 التفسير:

  • استخدمنا ! للتأكد من أن القيم داخل القائمة ليست null قبل إجراء العمليات الحسابية.

⚠️ لكن لو حاولنا استخدام ! على null، سيحدث خطأ:

dartنسختحريرvoid main() {
  List<int?> numbers = [null, 20];

  print(numbers[0]!); // ❌ خطأ: Unhandled Exception
}

الحل: استخدم ?. أو ??:

dartنسختحريرvoid main() {
  List<int?> numbers = [null, 20];

  print(numbers[0] ?? "Value is null"); // ✅ Output: Value is null
}

📌 مثال مع Map

dartنسختحريرvoid main() {
  Map<String, String?> user = {
    "name": "Ali",
    "email": null,
  };

  print(user["name"]!.toUpperCase()); // ✅ Output: ALI
  print(user["email"]!.toUpperCase()); // ❌ خطأ: لأن القيمة `null`
}

الحل: استخدم ?? بدلًا من !:

dartنسختحريرvoid main() {
  Map<String, String?> user = {
    "name": "Ali",
    "email": null,
  };

  print(user["email"] ?? "No email provided"); // ✅ Output: No email provided
}

🔹 ! مع دوال تُرجع nullable

إذا كانت دالة تُرجع null أو قيمة (nullable type)، يمكنك استخدام ! إذا كنت متأكدًا من أنها لن تُرجع null.

📌 مثال مع nullable function

dartنسختحريرString? getUsername(bool isValid) {
  return isValid ? "DartUser" : null;
}

void main() {
  String username = getUsername(true)!;
  print(username); // ✅ Output: DartUser
}

🔹 لكن إذا أرجعت null، سيحدث خطأ:

dartنسختحريرvoid main() {
  String username = getUsername(false)!; // ❌ خطأ: لأنه `null`
  print(username);
}

الحل: استخدم ?? لتوفير قيمة بديلة:

dartنسختحريرvoid main() {
  String username = getUsername(false) ?? "Guest";
  print(username); // ✅ Output: Guest
}

🔹 متى تستخدم ! ومتى تتجنبها؟

الحالةهل يجب استخدام !؟البديل
إذا كنت متأكدًا 100% أن المتغير ليس null✅ نعملا يوجد بديل
إذا كنت لست متأكدًا مما إذا كان المتغير null❌ لااستخدم ?. أو ??
إذا كنت تتعامل مع دالة تُرجع nullable❌ لااستخدم ?? لتوفير قيمة افتراضية

🎯 خلاصة ! في Null Safety

! يستخدم لإجبار المتغيرات nullable على أن تكون non-nullable، ولكن يجب التأكد من أنها ليست null فعليًا.
إذا كان هناك احتمال أن يكون null، استخدم ?. أو ?? لتجنب الأخطاء.
لا تفرط في استخدام ! لأنه قد يؤدي إلى Null Pointer Exception.