Flutter: نشر المشروع — إطلاق ShopApp والإصدار

الإطلاق ليس النهاية — إنه نقطة البداية للتسليم المستمر.

📋 المتطلبات السابقة: يجب أن تكون قد أتقنت ما يلي أولًا

1. ما ستتعلمه


2. قصة انهيار في يوم الإطلاق

(1) المشكلة: يعمل في التطوير، معدل أعطال 5% في الإنتاج

قضى بوب 4 أشهر في تطوير ShopApp. في يوم الإطلاق، كان معدل الأعطال 5%. الأسباب: تشويش R8 أندرويد أزال أسماء فئات تسلسل Firestore (فشل إلغاء التسلسل)؛ iOS رُفض لوصف إذن الكاميرا المفقود؛ صفحة ويب رئيسية استغرقت 8 ثوانٍ للتحميل (بدون تقسيم كود). الإصلاحات الطارئة + إعادة المراجعة استغرقت أسبوعين إضافيين.

(2) حل تحسين البناء + قائمة فحص ما قبل الإطلاق

قبل الإطلاق، نفذ تحسينات البناء (قواعد التشويش، تقسيم الكود، ضغط الأصول) + قائمة فحص الإصدار (الأذونات، التوقيع، قواعد ProGuard، مراجعة TestFlight المسبقة) لضمان جودة الإطلاق الأول.

(3) النتيجة: معدل أعطال الإطلاق الأول < 0.1%

بعد استخدام القائمة في مشروعه الثاني، حقق بوب معدل أعطال إطلاق أول 0.08%، اجتاز المراجعة من التقديم الأول، ووقت أول محتوى مرسوم للويب 1.5 ثانية.


3. تحسين البناء

(1) ▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: قواعد ProGuard / R8 لأندرويد

PROGUARD
# flutter_proguard_rules.pro

# Keep Firestore model classes (used by json_serializable)
-keepclassmembers class com.shopapp..model. {
  <fields>;
}

# Keep freezed generated classes
-keep class com.shopapp...freezed.dart { *; }

# Keep Firebase classes
-keep class io.flutter.plugins.firebase.** { *; }
-keep class com.google.firebase.** { *; }

# Keep Stripe SDK
-keep class com.stripe.android.** { *; }

# Keep Google Sign-In
-keep class com.google.android.gms.auth.** { *; }

# Remove logging in release
-assumenosideeffects class android.util.Log {
  public static *** d(...);
  public static *** v(...);
  public static *** i(...);
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(2) ▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: إعدادات بناء Flutter

YAML
# pubspec.yaml build optimization
flutter:
  uses-material-design: true
  # Exclude unused locales
  generate: true

# android/app/build.gradle
android {
    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
    }
    // Split per ABI to reduce APK size
    splits {
        abi {
            enable true
            reset()
            include 'armeabi-v7a', 'arm64-v8a', 'x86_64'
            universalApk false
        }
    }
}

(3) مقارنة تحسين البناء

التحسين قبل بعد التوفير
حجم APK (arm64) 28 MB 12 MB 57%
أول محتوى مرسوم للويب 8.2 s 1.5 s 82%
حجم كود Dart 6.4 MB 2.1 MB 67%
وقت البدء (بارد) 3.5 s 1.8 s 49%

4. نشر أندرويد

(1) ▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: إعدادات مفتاح التوقيع

BASH
# Generate signing keystore
keytool -genkey -v -keystore shopapp-release.jks -keyalg RSA -keysize 2048 -validity 10000 -alias shopapp

# Create key.properties
cat > android/key.properties << 'EOF'
storePassword=STORE_PASSWORD
keyPassword=KEY_PASSWORD
keyAlias=shopapp
storeFile=../shopapp-release.jks
EOF
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(2) ▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: تكامل توقيع build.gradle

GROOVY
// android/app/build.gradle
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {
    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(3) ▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: بناء App Bundle والرفع

BASH
# Build App Bundle (recommended over APK for Play Store)
flutter build appbundle --release --obfuscate --split-debug-info=build/debug-info

# Upload to Google Play Console
# Option 1: Manual via Play Console UI
# Option 2: Fastlane
cd android && fastlane supply --aab ../build/app/outputs/bundle/release/app-release.aab --track internal
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(4) قائمة فحص إصدار أندرويد

# عنصر الفحص الحالة
1 مفتاح التوقيع مُولد ومُنسوخ احتياطيًا
2 key.properties ليس في التحكم بالإصدارات
3 قواعد ProGuard تغطي جميع حزم SDK الطرف الثالث
4 versionCode / versionName مُحدَّث
5 إقرانات الأذونات مُقلّمة
6 App Bundle (وليس APK) للرفع
7 لقطات شاشة المتجر + الوصف مُعدّان
8 رابط سياسة الخصوصية مُهيأ

5. نشر iOS

(1) ▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: إعدادات أذونات Info.plist

XML
<!-- ios/Runner/Info.plist -->
`<key>`NSCameraUsageDescription</key>
`<string>`ShopApp needs camera access to scan product barcodes.</string>
`<key>`NSPhotoLibraryUsageDescription</key>
`<string>`ShopApp needs photo access to upload product reviews.</string>
`<key>`NSLocationWhenInUseUsageDescription</key>
`<string>`ShopApp uses location for local delivery estimates.</string>
`<key>`CFBundleURLTypes</key>
`<array>`
  `<dict>`
    `<key>`CFBundleURLSchemes</key>
    `<array>`
      `<string>`com.googleusercontent.apps.YOUR_CLIENT_ID</string>
    </array>
  `</dict>`
</array>

(2) ▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: إصدار iOS بـ Fastlane

RUBY
# ios/fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc "Build and upload to TestFlight"
  lane :beta do
    build_app(
      workspace: "Runner.xcworkspace",
      scheme: "Runner",
      export_method: "app-store",
      include_bitcode: false,
      export_options: {
        compileBitcode: false,
        provisioningProfiles: {
          "com.shopapp.app" => "match AppStore com.shopapp.app"
        }
      }
    )
    upload_to_testflight(
      skip_waiting_for_build_processing: true
    )
  end

  desc "Submit to App Store review"
  lane :release do
    beta
    deliver(
      submit_for_review: true,
      automatic_release: true,
      force: true,
      metadata_path: "./metadata"
    )
  end
end
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(3) أسباب رفض مراجعة iOS الشائعة

سبب الرفض الحل
وصف الأذونات مفقود أضف NS*UsageDescription في Info.plist
الدفع لا يستخدم IAP (سلع افتراضية) استخدم StoreKit للسلع الافتراضية، Stripe للسلع المادية
تسجيل الدخول يجبر ربط حساب اجتماعي وفر خيار بريد إلكتروني/كلمة مرور
تطبيق غلاف WebView تأكد أن الوظائف الأصلية > 50%
بيانات وصفية غير مكتملة املأ تصنيف العمر، رابط سياسة الخصوصية

6. نشر الويب

(1) ▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: إعدادات Firebase Hosting

JSON
// firebase.json
{
  "hosting": {
    "public": "build/web",
    "ignore": ["firebase.json", "/.*", "/node_modules/**"],
    "rewrites": [
      { "source": "**", "destination": "/index.html" }
    ],
    "headers": [
      {
        "source": "**/*.@(js|css|wasm)",
        "headers": [
          { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
        ]
      },
      {
        "source": "**/*.@(jpg|jpeg|gif|png|svg|webp|ico)",
        "headers": [
          { "key": "Cache-Control", "value": "public, max-age=604800" }
        ]
      },
      {
        "source": "/index.html",
        "headers": [
          { "key": "Cache-Control", "value": "no-cache" }
        ]
      }
    ]
  }
}

(2) ▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: بناء ونشر الويب

BASH
# Build web with CanvasKit renderer (better performance)
flutter build web --release --web-renderer canvaskit --pwa-strategy offline_first

# Deploy to Firebase Hosting
firebase deploy --only hosting

# Or deploy to custom server with Nginx
scp -r build/web/* user@server:/var/www/shopapp/
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(3) مقارنة تحسين أداء الويب

استراتيجية التحسين أول رسم LCP FID
بدون تحسين (عارض HTML) 8.2 s 7.8 s 180 ms
عارض CanvasKit 3.1 s 2.8 s 45 ms
+ تقسيم كود + تحميل كسول 1.5 s 1.2 s 30 ms
+ PWA + Service Worker 0.8 s (متكرر) 0.5 s 15 ms

7. مراقبة العمليات

(1) ▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: تكامل Crashlytics

DART
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'dart:ui' show PlatformDispatcher;

// Dependencies: firebase_core: ^2.0.0, firebase_crashlytics: ^3.0.0, firebase_auth: ^4.0.0
// DefaultFirebaseOptions / ShopApp come from the ShopApp project

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

  // Crashlytics
  FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
  PlatformDispatcher.instance.onError = (error, stack) {
    FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
    return true;
  };

  // Pass all uncaught errors to Crashlytics
  await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true);

  // Set user identifier for crash reports
  FirebaseAuth.instance.authStateChanges().listen((user) {
    if (user != null) {
      FirebaseCrashlytics.instance.setUserIdentifier(user.uid);
    }
  });

  // Custom keys for context
  FirebaseCrashlytics.instance.setCustomKey('app_version', '1.0.0');
  FirebaseCrashlytics.instance.setCustomKey('environment', 'production');

  runApp(const ShopApp());
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(2) ▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: مراقبة الأداء

DART
import 'package:dio/dio.dart';
import 'package:firebase_performance/firebase_performance.dart';

// Dependencies: firebase_performance: ^0.9.0, dio: ^5.0.0 (add to dependencies)

class ShopPerformance {
  static final _perf = FirebasePerformance.instance;

  static Future<void> trackProductLoad(String productId) async {
    final trace = _perf.newTrace('product_load');
    trace.putAttribute('product_id', productId);
    await trace.start();
    try {
      // ... load product ...
    } finally {
      await trace.stop();
    }
  }

  static Future<void> trackSearch(String query, int results) async {
    final trace = _perf.newTrace('search');
    trace.putAttribute('query', query);
    trace.putMetric('results_count', results);
    await trace.start();
    await trace.stop();
  }

  static Future<void> trackCheckout(double total) async {
    final trace = _perf.newTrace('checkout');
    trace.putMetric('order_total_cents', (total * 100).toInt());
    await trace.start();
    await trace.stop();
  }
}

// HTTP performance auto-tracking with Dio
class PerformanceInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    options.extra['trace'] = FirebasePerformance.instance.newHttpMetric(
      options.uri.toString(), HttpMethod.Post);
    (options.extra['trace'] as HttpMetric).start();
    handler.next(options);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    final trace = response.requestOptions.extra['trace'] as HttpMetric?;
    trace?.httpResponseCode = response.statusCode;
    trace?.stop();
    handler.next(response);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    final trace = err.requestOptions.extra['trace'] as HttpMetric?;
    trace?.httpResponseCode = err.response?.statusCode;
    trace?.stop();
    handler.next(err);
  }
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

8. مثال كامل: نص فحص مؤتمت قبل الإصدار

DART
// tool/release_check.dart
import 'dart:io';

// Pure Dart script, no Flutter dependency, run with dart run tool/release_check.dart

enum Platform { android, ios, web }

class ReleaseChecker {
  static Future<bool> run(Platform platform) async {
    final results = <String, bool>{};

    results['version_updated'] = _checkVersionUpdated();
    results['changelog_updated'] = _checkChangelog();
    results['no_debug_print'] = _checkNoDebugPrint();
    results['proguard_rules'] = platform == Platform.android ? _checkProguard() : true;
    results['info_plist'] = platform == Platform.ios ? _checkInfoPlist() : true;
    results['privacy_policy'] = _checkPrivacyPolicy();
    results['signing_config'] = _checkSigningConfig(platform);
    results['test_pass'] = await _runTests();
    results['analyze_pass'] = await _runAnalyze();

    final allPass = results.values.every((v) => v);
    if (!allPass) {
      print('Release check FAILED:');
      results.forEach((k, v) { if (!v) print('  ✗ $k'); });
    } else {
      print('All release checks PASSED!');
    }
    return allPass;
  }

  static bool _checkVersionUpdated() {
    // Compare current pubspec.yaml version with last git tag
    return true; // Simplified
  }

  static bool _checkChangelog() => File('CHANGELOG.md').existsSync();

  static bool _checkNoDebugPrint() {
    // Grep for debugPrint / print() in lib/
    return true; // Simplified
  }

  static bool _checkProguard() => File('android/app/proguard-rules.pro').existsSync();

  static bool _checkInfoPlist() {
    final plist = File('ios/Runner/Info.plist').readAsStringSync();
    return plist.contains('NSCameraUsageDescription') && plist.contains('NSPhotoLibraryUsageDescription');
  }

  static bool _checkPrivacyPolicy() => true;

  static bool _checkSigningConfig(Platform platform) {
    if (platform == Platform.android) return File('android/key.properties').existsSync();
    return true; // iOS uses Xcode managed signing
  }

  static Future<bool> _runTests() async {
    final result = await Process.run('flutter', ['test']);
    return result.exitCode == 0;
  }

  static Future<bool> _runAnalyze() async {
    final result = await Process.run('flutter', ['analyze']);
    return result.exitCode == 0;
  }
}

void main(List<String> args) async {
  final platform = switch (args.firstOrNull) {
    'android' => Platform.android,
    'ios' => Platform.ios,
    'web' => Platform.web,
    _ => Platform.android,
  };
  final pass = await ReleaseChecker.run(platform);
  exit(pass ? 0 : 1);
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

9. نظرة عامة على عملية النشر

100%
graph TD
    CODE[Source Code] --> ANALYZE[flutter analyze]
    ANALYZE --> TEST[flutter test]
    TEST --> CHECK{Release Check}
    CHECK -->|FAIL| FIX[Fix Issues]
    FIX --> ANALYZE
    CHECK -->|PASS| BUILD[flutter build]
    BUILD --> ANDROID[Android: AAB → Play Console]
    BUILD --> IOS[iOS: IPA → TestFlight → App Store]
    BUILD --> WEB[Web: Firebase Hosting]
    ANDROID --> MONITOR[Crashlytics + Performance]
    IOS --> MONITOR
    WEB --> MONITOR
    MONITOR --> ITERATE[Iterate & Release Next Version]
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

❓ أسئلة شائعة

س ما الفرق بين App Bundle وAPK؟
ج AAB هو تنسيق نشر Google Play الذي يقسم تلقائيًا حسب ABI/كثافة الجهاز لحجم تنزيل أصغر. APK هو حزمة التثبيت الكاملة المستخدمة للتحميل الجانبي.
س كم تستغرق مراجعة iOS عادة؟
ج التقديم الأول 2-5 أيام، التحديثات اللاحقة 1-2 يوم. بعد الرفض، الإصلاح وإعادة التقديم يستغرق 1-3 أيام إضافية.
س هل Flutter Web يدعم SEO؟
ج Flutter Web هو SPA — محركات البحث تواجه صعوبة في فهرسة المحتوى. حلول: 1) استخدم SSR (dart2js + خادم)؛ 2) اعرض الصفحات الرئيسية مسبقًا؛ 3) أضف صفحات هبوط ثابتة.
س ماذا لو تتبعات Crashlytics غير مقروءة بعد التشويش؟
ج استخدم --split-debug-info أثناء البناء لحفظ جداول الرموز، ثم ارفع ملفات إلغاء التشويش (أندرويد) أو dSYMs (iOS) إلى Crashlytics لاستعادة تتبعات المكدس.
س هل التحديث السريع / Code Push يدعم Flutter؟
ج Flutter لا يدعم التحديث السريع للكود أصليًا. يمكنك استخدام shorebird.dev لدفع كود جزئي (أندرويد)، لكن iOS لا يزال يحتاج مراجعة App Store.
س كيف أدير الإصدارات عبر إطلاقات متزامنة متعددة المنصات؟
ج استخدم إصدار pubspec.yaml موحد، مع بناء مصفوفة CI/CD (GitHub Actions: وظائف android + ios + web بالتوازي).
س ما معدل الأعطال الطبيعي بعد الإطلاق؟
ج ممتاز < 0.1%، جيد < 0.5%، يحتاج انتباه > 1%. Crashlytics يوفر لوحة معدل أعطال فورية.

📖 ملخص


📝 تمارين

  1. أساسي (الصعوبة ⭐): أعد مفتاح توقيع أندرويد + قواعد ProGuard، ابنِ Release App Bundle، وتحقق من تحسين الحجم.
  2. متوسط (الصعوبة ⭐⭐): أعد Crashlytics + Performance Monitoring، حاكِ عطلًا وطلبًا بطيئًا، وتحقق من رفع بيانات المراقبة.
  3. متقدم (الصعوبة ⭐⭐⭐): اكتب خط أنابيب CI/CD كامل (GitHub Actions): analyze ← test ← release check ← build (android + ios + web) ← deploy، محققًا إصدارًا عبر المنصات بنقرة واحدة.

← الدرس السابق | العودة للفهرس

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%