الخرائط والموقع وميزات الجهاز

استرجاع معلومات الجهاز باستخدام device_info_plus

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

استرجاع معلومات الجهاز باستخدام device_info_plus

كثير من تطبيقات Flutter في الإنتاج تحتاج إلى معرفة الجهاز الذي تعمل عليه — لتخصيص تجربة المستخدم، أو الإبلاغ عن الأعطال بدقة، أو تطبيق ترخيص خاص بالمنصة، أو تسجيل البيانات التشخيصية. توفر حزمة device_info_plus واجهة برمجية نظيفة ومكتوبة بالأنواع تعرض البيانات الوصفية الخاصة بكل منصة مثل طراز الجهاز وإصدار نظام التشغيل واسم النظام والمعرّفات الفريدة على كل من Android وiOS (وكذلك الويب وWindows وmacOS وLinux).

ملاحظة: device_info_plus هو الخليفة الذي تحتفظ به المجتمع للإضافة الأصلية device_info. استخدم دائماً device_info_plus في المشاريع الجديدة — الحزمة القديمة متروكة وغير مصانة.

إضافة التبعية

أضف device_info_plus إلى ملف pubspec.yaml ونفِّذ flutter pub get:

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  device_info_plus: ^10.1.0   # استخدم أحدث إصدار مستقر

لا تحتاج إلى أذونات Android إضافية أو إدخالات في Info.plist لنظام iOS للحقول الأساسية التي يغطيها هذا الدرس.

واجهة برمجة التطبيقات الأساسية — DeviceInfoPlugin

نقطة الدخول للحزمة هي DeviceInfoPlugin. تنشئ نسخة واحدة منه وتستدعي دالة الجلب المناسبة للمنصة، التي تُعيد كائن معلومات بأنواع محددة:

  • AndroidandroidInfo يُعيد كائن AndroidDeviceInfo
  • iOSiosInfo يُعيد كائن IosDeviceInfo
  • الويبwebBrowserInfo يُعيد كائن WebBrowserInfo
  • Windows / macOS / Linux — كائنات مكتوبة بالأنواع متاحة أيضاً

بما أن كل دالة جلب هي استدعاء غير متزامن لقناة المنصة، يجب عليك استخدام await للنتيجة داخل طريقة async أو تجاوز initState.

جلب معلومات الجهاز (آمن عبر المنصات)

import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart' show defaultTargetPlatform, TargetPlatform;

Future<Map<String, String>> fetchDeviceInfo() async {
  final plugin = DeviceInfoPlugin();
  final info = <String, String>{};

  if (defaultTargetPlatform == TargetPlatform.android) {
    final android = await plugin.androidInfo;
    info['المنصة']       = 'Android';
    info['الطراز']       = android.model;
    info['العلامة التجارية'] = android.brand;
    info['إصدار SDK']    = android.version.sdkInt.toString();
    info['إصدار النظام'] = android.version.release;
    info['معرّف الجهاز'] = android.id;
    info['جهاز حقيقي']  = android.isPhysicalDevice.toString();
  } else if (defaultTargetPlatform == TargetPlatform.iOS) {
    final ios = await plugin.iosInfo;
    info['المنصة']         = 'iOS';
    info['الطراز']         = ios.model;
    info['اسم الجهاز']     = ios.name;
    info['اسم النظام']     = ios.systemName;
    info['إصدار النظام']   = ios.systemVersion;
    info['معرّف المورّد']  = ios.identifierForVendor ?? 'غير متاح';
    info['جهاز حقيقي']    = ios.isPhysicalDevice.toString();
  }

  return info;
}
نصيحة: استخدم defaultTargetPlatform من package:flutter/foundation.dart بدلاً من Platform.isAndroid من dart:io. الأول يعمل على جميع المنصات (بما فيها الويب)؛ الثاني يُطلق استثناءً على الويب.

مرجع الحقول الأساسية

فهم معنى كل حقل يساعدك على اختيار الحقل المناسب لحالة الاستخدام الخاصة بك:

  • Android — model: اسم الطراز التسويقي (مثل Pixel 8 Pro)
  • Android — brand: العلامة التجارية الموجهة للمستهلك (مثل google)
  • Android — version.sdkInt: مستوى Android SDK (مثل 34 لنظام Android 14)
  • Android — version.release: سلسلة إصدار نظام التشغيل القابلة للقراءة (مثل 14)
  • Android — id: سلسلة بصمة البناء؛ ليست معرّف جهاز فريداً ثابتاً
  • iOS — identifierForVendor: UUID فريد لكل مورّد تطبيق لكل جهاز؛ يُعاد تعيينه عند إعادة التثبيت إذا لم تكن هناك تطبيقات أخرى من نفس المورّد
  • iOS — utsname.machine: معرّف جهاز العتاد (مثل iPhone16,2)
تحذير: لا يوجد معرّف جهاز فريد ثابت حقيقي متاح بدون أذونات خاصة على Android أو iOS الحديثين. identifierForVendor قد يتغير؛ ANDROID_ID قد يتغير بعد إعادة ضبط المصنع. للتحليلات، يُفضّل استخدام UUID مُنشأ من الخادم ومخزّن في تخزين آمن بدلاً من الاعتماد على معرّفات العتاد.

بناء شاشة عرض على طراز الإعدادات

من الأنماط الشائعة عرض البيانات الوصفية للجهاز على شاشة تشخيصية أو شاشة "حول هذا الجهاز". يقوم الودجت بتحميل البيانات بشكل غير متزامن في initState ويخزّنها في Map يقود قائمة ListView من صفوف ListTile.

DeviceInfoScreen — مثال كامل

import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

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

  @override
  State<DeviceInfoScreen> createState() => _DeviceInfoScreenState();
}

class _DeviceInfoScreenState extends State<DeviceInfoScreen> {
  Map<String, String> _deviceData = {};
  bool _isLoading = true;

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

  Future<void> _loadDeviceInfo() async {
    final plugin = DeviceInfoPlugin();
    final data = <String, String>{};

    try {
      if (defaultTargetPlatform == TargetPlatform.android) {
        final android = await plugin.androidInfo;
        data['الطراز']           = android.model;
        data['العلامة التجارية'] = android.brand;
        data['الشركة المصنّعة'] = android.manufacturer;
        data['إصدار Android SDK'] = android.version.sdkInt.toString();
        data['إصدار النظام']      = android.version.release;
        data['تصحيح الأمان']     = android.version.securityPatch ?? 'غير متاح';
        data['جهاز حقيقي']       = android.isPhysicalDevice ? 'نعم' : 'لا';
      } else if (defaultTargetPlatform == TargetPlatform.iOS) {
        final ios = await plugin.iosInfo;
        data['اسم الجهاز']    = ios.name;
        data['الطراز']        = ios.model;
        data['الجهاز']        = ios.utsname.machine;
        data['اسم النظام']    = ios.systemName;
        data['إصدار النظام']  = ios.systemVersion;
        data['معرّف المورّد'] = ios.identifierForVendor ?? 'غير متاح';
        data['جهاز حقيقي']   = ios.isPhysicalDevice ? 'نعم' : 'لا';
      }
    } catch (e) {
      data['خطأ'] = e.toString();
    }

    if (mounted) {
      setState(() {
        _deviceData = data;
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('معلومات الجهاز')),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : ListView(
              children: _deviceData.entries.map((entry) {
                return ListTile(
                  title: Text(
                    entry.key,
                    style: const TextStyle(
                      fontWeight: FontWeight.w600,
                      fontSize: 13,
                      color: Colors.grey,
                    ),
                  ),
                  subtitle: Text(
                    entry.value,
                    style: const TextStyle(fontSize: 16),
                  ),
                  dense: true,
                );
              }).toList(),
            ),
    );
  }
}

أفضل الممارسات

  • استدعِ مرة واحدة واحتفظ بالنتيجة — استدعاءات قناة المنصة لها تكلفة أداء؛ خزّن البيانات المُعادة في متغير أو مزوّد إدارة الحالة بدلاً من استدعاء الإضافة بشكل متكرر.
  • احمِ باستخدام mounted — تحقق دائماً من if (mounted) قبل استدعاء setState داخل الطرق غير المتزامنة لتجنب ضبط الحالة على ودجت تم التخلص منه.
  • احتوِ الاستثناءات باستخدام try/catch — على المنصات غير المدعومة أو أثناء الاختبار، قد تُطلق الإضافة استثناءات؛ المعالجة اللطيفة للأخطاء تُبقي تطبيقك مستقراً.
  • لا تُشفِّر مستويات SDK بشكل ثابت — قارن android.version.sdkInt مع ثوابت معروفة أو قيم صحيحة موثّقة جيداً بدلاً من الأرقام السحرية.
النقطة الرئيسية: توفر device_info_plus واجهة برمجية غير متزامنة بسيطة لقراءة البيانات الوصفية للجهاز الخاصة بالمنصة والمكتوبة بالأنواع. أنشئ نسخة واحدة من DeviceInfoPlugin، وانتظر دالة الجلب الصحيحة للمنصة، وخزّن النتيجة. احمِ تحديثات الحالة غير المتزامنة باستخدام mounted، وفضّل UUID مُنشأ من الخادم على معرّفات العتاد لأغراض التتبع.

اكتمل الدرس!

تهانينا! لقد أكملت جميع الدروس في هذا البرنامج التعليمي.