إضافة "خرائط Google" إلى تطبيق Flutter

1. مقدمة

‫Flutter هي حزمة تطوير البرامج لتطبيقات الأجهزة الجوّالة من Google، ويمكن استخدامها لإنشاء تجارب عالية الجودة ومصمّمة للاستخدام على نظامَي التشغيل iOS وAndroid في وقت قياسي.

باستخدام مكوّن Flutter الإضافي لـ "خرائط Google"، يمكنك إضافة خرائط تستند إلى بيانات "خرائط Google" إلى تطبيقك. يتعامل المكوّن الإضافي تلقائيًا مع إمكانية الوصول إلى خوادم "خرائط Google" وعرض الخريطة والاستجابة لإيماءات المستخدمين، مثل النقرات وعمليات السحب. يمكنك أيضًا إضافة علامات إلى خريطتك. توفّر هذه العناصر معلومات إضافية عن المواقع الجغرافية على الخريطة، وتسمح للمستخدم بالتفاعل مع الخريطة.

ما ستنشئه

في هذا الدرس التطبيقي حول الترميز، ستنشئ تطبيقًا على الأجهزة الجوّالة يتضمّن "خريطة Google" باستخدام حزمة تطوير البرامج (SDK) من Flutter. سيتم إجراء ما يلي في تطبيقك:

  • عرض خريطة Google
  • استرداد بيانات الخريطة من خدمة ويب
  • عرض هذه البيانات كعلامات على الخريطة

لقطة شاشة لتطبيق Flutter يعرض "خريطة Google" في محاكي iPhone مع تمييز Mountain View

ما هو Flutter؟

يوفّر Flutter ثلاث إمكانات أساسية.

  • سرعة التطوير: يمكنك إنشاء تطبيقات Android وiOS في غضون أجزاء من الثانية باستخدام ميزة "إعادة التحميل السريع مع الحفاظ على الحالة".
  • تعبيرية ومرنة: يمكنك إطلاق الميزات بسرعة مع التركيز على تجارب المستخدمين الأصلية.
  • الأداء الأصلي على كلّ من iOS وAndroid: تتضمّن أدوات Flutter جميع الاختلافات المهمة في الأنظمة الأساسية، مثل التمرير والتنقّل والرموز والخطوط، لتوفير أداء أصلي كامل.

تتضمّن "خرائط Google" ما يلي:

  • تغطية% 99 من العالم: يمكنك إنشاء تطبيقات باستخدام بيانات شاملة وموثوقة لأكثر من 200 بلد ومنطقة.
  • 25 مليون تعديل يوميًا: يمكنك الاعتماد على معلومات دقيقة عن الموقع الجغرافي في الوقت الفعلي.
  • مليار مستخدم نشط شهريًا: يمكنك التوسّع بثقة، مع الاستفادة من البنية التحتية لـ "خرائط Google".

يوضّح لك هذا الدرس التطبيقي حول الترميز كيفية إنشاء تجربة في "خرائط Google" ضمن تطبيق Flutter على كل من iOS وAndroid.

ما ستتعلمه

  • كيفية إنشاء تطبيق Flutter جديد
  • كيفية ضبط إعدادات مكوّن Flutter الإضافي لـ "خرائط Google"
  • كيفية إضافة علامات إلى خريطة باستخدام بيانات الموقع الجغرافي من خدمة ويب

يركّز هذا الدرس التطبيقي حول الترميز على إضافة خريطة Google إلى تطبيق Flutter. يتم تجاهل المفاهيم وكتل الرموز غير ذات الصلة، ويتم توفيرها لك لنسخها ولصقها ببساطة.

ما الذي تريد تعلّمه من هذا الدرس العملي؟

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

2. إعداد بيئة Flutter

تحتاج إلى برنامجَين لإكمال هذا الدرس التطبيقي: حزمة تطوير البرامج (SDK) الخاصة بإطار عمل Flutter ومحرِّر. يفترض هذا الدرس التطبيقي العملي استخدام "استوديو Android"، ولكن يمكنك استخدام أداة التعديل المفضّلة لديك.

يمكنك تشغيل هذا الدرس التطبيقي العملي باستخدام أي من الأجهزة التالية:

  • جهاز فعلي (Android أو iOS) متصل بجهاز الكمبيوتر وتم ضبطه على وضع مطور البرامج
  • محاكي iOS (يتطلّب ذلك تثبيت أدوات Xcode).
  • محاكي Android (يتطلّب الإعداد في استوديو Android)

3- الخطوات الأولى

بدء استخدام Flutter

أسهل طريقة للبدء باستخدام Flutter هي استخدام أداة سطر الأوامر لإنشاء جميع الرموز المطلوبة لتوفير تجربة بسيطة للمبتدئين.

$ flutter create google_maps_in_flutter --platforms android,ios,web
Creating project google_maps_in_flutter...
Resolving dependencies in `google_maps_in_flutter`... 
Downloading packages... 
Got dependencies in `google_maps_in_flutter`.
Wrote 81 files.

All done!
You can find general documentation for Flutter at: https://docs.flutter.dev/
Detailed API documentation is available at: https://api.flutter.dev/
If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev

In order to run your application, type:

  $ cd google_maps_in_flutter
  $ flutter run

Your application code is in google_maps_in_flutter/lib/main.dart.

إضافة مكوّن خرائط Google Flutter الإضافي كعنصر تابع

يمكنك بسهولة إضافة إمكانات إضافية إلى تطبيق Flutter باستخدام حِزم Pub. في هذا الدرس التطبيقي حول الترميز، ستتعرّف على المكوّن الإضافي لخرائط Google في Flutter من خلال تنفيذ الأمر التالي من دليل المشروع.

$ cd google_maps_in_flutter
$ flutter pub add google_maps_flutter
Resolving dependencies... 
Downloading packages... 
+ csslib 1.0.0
+ flutter_plugin_android_lifecycle 2.0.19
+ flutter_web_plugins 0.0.0 from sdk flutter
+ google_maps 7.1.0
+ google_maps_flutter 2.6.1
+ google_maps_flutter_android 2.8.0
+ google_maps_flutter_ios 2.6.0
+ google_maps_flutter_platform_interface 2.6.0
+ google_maps_flutter_web 0.5.7
+ html 0.15.4
+ js 0.6.7 (0.7.1 available)
+ js_wrapping 0.7.4
  leak_tracker 10.0.4 (10.0.5 available)
  leak_tracker_flutter_testing 3.0.3 (3.0.5 available)
  material_color_utilities 0.8.0 (0.11.1 available)
  meta 1.12.0 (1.14.0 available)
+ plugin_platform_interface 2.1.8
+ sanitize_html 2.1.0
+ stream_transform 2.1.0
  test_api 0.7.0 (0.7.1 available)
+ web 0.5.1
Changed 16 dependencies!
6 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.

إعداد platform على أجهزة iOS

يتطلّب الحصول على أحدث إصدار من حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" على نظام التشغيل iOS توفّر الإصدار 14 من نظام التشغيل iOS كحدّ أدنى. عدِّل الجزء العلوي من ملف إعدادات ios/Podfile على النحو التالي.

ios/Podfile

# Google Maps SDK requires platform version 14
# https://developers.google.com/maps/flutter-package/config#ios
platform :ios, '14.0'


# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

ضبط إعدادات Android minSDK

لاستخدام حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" على Android، يجب ضبط minSdk على 21. عدِّل ملف إعدادات android/app/build.gradle على النحو التالي.

android/app/build.gradle

android {
    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId = "com.example.google_maps_in_flutter"
        // Minimum Android version for Google Maps SDK
        // https://developers.google.com/maps/flutter-package/config#android
        minSdk = 21
        targetSdk = flutter.targetSdkVersion
        versionCode = flutterVersionCode.toInteger()
        versionName = flutterVersionName
    }

}

4. إضافة "خرائط Google" إلى التطبيق

الأمر يتعلق بمفاتيح واجهة برمجة التطبيقات

لاستخدام "خرائط Google" في تطبيق Flutter، عليك إعداد مشروع واجهة برمجة تطبيقات باستخدام منصة خرائط Google، وذلك باتّباع استخدام مفتاح واجهة برمجة التطبيقات في حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google للتطبيقات المتوافقة مع Android واستخدام مفتاح واجهة برمجة التطبيقات في حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" للتطبيقات المتوافقة مع iOS واستخدام مفتاح واجهة برمجة التطبيقات في Maps JavaScript API. بعد الحصول على مفاتيح واجهة برمجة التطبيقات، اتّبِع الخطوات التالية لإعداد كلّ من تطبيقات Android وiOS.

إضافة مفتاح واجهة برمجة تطبيقات لتطبيق Android

لإضافة مفتاح واجهة برمجة التطبيقات إلى تطبيق Android، عدِّل الملف AndroidManifest.xml في android/app/src/main. أضِف إدخالاً واحدًا meta-data يحتوي على مفتاح واجهة برمجة التطبيقات الذي تم إنشاؤه في الخطوة السابقة داخل عقدة application.

android/app/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:label="google_maps_in_flutter"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">

        <!-- TODO: Add your Google Maps API key here -->
        <meta-data android:name="com.google.android.geo.API_KEY"
               android:value="YOUR-KEY-HERE"/>

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:taskAffinity=""
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
    <!-- Required to query activities that can process text, see:
         https://developer.android.com/training/package-visibility and
         https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.

         In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
    <queries>
        <intent>
            <action android:name="android.intent.action.PROCESS_TEXT"/>
            <data android:mimeType="text/plain"/>
        </intent>
    </queries>
</manifest>

إضافة مفتاح واجهة برمجة تطبيقات لتطبيق iOS

لإضافة مفتاح واجهة برمجة تطبيقات إلى تطبيق iOS، عدِّل الملف AppDelegate.swift في ios/Runner. على عكس نظام التشغيل Android، تتطلّب إضافة مفتاح واجهة برمجة التطبيقات على نظام التشغيل iOS إجراء تغييرات على الرمز المصدر لتطبيق Runner. وAppDelegate هو العنصر الأساسي الفردي الذي يشكّل جزءًا من عملية إعداد التطبيق.

أريد إجراء تغييرَين على هذا الملف. أولاً، أضِف عبارة #import لجلب عناوين خرائط Google، ثم استدعِ طريقة provideAPIKey() من عنصر GMSServices الفردي. يتيح مفتاح واجهة برمجة التطبيقات هذا لـ "خرائط Google" عرض مربّعات الخرائط بشكلٍ صحيح.

ios/Runner/AppDelegate.swift

import Flutter
import UIKit
import GoogleMaps                                          // Add this import

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)

    // TODO: Add your Google Maps API key
    GMSServices.provideAPIKey("YOUR-API-KEY")               // Add this line

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

إضافة مفتاح واجهة برمجة التطبيقات لتطبيق ويب

لإضافة مفتاح واجهة برمجة تطبيقات إلى تطبيق الويب، عدِّل ملف index.html في web. أضِف مرجعًا إلى نص JavaScript البرمجي الخاص بـ "خرائط Google" في قسم العنوان، مع مفتاح واجهة برمجة التطبيقات.

web/index.html

<!DOCTYPE html>
<html>
<head>
  <!--
    If you are serving your web app in a path other than the root, change the
    href value below to reflect the base path you are serving from.

    The path provided below has to start and end with a slash "/" in order for
    it to work correctly.

    For more details:
    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base

    This is a placeholder for base href that will be replaced by the value of
    the `--base-href` argument provided to `flutter build`.
  -->
  <base href="$FLUTTER_BASE_HREF">

  <meta charset="UTF-8">
  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
  <meta name="description" content="A new Flutter project.">

  <!-- iOS meta tags & icons -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="google_maps_in_flutter">
  <link rel="apple-touch-icon" href="icons/Icon-192.png">

  <!-- Favicon -->
  <link rel="icon" type="image/png" href="favicon.png"/>

  <!-- Add your Google Maps API key here -->
  <script src="https://maps.googleapis.com/maps/api/js?key=YOUR-KEY-HERE"></script>

  <title>google_maps_in_flutter</title>
  <link rel="manifest" href="manifest.json">
</head>
<body>
  <script src="flutter_bootstrap.js" async></script>
</body>
</html>

عرض خريطة على الشاشة

حان الوقت الآن لعرض خريطة على الشاشة. استبدِل محتوى lib/main.dart بما يلي.

lib/main.dart

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late GoogleMapController mapController;

  final LatLng _center = const LatLng(45.521563, -122.677433);

  void _onMapCreated(GoogleMapController controller) {
    mapController = controller;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: Colors.green[700],
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Maps Sample App'),
          elevation: 2,
        ),
        body: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: CameraPosition(
            target: _center,
            zoom: 11.0,
          ),
        ),
      ),
    );
  }
}

تشغيل التطبيق

شغِّل تطبيق Flutter على نظام التشغيل iOS أو Android للاطّلاع على عرض خريطة واحد في وسط مدينة بورتلاند. يمكنك بدلاً من ذلك تشغيل محاكي Android أو محاكي iOS. يمكنك تعديل مركز الخريطة لتمثيل مسقط رأسك أو مكان مهم بالنسبة إليك.

$ flutter run

لقطة شاشة لتطبيق Flutter يعرض &quot;خريطة Google&quot; في محاكي iPhone

لقطة شاشة لتطبيق Flutter يعرض &quot;خريطة Google&quot; في محاكي Android

5- وضع Google على الخريطة

لدى Google العديد من المكاتب حول العالم، بدءًا من أمريكا الشمالية وأمريكا اللاتينية وأوروبا وآسيا والمحيط الهادئ وصولاً إلى أفريقيا والشرق الأوسط. الميزة الرائعة في هذه الخرائط، إذا بحثت فيها، هي أنّها تتضمّن نقطة نهاية لواجهة برمجة التطبيقات سهلة الاستخدام لتوفير معلومات موقع المكتب بتنسيق JSON. في هذه الخطوة، يمكنك وضع مواقع المكاتب هذه على الخريطة. في هذه الخطوة، ستستخدم إنشاء الرمز البرمجي لتحليل JSON.

أضِف ثلاث تبعيات جديدة في Flutter إلى المشروع على النحو التالي. أضِف حزمة http لتسهيل تقديم طلبات HTTP، وحزمتَي json_serializable و json_annotation لتحديد بنية الكائن لتمثيل مستندات JSON، وأضِف حزمة build_runner لتوفير إمكانية إنشاء الرموز.

$ flutter pub add http json_annotation json_serializable dev:build_runner
Resolving dependencies... 
Downloading packages... 
+ _fe_analyzer_shared 67.0.0 (68.0.0 available)
+ analyzer 6.4.1 (6.5.0 available)
+ args 2.5.0
+ build 2.4.1
+ build_config 1.1.1
+ build_daemon 4.0.1
+ build_resolvers 2.4.2
+ build_runner 2.4.9
+ build_runner_core 7.3.0
+ built_collection 5.1.1
+ built_value 8.9.2
+ checked_yaml 2.0.3
+ code_builder 4.10.0
+ convert 3.1.1
+ crypto 3.0.3
+ dart_style 2.3.6
+ file 7.0.0
+ fixnum 1.1.0
+ frontend_server_client 4.0.0
+ glob 2.1.2
+ graphs 2.3.1
+ http 1.2.1
+ http_multi_server 3.2.1
+ http_parser 4.0.2
+ io 1.0.4
  js 0.6.7 (0.7.1 available)
+ json_annotation 4.9.0
+ json_serializable 6.8.0
  leak_tracker 10.0.4 (10.0.5 available)
  leak_tracker_flutter_testing 3.0.3 (3.0.5 available)
+ logging 1.2.0
  material_color_utilities 0.8.0 (0.11.1 available)
  meta 1.12.0 (1.14.0 available)
+ mime 1.0.5
+ package_config 2.1.0
+ pool 1.5.1
+ pub_semver 2.1.4
+ pubspec_parse 1.2.3
+ shelf 1.4.1
+ shelf_web_socket 1.0.4
+ source_gen 1.5.0
+ source_helper 1.3.4
  test_api 0.7.0 (0.7.1 available)
+ timing 1.0.1
+ typed_data 1.3.2
+ watcher 1.1.0
+ web_socket_channel 2.4.5
+ yaml 3.1.2
Changed 42 dependencies!
8 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.

تحليل JSON باستخدام إنشاء الرموز البرمجية

ربما لاحظت أنّ بيانات JSON التي يتم عرضها من نقطة نهاية واجهة برمجة التطبيقات تتضمّن بنية عادية. سيكون من المفيد إنشاء الرمز البرمجي لترتيب هذه البيانات في عناصر يمكنك استخدامها في الرمز البرمجي.

في الدليل lib/src، أنشئ ملف locations.dart واشرح بنية بيانات JSON التي تم إرجاعها على النحو التالي:

lib/src/locations.dart

import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:http/http.dart' as http;
import 'package:json_annotation/json_annotation.dart';

part 'locations.g.dart';

@JsonSerializable()
class LatLng {
  LatLng({
    required this.lat,
    required this.lng,
  });

  factory LatLng.fromJson(Map<String, dynamic> json) => _$LatLngFromJson(json);
  Map<String, dynamic> toJson() => _$LatLngToJson(this);

  final double lat;
  final double lng;
}

@JsonSerializable()
class Region {
  Region({
    required this.coords,
    required this.id,
    required this.name,
    required this.zoom,
  });

  factory Region.fromJson(Map<String, dynamic> json) => _$RegionFromJson(json);
  Map<String, dynamic> toJson() => _$RegionToJson(this);

  final LatLng coords;
  final String id;
  final String name;
  final double zoom;
}

@JsonSerializable()
class Office {
  Office({
    required this.address,
    required this.id,
    required this.image,
    required this.lat,
    required this.lng,
    required this.name,
    required this.phone,
    required this.region,
  });

  factory Office.fromJson(Map<String, dynamic> json) => _$OfficeFromJson(json);
  Map<String, dynamic> toJson() => _$OfficeToJson(this);

  final String address;
  final String id;
  final String image;
  final double lat;
  final double lng;
  final String name;
  final String phone;
  final String region;
}

@JsonSerializable()
class Locations {
  Locations({
    required this.offices,
    required this.regions,
  });

  factory Locations.fromJson(Map<String, dynamic> json) =>
      _$LocationsFromJson(json);
  Map<String, dynamic> toJson() => _$LocationsToJson(this);

  final List<Office> offices;
  final List<Region> regions;
}

Future<Locations> getGoogleOffices() async {
  const googleLocationsURL = 'https://about.google/static/data/locations.json';

  // Retrieve the locations of Google offices
  try {
    final response = await http.get(Uri.parse(googleLocationsURL));
    if (response.statusCode == 200) {
      return Locations.fromJson(
          json.decode(response.body) as Map<String, dynamic>);
    }
  } catch (e) {
    if (kDebugMode) {
      print(e);
    }
  }

  // Fallback for when the above HTTP request fails.
  return Locations.fromJson(
    json.decode(
      await rootBundle.loadString('assets/locations.json'),
    ) as Map<String, dynamic>,
  );
}

بعد إضافة هذا الرمز، من المفترض أن تعرض بيئة التطوير المتكاملة (IDE) (إذا كنت تستخدم إحداها) بعض الخطوط الحمراء المتعرجة، لأنّها تشير إلى ملف مجاور غير موجود، locations.g.dart. يحوّل هذا الملف الذي تم إنشاؤه بين بنى JSON غير مكتوبة وأسماء الكائنات. يمكنك إنشاء هذا الملف عن طريق تنفيذ الأمر build_runner على النحو التالي:

$ dart run build_runner build --delete-conflicting-outputs
[INFO] Generating build script...
[INFO] Generating build script completed, took 357ms

[INFO] Creating build script snapshot......
[INFO] Creating build script snapshot... completed, took 10.5s

[INFO] There was output on stdout while compiling the build script snapshot, run with `--verbose` to see it (you will need to run a `clean` first to re-snapshot).

[INFO] Initializing inputs
[INFO] Building new asset graph...
[INFO] Building new asset graph completed, took 646ms

[INFO] Checking for unexpected pre-existing outputs....
[INFO] Deleting 1 declared outputs which already existed on disk.
[INFO] Checking for unexpected pre-existing outputs. completed, took 3ms

[INFO] Running build...
[INFO] Generating SDK summary...
[INFO] 3.4s elapsed, 0/3 actions completed.
[INFO] Generating SDK summary completed, took 3.4s

[INFO] 4.7s elapsed, 2/3 actions completed.
[INFO] Running build completed, took 4.7s

[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 36ms

[INFO] Succeeded after 4.8s with 2 outputs (7 actions)

من المفترض أن يتم تحليل الرمز البرمجي الآن بشكل سليم مرة أخرى. بعد ذلك، علينا إضافة ملف locations.json الاحتياطي المستخدَم في الدالة getGoogleOffices. أحد أسباب تضمين هذا الخيار الاحتياطي هو أنّ البيانات الثابتة التي يتم تحميلها في هذه الدالة يتم عرضها بدون عناوين CORS، وبالتالي لن يتم تحميلها في متصفّح الويب. لا تحتاج تطبيقات Flutter على Android وiOS إلى عناوين CORS، ولكن قد يكون الوصول إلى بيانات شبكة الجوّال صعبًا في أفضل الأوقات.

انتقِل إلى https://about.google/static/data/locations.json في المتصفّح، واحفظ المحتوى في دليل مواد العرض. بدلاً من ذلك، يمكنك استخدام سطر الأوامر على النحو التالي.

$ mkdir assets
$ cd assets
$ curl -o locations.json https://about.google/static/data/locations.json
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 30348  100 30348    0     0  75492      0 --:--:-- --:--:-- --:--:-- 75492

بعد تنزيل ملف مادة العرض، أضِفه إلى قسم Flutter في ملف pubspec.yaml.

pubspec.yaml

flutter:
  uses-material-design: true

  assets:
    - assets/locations.json

عدِّل الملف main.dart لطلب بيانات الخريطة، ثم استخدِم المعلومات التي تم إرجاعها لإضافة مكاتب إلى الخريطة:

lib/main.dart

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'src/locations.dart' as locations;

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final Map<String, Marker> _markers = {};
  Future<void> _onMapCreated(GoogleMapController controller) async {
    final googleOffices = await locations.getGoogleOffices();
    setState(() {
      _markers.clear();
      for (final office in googleOffices.offices) {
        final marker = Marker(
          markerId: MarkerId(office.name),
          position: LatLng(office.lat, office.lng),
          infoWindow: InfoWindow(
            title: office.name,
            snippet: office.address,
          ),
        );
        _markers[office.name] = marker;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: Colors.green[700],
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Google Office Locations'),
          elevation: 2,
        ),
        body: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: const CameraPosition(
            target: LatLng(0, 0),
            zoom: 2,
          ),
          markers: _markers.values.toSet(),
        ),
      ),
    );
  }
}

ينفّذ هذا الرمز عدة عمليات:

  • في _onMapCreated، يتم استخدام رمز تحليل JSON من الخطوة السابقة، ويتم await إلى أن يتم تحميله. ثم تستخدم البيانات التي تم إرجاعها لإنشاء Markers داخل دالة معاودة الاتصال setState(). بعد أن يتلقّى التطبيق علامات جديدة، تطلب الدالة setState من Flutter إعادة طلاء الشاشة، ما يؤدي إلى عرض مواقع المكاتب.
  • يتم تخزين العلامات في Map مرتبط بأداة GoogleMap. يؤدي ذلك إلى ربط علامات محدّد الموقع بالخريطة الصحيحة. يمكنك بالطبع استخدام خرائط متعددة وعرض علامات مختلفة في كل منها.

لقطة شاشة لتطبيق Flutter يعرض &quot;خريطة Google&quot; في محاكي iPhone مع تمييز Mountain View

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

6. الخطوات التالية

تهانينا!

لقد أكملت الدرس التطبيقي حول الترميز وأنشأت تطبيق Flutter يتضمّن خريطة Google. تفاعلت أيضًا مع خدمة ويب JSON.

الخطوات التالية الأخرى

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