بدء استخدام حزمة تطوير البرامج (SDK) لتطبيق Drive لنظام التشغيل iOS

حزمة Driver SDK هي مكتبة تدمجها في تطبيق برنامج التشغيل. من المهم مسئولاً عن تحديث Fleet Engine بموقع السائق وطريقه والمسافة المتبقية والوقت المقدر للوصول. وهي تتكامل أيضًا مع حزمة SDK للتنقل، والتي توفّر إرشادات التنقّل باتّجاهات مفصّلة للسائق.

الحد الأدنى لمتطلبات النظام

  • يجب أن يعمل الجهاز الجوّال بنظام التشغيل iOS 14 أو إصدار أحدث.
  • Xcode الإصدار 15 أو إصدار أحدث
  • المتطلبات الأساسية

    يفترض هذا الدليل أن تطبيقك ينفذ حزمة SDK للتنقل وأنّ Fleet Engine إعداد الواجهة الخلفية وإتاحتها. ومع ذلك، يوفر مثال التعليمة البرمجية نموذج لكيفية إعداد حزمة SDK للتنقل.

    يجب أيضًا تفعيل حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لنظام التشغيل iOS في مشروع Google Cloud والحصول على مفتاح واجهة برمجة تطبيقات.

    تنمية محلية

    بالنسبة للتنمية المحلية، يكفي تسجيل الدخول باستخدام Cloud SDK:

    gcloud

    gcloud auth login
    

    يجب أن يكون عنوان البريد الإلكتروني المستخدَم لتسجيل الدخول عضوًا في مجموعة Workspace.

    الأتمتة (إنشاء الأنظمة أو الدمج المستمر)

    إعداد مضيفي التشغيل الآلي وفقًا لما يلي: أفضل الممارسات:

    • في حال إجراء العملية داخل بيئة Google Cloud، استخدِم آلي بيانات الاعتماد.

    • بخلاف ذلك، يمكنك تخزين ملف مفتاح حساب الخدمة في موقع آمن على نظام ملفات المضيف وتعيين GOOGLE_APPLICATION_CREDENTIALS متغير البيئة بشكل مناسب.

    يجب أن يكون البريد الإلكتروني لحساب الخدمة المرتبط ببيانات الاعتماد عضوًا في Workspace Goup.

    إعدادات المشروع

    مدير حزم Swift

    يمكن تثبيت Driver SDK من خلال مدير حزم Swift لإضافة حزمة تطوير البرامج (SDK)، تأكَّد من أنّ لديك إزالة أي تبعيات حالية لحزمة تطوير البرامج (SDK) لبرنامج التشغيل.

    لإضافة حزمة تطوير البرامج (SDK) إلى مشروع جديد أو حالي، اتّبِع الخطوات التالية:

    1. افتح Xcode project أو workspace، ثم انتقِل إلى File >. أضِف تبعيات الحزمة.
    2. أدخِل https://github.com/googlemaps/ios-driver-sdk كعنوان URL واضغط على Enter. لسحب الحزمة، والنقر فوق "Add Package" (إضافة حزمة).
    3. لتثبيت قاعدة version محدّدة، اضبط حقل قاعدة التبعية على أحد الخيارات المستندة إلى الإصدار. وبالنسبة للمشروعات الجديدة، نوصي بتحديد أحدث إصدار باستخدام "الإصدار الدقيق" الخيار. بعد اكتمال عملية النقل، انقر على "إضافة حزمة".
    4. من نافذة اختيار منتجات الحزمة، تأكَّد من إضافة GoogleRidesharingDriver إلى هدف main المحدد. بعد اكتمال عملية النقل، انقر على "إضافة حزمة".
    5. للتحقّق من عملية التثبيت، انتقِل إلى جزء General في استهدافك. من المفترَض أن تظهر الحِزَم المثبَّتة في أُطر العمل والمكتبات والمحتوى المضمَّن. يمكنك أيضًا عرض قسم "تبعيات الحزمة" قسم "مستكشف المشروع" للتحقق من الحزمة وإصدارها.

    لتعديل package لمشروع حالي، اتّبِع الخطوات التالية:

    1. في حال الترقية من إصدار أقدم من 9.0.0، يجب إزالة التبعيات التالية: GoogleMapsBase وGoogleMapsCore و GoogleMapsM4B بعد الترقية. لا تقم بإزالة تبعية GoogleMaps لمزيد من المعلومات، يُرجى الاطّلاع على ملاحظات الإصدار 9.0.0:

      من إعدادات ضبط مشروع Xcode، ابحث عن Frameworks وLibraries والمحتوى المضمَّن استخدِم علامة الطرح(-) لإزالة إطار العمل التالي:

      • GoogleMapsBase (للترقيات من الإصدارات السابقة على 9.0.0 فقط)
      • GoogleMapsCore (للترقيات من الإصدارات السابقة على 9.0.0 فقط)
      • GoogleMapsM4B (للترقيات من الإصدارات السابقة على 9.0.0 فقط)
    2. من Xcode، انتقل إلى "File >" (ملف >) الحزم > التحديث إلى أحدث إصدارات الحزمة".
    3. للتحقّق من عملية التثبيت، انتقِل إلى قسم تبعيات الحزمة في Project Navigator (أداة التنقّل في المشروع) للتحقق من الحزمة وإصدارها.

    لإزالة العناصر الاعتمادية الحالية لـ Driver SDK التي تمت إضافتها باستخدام CocoaPods، يُرجى اتّباع الخطوات التالية:

    1. أغلِق مساحة عمل Xcode. افتح المحطة الطرفية ونفِّذ الأمر التالي:
      sudo gem install cocoapods-deintegrate cocoapods-clean 
      pod deintegrate 
      pod cache clean --all
    2. إزالة Podfile وPodfile.resolved و Xcode workspace إذا كنت لا تستخدمهم لأي غرض آخر غير CocoaPods.

    لإزالة تثبيت Driver SDK الحالي، يدويًا، اتبع الخطوات التالية:

    1. من إعدادات ضبط مشروع Xcode، ابحث عن Frameworks، المكتبات والمحتوى المضمَّن استخدِم علامة الطرح(-) لإزالتها إطار العمل التالي:

      • GoogleRidesharingDriver.xcframework
    2. من دليل المستوى الأعلى لمشروع Xcode، أزِل حزمة GoogleRidesharingDriver.

    CocoaPods

    لضبط حزمة تطوير البرامج (SDK) لبرنامج التشغيل باستخدام CocoaPods، ستحتاج إلى العناصر التالية:

    • أداة CocoaPods: لتثبيت هذه الأداة، افتح المحطة الطرفية وشغِّل الأمر التالي.
       sudo gem install cocoapods
    
    1. إنشاء ملف Podfile لـ Driver SDK واستخدامه لتثبيت واجهة برمجة التطبيقات ملحقاته: قم بإنشاء ملف يسمى Podfile في دليل المشروع الخاص بك. يحدد هذا الملف تبعيات مشروعك. قم بتحرير Podfile وإضافة وتبعياتك. فيما يلي مثال يتضمن التبعيات:

      source "https://github.com/CocoaPods/Specs.git"
      
      target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
        pod 'GoogleRidesharingDriver'
      end
      

      في ما يلي مثال يتضمن الإصدارات الأولية والتجريبية حزمة تطوير البرامج (SDK) لبرنامج التشغيل كعناصر تابعة:

      source "https://cpdc-eap.googlesource.com/ridesharing-driver-sdk.git"
      source "https://github.com/CocoaPods/Specs.git"
      
      target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
        pod 'GoogleRidesharingDriver'
      end
      
    2. احفظ ملف Podfile. افتح نافذة طرفية وانتقِل إلى الدليل الذي يحتوي على ملف Podfile:

      cd <path-to-project>
      
    3. شغِّل الأمر pod install. سيؤدي هذا إلى تثبيت واجهات برمجة التطبيقات المحددة في Podfile، إلى جانب أي تبعيات قد تكون موجودة.

      pod install
      
    4. أغلِق Xcode، ثم افتح ملف xcworkspace لمشروعك (بالنقر مرّتين). لتشغيل Xcode. ومن الآن فصاعدًا، يجب عليك استخدام .xcworkspace لفتح المشروع.

    يُرجى الاطّلاع على مقالة بدء استخدام CocoaPods. لمزيد من المعلومات التفاصيل.

    التثبيت اليدوي

    XCFramework عبارة عن حزمة ثنائية تستخدمها لتثبيت SDK لبرنامج التشغيل. يمكنك استخدام هذه الحزمة على عدة حِزم بما في ذلك الأجهزة التي تستخدم نظام Apple السيليكون. يوضح هذا الدليل كيفية أضف XCFramework يدويًا الذي يحتوي على حزمة تطوير البرامج (SDK) لبرنامج التشغيل إلى مشروعك وإعداد إصدارك الإعدادات في Xcode.

    تنزيل البرنامج الثنائي لحزمة SDK والموارد:

    1. يمكنك استخراج الملفات للوصول إلى XCFramework والموارد.

    2. ابدأ Xcode وافتح مشروعًا حاليًا أو أنشئ مشروعًا جديدًا مشروعك. إذا كنت مستخدمًا جديدًا لنظام التشغيل iOS، أنشئ مشروعًا جديدًا واختَر نظام التشغيل iOS. نموذج التطبيق.

    3. إنشاء مجموعة أطر عمل ضمن مجموعة المشروعات إذا لم تكن هناك مجموعة بالفعل.

    4. لتثبيت Driver SDK، اسحب ملف واحد (GoogleRidesharingDriver.xcframework) إلى مشروعك ضمن أُطر العمل والمكتبات والمحتوى المضمَّن: عندما يُطلب منك ذلك، اختَر انسخ العناصر إذا لزم الأمر.

    5. اسحب "GoogleRidesharingDriver.bundle" الذي تم تنزيله إلى المستوى الأعلى. دليل مشروع Xcode الخاص بك. اختَر "Copy items if needed" عندما يُطلب منك ذلك.

    6. اختَر مشروعك من Project Navigator، ثم اختَر هدف التطبيق.

    7. افتح علامة التبويب "مراحل الإنشاء"، وفي "Link Binary with Libraries" (رابط ثنائي مع المكتبات)، أضف أُطر العمل والمكتبات التالية إذا لم تكن موجودة بالفعل:

      • Accelerate.framework
      • AudioToolbox.framework
      • AVFoundation.framework
      • CoreData.framework
      • CoreGraphics.framework
      • CoreLocation.framework
      • CoreTelephony.framework
      • CoreText.framework
      • GLKit.framework
      • ImageIO.framework
      • libc++.tbd
      • libxml2.tbd
      • libz.tbd
      • LocalAuthentication.framework
      • OpenGLES.framework
      • QuartzCore.framework
      • SystemConfiguration.framework
      • UIKit.framework
      • WebKit.framework
    8. اختر مشروعك بدلاً من هدف محدد، وافتح زر إنشاء الإعدادات في القسم علامات الروابط الأخرى، أضِف -ObjC عندما تصحيح الأخطاء والإصدار. إذا لم تكن هذه الإعدادات مرئية، فغيّر في شريط "إعدادات الإصدار" من أساسي إلى الكل.

    فحص ملف بيان الخصوصية في Apple

    تشترط Apple تفاصيل خصوصية التطبيقات للتطبيقات المتوفّرة على App Store. يُرجى الانتقال إلى صفحة تفاصيل خصوصية Apple App Store للاطّلاع على آخر الأخبار والمعلومات.

    يتم تضمين ملف بيان الخصوصية من Apple في حزمة الموارد لحزمة تطوير البرامج (SDK). للتأكّد من تضمين "ملف بيان الخصوصية" وفحص محتواه، عليك إنشاء أرشيف لتطبيقك وإنشاء تقرير خصوصية من الأرشيف.

    تنفيذ التفويض والمصادقة

    عندما ينشئ تطبيق Driver التحديثات ويرسلها إلى خلفية Fleet Engine، يجب أن تتضمن الطلبات رموز دخول صالحة. لمنح الإذن بمصادقة هذه الطلبات، فإن "حزمة تطوير البرامج (SDK) لبرنامج التشغيل" تستدعي الكائن الذي يتوافق مع GMTDAuthorization والبروتوكول. الكائن مسؤول عن توفير رمز الدخول المطلوب.

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

    • استرجاع رمز دخول، ربما بتنسيق JSON، من خادم HTTPS.
    • تحليل الرمز المميّز وتخزينه مؤقتًا
    • أعِد تحميل الرمز المميّز عند انتهاء صلاحيته.

    للحصول على تفاصيل الرموز المميّزة التي يتوقعها خادم Fleet Engine، يُرجى الاطّلاع على إنشاء رمز JSON المميّز للويب (JWT) للتفويض:

    رقم تعريف مقدّم الخدمة هو نفسه رقم تعريف مشروع Google Cloud. يُرجى الاطّلاع على دليل المستخدم لواجهة برمجة التطبيقات Fleet Engine Deliveries API. لمزيد من المعلومات.

    يقدم المثال التالي موفّرًا لرمز الدخول:

    #import "SampleAccessTokenProvider.h"
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    // SampleAccessTokenProvider.h
    @interface SampleAccessTokenProvider : NSObject<GMTDAuthorization>
    @end
    
    static NSString *const PROVIDER_URL = @"INSERT_YOUR_TOKEN_PROVIDER_URL";
    
    // SampleAccessTokenProvider.m
    @implementation SampleAccessTokenProvider{
      // The cached vehicle token.
      NSString *_cachedVehicleToken;
      // Keep track of the vehicle ID the cached token is for.
      NSString *_lastKnownVehicleID;
      // Keep track of when tokens expire for caching.
      NSTimeInterval _tokenExpiration;
    }
    
    - (void)fetchTokenWithContext:(nullable GMTDAuthorizationContext *)authorizationContext
                       completion:(nonnull GMTDAuthTokenFetchCompletionHandler)completion {
      if (!completion) {
        NSAssert(NO, @"%s encountered an unexpected nil completion.", __PRETTY_FUNCTION__);
        return;
      }
    
      // Get the vehicle ID from the authorizationContext. This is set by the Driver SDK.
      NSString *vehicleID = authorizationContext.vehicleID;
      if (!vehicleID) {
        NSAssert(NO, @"Vehicle ID is missing from authorizationContext.");
        return;
      }
    
    // Clear cached vehicle token if vehicle ID has changed.
      if (![_lastKnownVehicleID isEqual:vehicleID]) {
        _tokenExpiration = 0.0;
        _cachedVehicleToken = nil;
      }
      _lastKnownVehicleID = vehicleID;
    
      // Clear cached vehicle token if it has expired.
      if ([[NSDate date] timeIntervalSince1970] > _tokenExpiration) {
        _cachedVehicleToken = nil;
      }
    
      // If appropriate, use the cached token.
      if (_cachedVehicleToken) {
        completion(_cachedVehicleToken, nil);
        return;
      }
      // Otherwise, try to fetch a new token from your server.
      NSURL *requestURL = [NSURL URLWithString:PROVIDER_URL];
      NSMutableURLRequest *request = 
                              [[NSMutableURLRequest alloc] initWithURL:requestURL];
      request.HTTPMethod = @"GET";
      // Replace the following key values with the appropriate keys based on your
      // server's expected response.
      NSString *vehicleTokenKey = @"VEHICLE_TOKEN_KEY";
      NSString *tokenExpirationKey = @"TOKEN_EXPIRATION";
      __weak typeof(self) weakSelf = self;
      void (^handler)(NSData *_Nullable data, NSURLResponse *_Nullable response,
                      NSError *_Nullable error) =
          ^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
            typeof(self) strongSelf = weakSelf;
            if (error) {
              completion(nil, error);
              return;
            }
    
            NSError *JSONError;
            NSMutableDictionary *JSONResponse =
                [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&JSONError];
    
            if (JSONError) {
              completion(nil, JSONError);
              return;
            } else {
              // Sample code only. No validation logic.
              id expirationData = JSONResponse[tokenExpirationKey];
              if ([expirationData isKindOfClass:[NSNumber class]]) {
                NSTimeInterval expirationTime = ((NSNumber *)expirationData).doubleValue;
                strongSelf->_tokenExpiration = [[NSDate date] timeIntervalSince1970] + expirationTime;
              }
              strongSelf->_cachedVehicleToken = JSONResponse[vehicleTokenKey];
              completion(JSONResponse[vehicleTokenKey], nil);
            }
        };
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *mainQueueURLSession =  
           [NSURLSession  sessionWithConfiguration:config delegate:nil
    delegateQueue:[NSOperationQueue mainQueue]];
    NSURLSessionDataTask *task = [mainQueueURLSession dataTaskWithRequest:request completionHandler:handler];
    [task resume];
    }
    
    @end
    

    إنشاء مثيل DeliveryDriverAPI

    للحصول على مثيل GMTDDeliveryVehicleReporter، عليك أولاً إنشاء مثيل واحد (GMTDDeliveryDriverAPI) يستخدم معرِّف الموفّر السيارةID وdriverContext وaccessTokenProvider. معرِّف الموفِّر هو نفسه معرّف مشروع Google Cloud ويمكنك الوصول إلى GMTDDeliveryVehicleReporter. من واجهة برمجة تطبيقات برنامج التشغيل مباشرةً.

    ينشئ المثال التالي مثيل GMTDDeliveryDriverAPI:

    #import “SampleViewController.h”
    #import “SampleAccessTokenProvider.h”
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
    
    @implementation SampleViewController {
     GMSMapView *_mapView;
    }
    
    - (void)viewDidLoad {
      NSString *vehicleID = @"INSERT_CREATED_VEHICLE_ID";
      SampleAccessTokenProvider *accessTokenProvider = 
                                    [[SampleAccessTokenProvider alloc] init];
      GMTDDriverContext *driverContext = 
         [[GMTDDriverContext alloc] initWithAccessTokenProvider:accessTokenProvider
                                                     providerID:PROVIDER_ID 
                                                  vehicleID:vehicleID 
          navigator:_mapView.navigator];
    
      GMTDDeliveryDriverAPI *deliveryDriverAPI = [[GMTDDeliveryDriverAPI alloc] initWithDriverContext:driverContext];
    }
    

    الاستماع بشكل اختياري إلى أحداث AutomotiveReporter

    تُحدِّث "GMTDDeliveryVehicleReporter" المركبة بشكل دوري في حال locationTrackingEnabled هي نعم. للاستجابة لهذه التحديثات الدورية، سيتم يمكن للعنصر الاشتراك في أحداث GMTDDeliveryVehicleReporter من خلال التوافق مع بروتوكول GMTDVehicleReporterListener.

    يمكنك التعامل مع الأحداث التالية:

    • vehicleReporter:didSucceedVehicleUpdate

      تُبلغ تطبيق Driver بأنّ خدمات الخلفية قد تلقّت بنجاح وتحديث موقع المركبة والحالة.

    • vehicleReporter:didFailVehicleUpdate:withError

      تُبلِغ المستمِع بتعذُّر تحديث المركبة. طالما أن الموقع تفعيل التتبُّع، وسيستمر GMTDDeliveryVehicleReporter في إرسال أحدث البيانات في خلفية Fleet Engine.

    يتعامل المثال التالي مع هذه الأحداث:

    SampleViewController.h
    @interface SampleViewController : UIViewController<GMTDVehicleReporterListener>
    @end
    
    SampleViewController.m
    #import “SampleViewController.h”
    #import “SampleAccessTokenProvider.h”
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
    
    @implementation SampleViewController {
     GMSMapView *_mapView;
    }
    
    
    - (void)viewDidLoad {
      // ASSUMES YOU IMPLEMENTED HAVE THE SAMPLE CODE UP TO THIS STEP.
      [ridesharingDriverAPI.vehicleReporter addListener:self];
    }
    
    - (void)vehicleReporter:(GMTDDeliveryVehicleReporter *)vehicleReporter didSucceedVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate {
      // Handle update succeeded.
    }
    
    - (void)vehicleReporter:(GMTDDeliveryVehicleReporter *)vehicleReporter didFailVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate withError:(NSError *)error {
      // Handle update failed.
    }
    
    @end
    

    تفعيل تتبع الموقع الجغرافي

    لتفعيل ميزة تتبُّع الموقع الجغرافي، يمكن لتطبيقك ضبط locationTrackingEnabled على YES. في GMTDDeliveryVehicleReporter. بعد ذلك، سيكون GMTDDeliveryVehicleReporter = إرسال تحديثات الموقع تلقائيًا. عندما تكون "GMSNavigator" في وضع التنقّل وضع (عند تحديد وجهة من خلال setDestinations) تم ضبط locationTrackingEnabled على YES، وسيتضمن GMTDDeliveryVehicleReporter إرسال تحديثات المسار والوقت المقدر للوصول تلقائيًا أيضًا.

    سيكون المسار الذي تم ضبطه أثناء هذه التحديثات هو المسار نفسه الذي يسلكه السائق. الانتقال إليه أثناء جلسة التنقل. وبالتالي، لكي تعمل ميزة تتبُّع الشحن بشكل صحيح، يجب أن تتطابق النقطة الوسيطة المحددة من خلال -setDestinations:callback: مع البيانات المحددة في خلفية Fleet Engine.

    يوضّح المثال التالي ميزة تتبُّع الموقع الجغرافي:

    SampleViewController.m
    #import “SampleViewController.h”
    #import “SampleAccessTokenProvider.h”
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
    
    @implementation SampleViewController {
     GMSMapView *_mapView; 
    }
    
    - (void)viewDidLoad {
      // ASSUMES YOU IMPLEMENTED HAVE THE SAMPLE CODE UP TO THIS STEP.
      deliveryDriverAPI.vehicleReporter.locationTrackingEnabled = YES;
    }
    
    @end
    

    بشكل افتراضي، يبلغ الفاصل الزمني لإعداد التقارير 10 ثوانٍ، ولكن يمكن مع locationUpdateInterval. الحدّ الأدنى للفاصل الزمني المتوافق مع التحديثات 5 ثوانٍ. يبلغ الحدّ الأقصى للفاصل الزمني المسموح به للتحديث 60 ثانية. أكثر تكرارًا قد تؤدي التحديثات إلى إبطاء الطلبات والأخطاء.

    إيقاف تحديثات الموقع الجغرافي

    يمكن لتطبيقك إيقاف تحديثات الموقع الجغرافي لمركبة. على سبيل المثال، عندما تنتهي وردية عمل السائق، يمكن لتطبيقك ضبط locationTrackingEnabled على NO.

      _vehicleReporter.locationTrackingEnabled = NO
    

    التعامل مع أخطاء update_mask

    عندما يرسل "GMTDDeliveryVehicleReporter" معلومات جديدة عن المركبة، update_mask عندما يكون القناع فارغًا، وعادةً ما يحدث ذلك في أول التحديث بعد بدء التشغيل. يوضح المثال التالي كيفية التعامل مع هذا الخطأ:

    Swift

    import GoogleRidesharingDriver
    
    class VehicleReporterListener: NSObject, GMTDVehicleReporterListener {
      func vehicleReporter(
        _ vehicleReporter: GMTDVehicleReporter,
        didFail vehicleUpdate: GMTDVehicleUpdate,
        withError error: Error
      ) {
        let fullError = error as NSError
        if let innerError = fullError.userInfo[NSUnderlyingErrorKey] as? NSError {
          let innerFullError = innerError as NSError
          if innerFullError.localizedDescription.contains("update_mask cannot be empty") {
            emptyMaskUpdates += 1
            return
          }
        }
        failedUpdates += 1
      }
    
      override init() {
        emptyMaskUpdates = 0
        failedUpdates = 0
      }
    }
    
    

    Objective-C

    #import "VehicleReporterListener.h"
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    @implementation VehicleReporterListener {
      NSInteger emptyMaskUpdates = 0;
      NSInteger failedUpdates = 0;
    }
    
    - (void)vehicleReporter:(GMTDVehicleReporter *)vehicleReporter
      didFailVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate
                 withError:(NSError *)error {
      for (NSError *underlyingError in error.underlyingErrors) {
        if ([underlyingError.localizedDescription containsString:@"update_mask cannot be empty"]) {
          emptyMaskUpdates += 1;
          return;
        }
      }
      failedUpdates += 1
    }
    
    @end