Benutzerdefinierte Ereignisse für native Anzeigen

Vorbereitung

Schließen Sie die Einrichtung der benutzerdefinierten Ereignisse ab.

Native Anzeige anfordern

Wenn die Werbebuchung für das benutzerdefinierte Ereignis in der abfolgebasierten Vermittlungskette erreicht wird, wird die Methode loadNativeAd:adConfiguration:completionHandler: für den Klassennamen aufgerufen, den Sie beim Erstellen eines benutzerdefinierten Ereignisses angegeben haben. In diesem Fall befindet sich diese Methode in SampleCustomEvent, die dann die Methode loadNativeAd:adConfiguration:completionHandler: in SampleCustomEventNative aufruft.

Wenn Sie eine native Anzeige anfordern möchten, erstellen oder ändern Sie eine Klasse, die GADMediationAdapter und loadNativeAd:adConfiguration:completionHandler: implementiert. Wenn bereits eine Klasse vorhanden ist, die GADMediationAdapter erweitert, implementieren Sie loadNativeAd:adConfiguration:completionHandler: dort. Erstellen Sie außerdem eine neue Klasse, um GADMediationNativeAd zu implementieren.

In unserem Beispiel für ein benutzerdefiniertes Ereignis implementiert SampleCustomEvent die GADMediationAdapter-Benutzeroberfläche und delegiert dann an SampleCustomEventNative.

Swift

import GoogleMobileAds

class SampleCustomEvent: NSObject, GADMediationAdapter {

  fileprivate var nativeAd: SampleCustomEventNativeAd?

  func loadNativeAd(
    for adConfiguration: GADMediationNativeAdConfiguration,
    completionHandler: @escaping GADMediationNativeAdLoadCompletionHandler
  ) {
    self.nativeAd = SampleCustomEventNativeAd()
    self.nativeAd?.loadNativeAd(
      for: adConfiguration, completionHandler: completionHandler)
  }
}

Objective-C

#import "SampleCustomEvent.h"

@implementation SampleCustomEvent

SampleCustomEventNativeAd *sampleNativeAd;

- (void)loadNativeAdForAdConfiguration:
            (GADMediationNativeAdConfiguration *)adConfiguration
                     completionHandler:
                         (GADMediationNativeAdLoadCompletionHandler)
                             completionHandler {
  sampleNative = [[SampleCustomEventNativeAd alloc] init];
  [sampleNative loadNativeAdForAdConfiguration:adConfiguration
                             completionHandler:completionHandler];
}

„SampleCustomEventNative“ ist für die folgenden Aufgaben verantwortlich:

  • Native Anzeige laden

  • Implementierung des GADMediationNativeAd-Protokolls

  • Callbacks für Anzeigenereignisse an das Google Mobile Ads SDK empfangen und melden

Der in der AdMob-Benutzeroberfläche definierte optionale Parameter ist in der Anzeigenkonfiguration enthalten. Auf den Parameter kann über adConfiguration.credentials.settings[@"parameter"] zugegriffen werden. Dieser Parameter ist in der Regel eine Anzeigenblock-ID, die ein Werbenetzwerk-SDK beim Instanziieren eines Anzeigenobjekts benötigt.

Swift

class SampleCustomEventNativeAd: NSObject, GADMediationNativeAd {
  /// The Sample Ad Network native ad.
  var nativeAd: SampleNativeAd?

  /// The ad event delegate to forward ad rendering events to the Google Mobile
  /// Ads SDK.
  var delegate: GADMediationNativeAdEventDelegate?

  /// Completion handler called after ad load
  var completionHandler: GADMediationNativeLoadCompletionHandler?

  func loadNativeAd(
    for adConfiguration: GADMediationNativeAdConfiguration,
    completionHandler: @escaping GADMediationNativeLoadCompletionHandler
  ) {
    let adLoader = SampleNativeAdLoader()
    let sampleRequest = SampleNativeAdRequest()

    // The Google Mobile Ads SDK requires the image assets to be downloaded
    // automatically unless the publisher specifies otherwise by using the
    // GADNativeAdImageAdLoaderOptions object's disableImageLoading property. If
    // your network doesn't have an option like this and instead only ever
    // returns URLs for images (rather than the images themselves), your adapter
    // should download image assets on behalf of the publisher. This should be
    // done after receiving the native ad object from your network's SDK, and
    // before calling the connector's adapter:didReceiveMediatedNativeAd: method.
    sampleRequest.shouldDownloadImages = true
    sampleRequest.preferredImageOrientation = NativeAdImageOrientation.any
    sampleRequest.shouldRequestMultipleImages = false
    let options = adConfiguration.options
    for loaderOptions: GADAdLoaderOptions in options {
      if let imageOptions = loaderOptions as? GADNativeAdImageAdLoaderOptions {
        sampleRequest.shouldRequestMultipleImages =
          imageOptions.shouldRequestMultipleImages
        // If the GADNativeAdImageAdLoaderOptions' disableImageLoading property is
        // YES, the adapter should send just the URLs for the images.
        sampleRequest.shouldDownloadImages = !imageOptions.disableImageLoading
      } else if let mediaOptions = loaderOptions
        as? GADNativeAdMediaAdLoaderOptions
      {
        switch mediaOptions.mediaAspectRatio {
        case GADMediaAspectRatio.landscape:
          sampleRequest.preferredImageOrientation =
            NativeAdImageOrientation.landscape
        case GADMediaAspectRatio.portrait:
          sampleRequest.preferredImageOrientation =
            NativeAdImageOrientation.portrait
        default:
          sampleRequest.preferredImageOrientation = NativeAdImageOrientation.any
        }
      }
    }
    // This custom event uses the server parameter to carry an ad unit ID, which
    // is the most common use case.
    adLoader.delegate = self
    adLoader.adUnitID =
      adConfiguration.credentials.settings["parameter"] as? String
    self.completionHandler = completionHandler
    adLoader.fetchAd(sampleRequest)
  }
}

Objective-C

#import "SampleCustomEventNativeAd.h"

@interface SampleCustomEventNativeAd () <SampleNativeAdDelegate,
                                         GADMediationNativeAd> {
  /// The sample native ad.
  SampleNativeAd *_nativeAd;

  /// The completion handler to call when the ad loading succeeds or fails.
  GADMediationNativeLoadCompletionHandler _loadCompletionHandler;

  /// The ad event delegate to forward ad rendering events to the Google Mobile
  /// Ads SDK.
  id<GADMediationNativeAdEventDelegate> _adEventDelegate;
}
@end

- (void)loadNativeAdForAdConfiguration:
            (GADMediationNativeAdConfiguration *)adConfiguration
                     completionHandler:(GADMediationNativeLoadCompletionHandler)
                                           completionHandler {
  __block atomic_flag completionHandlerCalled = ATOMIC_FLAG_INIT;
  __block GADMediationNativeLoadCompletionHandler originalCompletionHandler =
      [completionHandler copy];

  _loadCompletionHandler = ^id<GADMediationNativeAdEventDelegate>(
      _Nullable id<GADMediationNativeAd> ad, NSError *_Nullable error) {
    // Only allow completion handler to be called once.
    if (atomic_flag_test_and_set(&completionHandlerCalled)) {
      return nil;
    }

    id<GADMediationNativeAdEventDelegate> delegate = nil;
    if (originalCompletionHandler) {
      // Call original handler and hold on to its return value.
      delegate = originalCompletionHandler(ad, error);
    }

    // Release reference to handler. Objects retained by the handler will also
    // be released.
    originalCompletionHandler = nil;

    return delegate;
  };

  SampleNativeAdLoader *adLoader = [[SampleNativeAdLoader alloc] init];
  SampleNativeAdRequest *sampleRequest = [[SampleNativeAdRequest alloc] init];

  // The Google Mobile Ads SDK requires the image assets to be downloaded
  // automatically unless the publisher specifies otherwise by using the
  // GADNativeAdImageAdLoaderOptions object's disableImageLoading property. If
  // your network doesn't have an option like this and instead only ever returns
  // URLs for images (rather than the images themselves), your adapter should
  // download image assets on behalf of the publisher. This should be done after
  // receiving the native ad object from your network's SDK, and before calling
  // the connector's adapter:didReceiveMediatedNativeAd: method.
  sampleRequest.shouldDownloadImages = YES;

  sampleRequest.preferredImageOrientation = NativeAdImageOrientationAny;
  sampleRequest.shouldRequestMultipleImages = NO;
  sampleRequest.testMode = adConfiguration.isTestRequest;

  for (GADAdLoaderOptions *loaderOptions in adConfiguration.options) {
    if ([loaderOptions isKindOfClass:[GADNativeAdImageAdLoaderOptions class]]) {
      GADNativeAdImageAdLoaderOptions *imageOptions =
          (GADNativeAdImageAdLoaderOptions *)loaderOptions;
      sampleRequest.shouldRequestMultipleImages =
          imageOptions.shouldRequestMultipleImages;

      // If the GADNativeAdImageAdLoaderOptions' disableImageLoading property is
      // YES, the adapter should send just the URLs for the images.
      sampleRequest.shouldDownloadImages = !imageOptions.disableImageLoading;
    } else if ([loaderOptions
                   isKindOfClass:[GADNativeAdMediaAdLoaderOptions class]]) {
      GADNativeAdMediaAdLoaderOptions *mediaOptions =
          (GADNativeAdMediaAdLoaderOptions *)loaderOptions;
      switch (mediaOptions.mediaAspectRatio) {
        case GADMediaAspectRatioLandscape:
          sampleRequest.preferredImageOrientation =
              NativeAdImageOrientationLandscape;
          break;
        case GADMediaAspectRatioPortrait:
          sampleRequest.preferredImageOrientation =
              NativeAdImageOrientationPortrait;
          break;
        default:
          sampleRequest.preferredImageOrientation = NativeAdImageOrientationAny;
          break;
      }
    } else if ([loaderOptions isKindOfClass:[GADNativeAdViewAdOptions class]]) {
      _nativeAdViewAdOptions = (GADNativeAdViewAdOptions *)loaderOptions;
    }
  }

  // This custom event uses the server parameter to carry an ad unit ID, which
  // is the most common use case.
  NSString *adUnit = adConfiguration.credentials.settings[@"parameter"];
  adLoader.adUnitID = adUnit;
  adLoader.delegate = self;

  [adLoader fetchAd:sampleRequest];
}

Unabhängig davon, ob die Anzeige erfolgreich abgerufen wurde oder ein Fehler auftritt, rufen Sie GADMediationNativeAdLoadCompletionHandler auf. Übergeben Sie bei Erfolg die Klasse, die GADMediationNativeAd implementiert, mit dem Wert nil für den Fehlerparameter. Geben Sie bei einem Fehler den aufgetretenen Fehler an.

Normalerweise werden diese Methoden in Callbacks aus dem SDK des Drittanbieters implementiert, das Sie in Ihrem Adapter verwenden. In diesem Beispiel hat das Beispiel-SDK eine SampleNativeAdDelegate mit relevanten Callbacks:

Swift

func adLoader(
  _ adLoader: SampleNativeAdLoader, didReceive nativeAd: SampleNativeAd
) {
  extraAssets = [
    SampleCustomEventConstantsSwift.awesomenessKey: nativeAd.degreeOfAwesomeness
      ?? ""
  ]

  if let image = nativeAd.image {
    images = [GADNativeAdImage(image: image)]
  } else {
    let imageUrl = URL(fileURLWithPath: nativeAd.imageURL)
    images = [GADNativeAdImage(url: imageUrl, scale: nativeAd.imageScale)]
  }
  if let mappedIcon = nativeAd.icon {
    icon = GADNativeAdImage(image: mappedIcon)
  } else {
    let iconURL = URL(fileURLWithPath: nativeAd.iconURL)
    icon = GADNativeAdImage(url: iconURL, scale: nativeAd.iconScale)
  }

  adChoicesView = SampleAdInfoView()
  self.nativeAd = nativeAd
  if let handler = completionHandler {
    delegate = handler(self, nil)
  }
}

func adLoader(
  _ adLoader: SampleNativeAdLoader,
  didFailToLoadAdWith errorCode: SampleErrorCode
) {
  let error =
    SampleCustomEventUtilsSwift.SampleCustomEventErrorWithCodeAndDescription(
      code: SampleCustomEventErrorCodeSwift
        .SampleCustomEventErrorAdLoadFailureCallback,
      description:
        "Sample SDK returned an ad load failure callback with error code: \(errorCode)"
    )
  if let handler = completionHandler {
    delegate = handler(nil, error)
  }
}

Objective-C

- (void)adLoader:(SampleNativeAdLoader *)adLoader
    didReceiveNativeAd:(SampleNativeAd *)nativeAd {
  if (nativeAd.image) {
    _images = @[ [[GADNativeAdImage alloc] initWithImage:nativeAd.image] ];
  } else {
    NSURL *imageURL = [[NSURL alloc] initFileURLWithPath:nativeAd.imageURL];
    _images = @[ [[GADNativeAdImage alloc] initWithURL:imageURL
                                                 scale:nativeAd.imageScale] ];
  }

  if (nativeAd.icon) {
    _icon = [[GADNativeAdImage alloc] initWithImage:nativeAd.icon];
  } else {
    NSURL *iconURL = [[NSURL alloc] initFileURLWithPath:nativeAd.iconURL];
    _icon = [[GADNativeAdImage alloc] initWithURL:iconURL
                                            scale:nativeAd.iconScale];
  }

  // The sample SDK provides an AdChoices view (SampleAdInfoView). If your SDK
  // provides image and click through URLs for its AdChoices icon instead of an
  // actual UIView, the adapter is responsible for downloading the icon image
  // and creating the AdChoices icon view.
  _adChoicesView = [[SampleAdInfoView alloc] init];
  _nativeAd = nativeAd;

  _adEventDelegate = _loadCompletionHandler(self, nil);
}

- (void)adLoader:(SampleNativeAdLoader *)adLoader
    didFailToLoadAdWithErrorCode:(SampleErrorCode)errorCode {
  NSError *error = SampleCustomEventErrorWithCodeAndDescription(
      SampleCustomEventErrorAdLoadFailureCallback,
      [NSString stringWithFormat:@"Sample SDK returned an ad load failure "
                                 @"callback with error code: %@",
                                 errorCode]);
  _adEventDelegate = _loadCompletionHandler(nil, error);
}

Native Anzeigen auf Google Maps

Verschiedene SDKs haben unterschiedliche Formate für native Anzeigen. Eine Funktion kann beispielsweise Objekte zurückgeben, die ein Feld „Titel“ enthalten, während eine andere Funktion „Überschrift“ hat. Außerdem können die Methoden zum Erfassen von Impressionen und zum Verarbeiten von Klicks je nach SDK variieren.

Um diese Probleme zu beheben, sollte bei einem benutzerdefinierten Ereignis, das ein natives Anzeigenobjekt aus dem vermittelten SDK empfängt, eine Klasse verwendet werden, die GADMediationNativeAd implementiert, z. B. SampleCustomEventNativeAd, um das native Anzeigenobjekt des vermittelten SDKs so zuzuordnen, dass es der vom Google Mobile Ads SDK erwarteten Schnittstelle entspricht.

Sehen wir uns nun die Implementierungsdetails für SampleCustomEventNativeAd genauer an.

Zuordnungen speichern

Für GADMediationNativeAd werden bestimmte Properties implementiert, die den Properties anderer SDKs zugeordnet werden:

Swift

var nativeAd: SampleNativeAd?

var headline: String? {
  return nativeAd?.headline
}

var images: [GADNativeAdImage]?

var body: String? {
  return nativeAd?.body
}

var icon: GADNativeAdImage?

var callToAction: String? {
  return nativeAd?.callToAction
}

var starRating: NSDecimalNumber? {
  return nativeAd?.starRating
}

var store: String? {
  return nativeAd?.store
}

var price: String? {
  return nativeAd?.price
}

var advertiser: String? {
  return nativeAd?.advertiser
}

var extraAssets: [String: Any]? {
  return [
    SampleCustomEventConstantsSwift.awesomenessKey:
      nativeAd?.degreeOfAwesomeness
      ?? ""
  ]
}

var adChoicesView: UIView?

var mediaView: UIView? {
  return nativeAd?.mediaView
}

Objective-C

/// Used to store the ad's images. In order to implement the
/// GADMediationNativeAd protocol, we use this class to return the images
/// property.
NSArray<GADNativeAdImage *> *_images;

/// Used to store the ad's icon. In order to implement the GADMediationNativeAd
/// protocol, we use this class to return the icon property.
GADNativeAdImage *_icon;

/// Used to store the ad's ad choices view. In order to implement the
/// GADMediationNativeAd protocol, we use this class to return the adChoicesView
/// property.
UIView *_adChoicesView;

- (nullable NSString *)headline {
  return _nativeAd.headline;
}

- (nullable NSArray<GADNativeAdImage *> *)images {
  return _images;
}

- (nullable NSString *)body {
  return _nativeAd.body;
}

- (nullable GADNativeAdImage *)icon {
  return _icon;
}

- (nullable NSString *)callToAction {
  return _nativeAd.callToAction;
}

- (nullable NSDecimalNumber *)starRating {
  return _nativeAd.starRating;
}

- (nullable NSString *)store {
  return _nativeAd.store;
}

- (nullable NSString *)price {
  return _nativeAd.price;
}

- (nullable NSString *)advertiser {
  return _nativeAd.advertiser;
}

- (nullable NSDictionary<NSString *, id> *)extraAssets {
  return
      @{SampleCustomEventExtraKeyAwesomeness : _nativeAd.degreeOfAwesomeness};
}

- (nullable UIView *)adChoicesView {
  return _adChoicesView;
}

- (nullable UIView *)mediaView {
  return _nativeAd.mediaView;
}

- (BOOL)hasVideoContent {
  return self.mediaView != nil;
}

Einige vermittelte Netzwerke können zusätzliche Assets bereitstellen, die über die vom Google Mobile Ads SDK definierten hinausgehen. Das GADMediationNativeAd-Protokoll enthält eine Methode namens extraAssets, mit der das Google Mobile Ads SDK diese zusätzlichen Assets von Ihrem Mapper abrufen kann.

Bild-Assets zuordnen

Die Zuordnung von Bild-Assets ist komplizierter als die von einfacheren Datentypen wie NSString oder double. Bilder können automatisch heruntergeladen oder als URL-Werte zurückgegeben werden. Auch die Pixeldichte kann variieren.

Zur Verwaltung dieser Details bietet das Google Mobile Ads SDK die Klasse GADNativeAdImage. Informationen zu Bild-Assets (ob es sich um tatsächliche UIImage-Objekte oder nur um NSURL-Werte handelt) sollten mithilfe dieser Klasse an das Google Mobile Ads SDK zurückgegeben werden.

So erstellt die Mapper-Klasse ein GADNativeAdImage für das Symbolbild:

Swift

if let image = nativeAd.image {
  images = [GADNativeAdImage(image: image)]
} else {
  let imageUrl = URL(fileURLWithPath: nativeAd.imageURL)
  images = [GADNativeAdImage(url: imageUrl, scale: nativeAd.imageScale)]
}

Objective-C

if (nativeAd.image) {
  _images = @[ [[GADNativeAdImage alloc] initWithImage:nativeAd.image] ];
} else {
  NSURL *imageURL = [[NSURL alloc] initFileURLWithPath:nativeAd.imageURL];
  _images = @[ [[GADNativeAdImage alloc] initWithURL:imageURL
                                               scale:nativeAd.imageScale] ];
}

Impressions- und Klickereignisse

Sowohl das Google Mobile Ads SDK als auch das vermittelte SDK müssen wissen, wann eine Impression oder ein Klick erfolgt. Diese Ereignisse müssen jedoch nur von einem SDK erfasst werden. Es gibt zwei verschiedene Ansätze für benutzerdefinierte Ereignisse, je nachdem, ob das vermittelte SDK das Tracking von Impressionen und Klicks unterstützt.

Klicks und Impressionen mit dem Google Mobile Ads SDK erfassen

Wenn das vermittelte SDK kein eigenes Impressions- und Klick-Tracking durchführt, aber Methoden zum Erfassen von Klicks und Impressionen bietet, kann das Google Mobile Ads SDK diese Ereignisse erfassen und den Adapter benachrichtigen. Das GADMediationNativeAd-Protokoll umfasst zwei Methoden: didRecordImpression: und didRecordClickOnAssetWithName:view:viewController:. Sie können in benutzerdefinierten Ereignissen implementiert werden, um die entsprechende Methode auf dem vermittelten nativen Anzeigenobjekt aufzurufen:

Swift

func didRecordImpression() {
  nativeAd?.recordImpression()
}

func didRecordClickOnAsset(
  withName assetName: GADUnifiedNativeAssetIdentifier,
  view: UIView,
  wController: UIViewController
) {
  nativeAd?.handleClick(on: view)
}

Objective-C

- (void)didRecordImpression {
  if (self.nativeAd) {
    [self.nativeAd recordImpression];
  }
}

- (void)didRecordClickOnAssetWithName:(GADUnifiedNativeAssetIdentifier)assetName
                                 view:(UIView *)view
                       viewController:(UIViewController *)viewController {
  if (self.nativeAd) {
    [self.nativeAd handleClickOnView:view];
  }
}

Da die Klasse, die das GADMediationNativeAd-Protokoll implementiert, eine Referenz auf das native Anzeigenobjekt des Sample SDK enthält, kann sie die entsprechende Methode auf diesem Objekt aufrufen, um einen Klick oder eine Impression zu melden. Die Methode didRecordClickOnAssetWithName:view:viewController: nimmt einen einzelnen Parameter an: das View-Objekt, das dem nativen Anzeigen-Asset entspricht, auf das der Klick erfolgte.

Klicks und Impressionen mit dem vermittelten SDK erfassen

Bei einigen vermittelten SDKs werden Klicks und Impressionen möglicherweise lieber selbst erfasst. In diesem Fall sollten Sie die Methoden handlesUserClicks und handlesUserImpressions wie im folgenden Snippet implementieren. Wenn Sie YES zurückgeben, geben Sie an, dass das benutzerdefinierte Ereignis für das Tracking dieser Ereignisse verantwortlich ist und das Google Mobile Ads SDK benachrichtigt, wenn diese Ereignisse auftreten.

Bei benutzerdefinierten Ereignissen, die das Klick- und Impressions-Tracking überschreiben, kann die Ansicht der nativen Anzeige über die didRenderInView:-Nachricht an das native Anzeigenobjekt des vermittelten SDKs übergeben werden, damit das vermittelte SDK das tatsächliche Tracking durchführen kann. Im Beispiel-SDK aus unserem Beispielprojekt für benutzerdefinierte Ereignisse (aus dem die Code-Snippets in diesem Leitfaden stammen) wird dieser Ansatz nicht verwendet. Würde er verwendet, würde der Code für benutzerdefinierte Ereignisse die Methode setNativeAdView:view: aufrufen, wie im folgenden Snippet gezeigt:

Swift

func handlesUserClicks() -> Bool {
  return true
}
func handlesUserImpressions() -> Bool {
  return true
}

func didRender(
  in view: UIView, clickableAssetViews: [GADNativeAssetIdentifier: UIView],
  nonclickableAssetViews: [GADNativeAssetIdentifier: UIView],
  viewController: UIViewController
) {
  // This method is called when the native ad view is rendered. Here you would pass the UIView
  // back to the mediated network's SDK.
  self.nativeAd?.setNativeAdView(view)
}

Objective-C

- (BOOL)handlesUserClicks {
  return YES;
}

- (BOOL)handlesUserImpressions {
  return YES;
}

- (void)didRenderInView:(UIView *)view
       clickableAssetViews:(NSDictionary<GADNativeAssetIdentifier, UIView *> *)
                               clickableAssetViews
    nonclickableAssetViews:(NSDictionary<GADNativeAssetIdentifier, UIView *> *)
                               nonclickableAssetViews
            viewController:(UIViewController *)viewController {
  // This method is called when the native ad view is rendered. Here you would
  // pass the UIView back to the mediated network's SDK. Playing video using
  // SampleNativeAd's playVideo method
  [_nativeAd setNativeAdView:view];
}

Vermittlungsereignisse an das Google Mobile Ads SDK weiterleiten

Nachdem Sie GADMediationNativeLoadCompletionHandler mit einer geladenen Anzeige aufgerufen haben, kann der Adapter das zurückgegebene GADMediationNativeAdEventDelegate-Delegierungsobjekt verwenden, um Präsentationsereignisse vom SDK des Drittanbieters an das Google Mobile Ads SDK weiterzuleiten.

Es ist wichtig, dass Ihr benutzerdefiniertes Ereignis so viele dieser Rückrufe wie möglich weiterleitet, damit Ihre App diese entsprechenden Ereignisse vom Google Mobile Ads SDK empfängt. Hier ein Beispiel für die Verwendung von Callbacks:

Damit ist die Implementierung benutzerdefinierter Ereignisse für native Anzeigen abgeschlossen. Das vollständige Beispiel finden Sie auf GitHub.