كيف نشرت تطبيق Flutter الخاص بي على متجر Ubuntu Snap

كيف نشرت تطبيق Flutter الخاص بي على متجر Ubuntu Snap

دليل شامل لنشر تطبيق Flutter لسطح المكتب على متجر Ubuntu Snap — من أول بناء إلى الموافقة النهائية، مع كل العقبات التي واجهتها.

15 دقائق للقراءة
تم التحديث ١ يونيو ٢٠٢٦

إذا كنت قد بنيت تطبيق Flutter وتريد توزيعه على Linux، فإن متجر Ubuntu Snap هو أحد أسهل القنوات — فهو مدمج في Ubuntu ويصل إلى ملايين المستخدمين. لكن نشر تطبيق Flutter متكامل يتطلب أكثر من مجرد تشغيل snapcraft ورفع الملف.

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

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

إعداد بناء Linux

دعم Flutter لسطح مكتب Linux قد نضج بشكل كبير، لكن تطبيقك لا يزال بحاجة للتجميع والتشغيل أصلياً على نظام Linux بمعمارية x86_64. الخطوة الأولى هي التأكد من أن flutter build linux يكتمل بنجاح.

بالنسبة لتطبيق هدى، اعتمد التطبيق على عدة مكتبات محلية وإضافات منصة كانت تعمل بشكل مثالي على Android و iOS لكن لم يكن لها مكافئ على Linux. ظهر التحدي الأول فوراً.

التعامل مع التبعيات المحلية

حل مكتبة libmpv لتشغيل الصوت

يستخدم تطبيق هدى media_kit لتشغيل صوت القرآن. على Windows، حزمة media_kit_libs_windows_audio توفر كل شيء. على Linux، لا توجد هذه الراحة — تحتاج مكتبة libmpv من النظام.

الخطأ:

Exception: Cannot find libmpv at the usual places.

الحل كان مباشراً: إضافة media_kit_libs_linux إلى pubspec.yaml وتثبيت مكتبة النظام:

sudo apt install libmpv2

بيئة Snap تعطل حل المكتبات

بعد تثبيت libmpv، استمر الخطأ — لكن فقط عند التشغيل من VS Code. كان هذا محيراً حتى أدركت أن VS Code نفسه مثبت كحزمة Snap.

العمليات المعزولة في Snap لديها بيئة رابط ديناميكي معدلة. DynamicLibrary.open('libmpv.so.2') يعتمد على dlopen() لحل الأسماء عبر ذاكرة ldconfig التخزينية للنظام، لكن داخل بيئة Snap، هذا الحل يفشل بصمت.

الحل كان تجاوز حل الأسماء بالكامل:

lib/core/bootstrap/bootstrap.dart
String? _findLinuxLibmpv() {
  final paths = [
    '/usr/lib/x86_64-linux-gnu/libmpv.so.2',
    '/usr/lib/libmpv.so.2',
    '/usr/lib64/libmpv.so.2',
  ];
  for (final p in paths) {
    if (File(p).existsSync()) return p;
  }
  return null;
}

تعارض رموز librsvg

حتى بعد حل المسار، تسبب تحميل libmpv بانهيار التطبيق:

Failed to load dynamic library: undefined symbol: rsvg_handle_get_intrinsic_size_in_pixels

السبب الجذري كان دقيقاً: حزمة Snap الخاصة بـ VS Code تضم نسخة قديمة من librsvg (2.47.0)، ومُشغل Snap يحقنها في مسار البحث للرابط الديناميكي. عندما يحمل libmpv سلسلة تبعياته، يلتقط النسخة القديمة بدلاً من نسخة النظام 2.60.0.

الحل طُبق على مستوى C++ في مُشغل GTK:

linux/runner/main.cc
#include <dlfcn.h>
 
int main(int argc, char** argv) {
  // تحميل librsvg من النظام قبل تهيئة Flutter
  dlopen("librsvg-2.so.2", RTLD_NOW | RTLD_GLOBAL);
  // ... تهيئة Flutter
}

توافق المنصة

بعد أن عمل أنبوب الصوت المحلي، ظهرت المجموعة التالية من المشاكل من ميزات خاصة بالمنصة غير موجودة على Linux.

الإشعارات المحلية

إضافة flutter_local_notifications أصدرت خطأ لأن LinuxInitializationSettings كان مفقوداً:

Invalid argument(s): Linux settings must be set when targeting Linux platform.

إضافة سريعة لإعدادات التهيئة حلت المشكلة:

lib/core/services/notification_services.dart
final settings = InitializationSettings(
  android: androidSettings,
  iOS: iosSettings,
  macOS: macOSSettings,
  linux: LinuxInitializationSettings(
    defaultActionName: 'Open notification',
  ),
);

الانتقال من Firebase إلى Supabase

كان هذا أكبر تغيير. Firebase لا يدعم Linux — حزم firebase_core و cloud_firestore و firebase_storage ليس لديها تنفيذ لـ Linux. البناء إما يفشل أو ينهار عند بدء التشغيل.

الحل كان ترحيل كامل للخادم الخلفي إلى Supabase، الذي يعمل على جميع المنصات بما فيها Linux:

  • كتابة مجموعات Firestore ← إدراجات جداول Supabase
  • رفع Firebase Storage ← رفع حاويات Supabase Storage
  • Firebase.initializeApp()Supabase.initialize()

كان هذا جهداً كبيراً لكنه أسفر عن بنية أنظف وأكثر قابلية للنقل. كأثر جانبي، كان يجب إعادة كتابة منطق تنزيل خطوط القرآن لاستخدام طلبات Dio HTTP مقابل عناوين URL العامة لتخزين Supabase.

التعامل الأنيق مع واجهات Linux المفقودة

إضافة geolocator لا تنفذ واجهات فحص الأذونات أو فتح الإعدادات على Linux. استدعاؤها يرمي MissingPluginException.

الحل استخدم حراسات المنصة:

if (PlatformUtils.isLinux) {
  // تخطي فحوصات الأذونات
  // إخفاء زر "فتح الإعدادات"
  // عرض "البحث يدوياً" بدلاً من ذلك
}

مربع حوار ManualLocationSearchDialog المدعوم بخدمة الترميز الجغرافي Nominatim يتيح لمستخدمي Linux تحديد موقعهم بالبحث عن اسم مدينة.

بالمثل، تبويب الحفظ (الذي يعتمد على الوصول للميكروفون) تم استبعاده شرطياً على Linux.

التغليف مع Snapcraft

مع تشغيل التطبيق على Linux، كانت المرحلة التالية تغليفه كحزمة Snap للتوزيع.

تعيين أيقونة النافذة

غلاف Linux في Flutter لا يعين أيقونة نافذة تلقائياً. التطبيق يُشغل بأيقونة GTK العامة.

الحل كان إضافة gtk_window_set_icon_from_file() في مُشغل GTK:

linux/runner/my_application.cc
if (g_file_test("assets/dev/huda_center.png", G_FILE_TEST_EXISTS)) {
  gtk_window_set_icon_from_file(window, "assets/dev/huda_center.png", NULL);
} else {
  gtk_window_set_icon_from_file(window,
    "data/flutter_assets/assets/dev/huda_center.png", NULL);
}

تضمين المكتبات داخل حزمة Snap

داخل صندوق العزل الصارم، لا تستطيع حزمة Snap الوصول إلى /usr/lib في النظام المضيف. يجب تضمين مكتبة libmpv داخل حزمة Snap نفسها.

snap/snapcraft.yaml
parts:
  huda:
    plugin: nil
    stage-packages:
      - libmpv2

إصلاح الروابط المفقودة لـ BLAS و LAPACK

بعد تضمين libmpv، ظهر انهيار جديد:

libblas.so.3: cannot open shared object file: No such file or directory

على Debian/Ubuntu، حزم libblas3 و liblapack3 تثبت ملفات .so في مجلدات فرعية وتنشئ روابط رمزية عبر update-alternatives. لكن Snapcraft يضمن الحزم حرفياً دون تشغيل update-alternatives، فلا تُنشأ الروابط الرمزية.

الحل كان سكريبت override-prime في snapcraft.yaml:

snap/snapcraft.yaml
override-prime: |
  craftctl default
  cd usr/lib/x86_64-linux-gnu
  ln -sf blas/libblas.so.3 libblas.so.3
  ln -sf lapack/liblapack.so.3 liblapack.so.3

التقديم للمتجر والمراجعة

مع بناء وتشغيل حزمة Snap بشكل صحيح، رفعتها إلى متجر Snap. قبل الأنبوب الآلي الرفع لكنه وضع علامة فورية لـ مراجعة بشرية يدوية:

Issues while processing snap:
- human review required due to 'deny-connection' constraint

ملف snapcraft.yaml يعلن فتحات لـ D-Bus (يطالب بمساحة الاسم com.aw.huda) و MPRIS (التحكم بالوسائط العالمي). لأن هذه الواجهات تطلب صلاحيات على مستوى النظام، يضع الأنبوب الآلي علامة عليها بقيد أمني.

الحل كان تقديم طلب مراجعة يدوية على منتدى Snapcraft تحت تصنيف store-requests > privileged-interfaces. تضمن الطلب:

  • وصف التطبيق والغرض منه
  • حالة المستودع المصدر
  • مبرر تقني لماذا D-Bus و MPRIS مطلوبان

بعد المراجعة والموافقة، نُشرت الحزمة وهي الآن متاحة على snapcraft.io/huda.

النقاط الرئيسية

  1. اختبر خارج صندوق عزل بيئة التطوير. إذا كانت بيئة التطوير حزمة Snap (مثل VS Code)، حل المكتبات يتصرف بشكل مختلف. اختبر دائماً من طرفية غير معزولة.

  2. راجع كل إضافة لدعم Linux. معظم إضافات Flutter تركز على الجوال. تحقق من مصفوفة دعم المنصات لكل تبعية.

  3. عزل Snap يعني تغليفاً مكتفياً ذاتياً. لا تفترض أن مكتبات النظام متاحة. ضمّن كل ما يحتاجه تطبيقك.

  4. update-alternatives لا يعمل في حزم Snap. إذا اعتمدت تبعياتك على روابط رمزية من سكربتات حزم Debian، أنشئها يدوياً في override-prime.

  5. الواجهات المميزة تتطلب مراجعة بشرية. إذا استخدم تطبيقك D-Bus أو MPRIS أو واجهات أخرى على مستوى النظام، خصص وقتاً لعملية المراجعة.

  6. قابلية نقل الخادم الخلفي مهمة. عدم دعم Firebase لـ Linux فرض ترحيلاً كاملاً. إذا خططت للنشر على Linux، اختر خادماً خلفياً يعمل في كل مكان من البداية.


الأسئلة الشائعة

كيف أنشر تطبيق Flutter على متجر Ubuntu Snap؟

ابنِ تطبيق Flutter لسطح مكتب Linux باستخدام flutter build linux، أنشئ snapcraft.yaml يغلف مخرجات البناء، شغّل snapcraft لإنتاج ملف .snap، ثم ارفعه بـ snapcraft upload. إذا استخدم تطبيقك واجهات مميزة، قد تحتاج لطلب مراجعة يدوية على منتدى Snapcraft.

لماذا لا يجد تطبيق Flutter مكتبة libmpv على Linux؟

يستخدم Flutter دالة DynamicLibrary.open() التي تعتمد على dlopen() لحل الأسماء. إذا كنت تعمل داخل بيئة Snap (مثل VS Code المثبت كحزمة Snap)، تتغير بيئة الرابط الديناميكي. استخدم مسارات مطلقة للمكتبة بدلاً من الاعتماد على حل الأسماء.

هل يعمل Firebase مع Flutter على Linux؟

لا. حزم Firebase FlutterFire تدعم فقط Android و iOS و macOS و Web. لتطبيقات Linux لسطح المكتب، استخدم بدائل مثل Supabase التي توفر وظائف مماثلة مع دعم كامل عبر المنصات.

كيف أصلح أخطاء "deny-connection" عند الرفع لمتجر Snap؟

يحدث هذا عندما تعلن حزمتك عن واجهات تتطلب صلاحيات على مستوى النظام (مثل D-Bus أو MPRIS). قدم طلب مراجعة يدوية على منتدى Snapcraft مع مبرر تقني لماذا كل واجهة مطلوبة.

ما المكتبات التي أحتاج تضمينها في حزمة Flutter Snap؟

أي مكتبة محلية يعتمد عليها تطبيقك وغير مقدمة من إضافة GNOME. من الأمثلة الشائعة libmpv2 لتشغيل الوسائط. تحقق من سجلات تشغيل Snap لأخطاء "cannot open shared object file" لتحديد المكتبات المفقودة.

إعداد بناء Linux