From c7d543d801bbb2bd511a46838a63014e48400391 Mon Sep 17 00:00:00 2001 From: SotoiGhost Date: Thu, 27 Jun 2019 13:57:45 -0500 Subject: [PATCH] Moved docs to new location --- docs/Firebase/AdMob/Details.md | 22 + docs/Firebase/AdMob/GettingStarted.md | 2230 +++++++++++++++++ docs/Firebase/Analytics/Details.md | 20 + docs/Firebase/Analytics/GettingStarted.md | 308 +++ docs/Firebase/Auth/Details.md | 26 + docs/Firebase/Auth/GettingStarted.md | 1982 +++++++++++++++ docs/Firebase/CloudFirestore/Details.md | 27 + .../Firebase/CloudFirestore/GettingStarted.md | 1560 ++++++++++++ docs/Firebase/CloudMessaging/Details.md | 17 + .../Firebase/CloudMessaging/GettingStarted.md | 597 +++++ docs/Firebase/CrashReporting/Details.md | 39 + .../Firebase/CrashReporting/GettingStarted.md | 157 ++ docs/Firebase/Crashlytics/Details.md | 16 + docs/Firebase/Crashlytics/GettingStarted.md | 356 +++ docs/Firebase/Database/Details.md | 23 + docs/Firebase/Database/GettingStarted.md | 734 ++++++ docs/Firebase/DynamicLinks/Details.md | 20 + docs/Firebase/DynamicLinks/GettingStarted.md | 281 +++ docs/Firebase/Invites/Details.md | 39 + docs/Firebase/Invites/GettingStarted.md | 167 ++ .../MLKit.ModelInterpreter/Details.md | 19 + .../MLKit.ModelInterpreter/GettingStarted.md | 268 ++ docs/Firebase/MLKit/Details.md | 31 + docs/Firebase/MLKit/GettingStarted.md | 1297 ++++++++++ .../Firebase/PerformanceMonitoring/Details.md | 47 + .../PerformanceMonitoring/GettingStarted.md | 323 +++ docs/Firebase/RemoteConfig/Details.md | 31 + docs/Firebase/RemoteConfig/GettingStarted.md | 190 ++ docs/Firebase/Storage/Details.md | 27 + docs/Firebase/Storage/GettingStarted.md | 810 ++++++ docs/Google/Analytics/Details.md | 7 + docs/Google/Analytics/GettingStarted.md | 97 + docs/Google/AppIndexing/Details.md | 23 + docs/Google/AppIndexing/GettingStarted.md | 114 + docs/Google/AppInvite/Details.md | 27 + docs/Google/AppInvite/GettingStarted.md | 113 + docs/Google/Cast/Details.md | 11 + docs/Google/Cast/GettingStarted.md | 708 ++++++ docs/Google/GoogleCloudMessaging/Details.md | 14 + .../GoogleCloudMessaging/GettingStarted.md | 204 ++ docs/Google/InstanceID/Details.md | 32 + docs/Google/InstanceID/GettingStarted.md | 29 + docs/Google/Maps/Details.md | 62 + docs/Google/Maps/GettingStarted.md | 112 + docs/Google/MobileAds/Details.md | 12 + docs/Google/MobileAds/GettingStarted.md | 71 + docs/Google/Places/Details.md | 12 + docs/Google/Places/GettingStarted.md | 1093 ++++++++ docs/Google/PlayGames/Details.md | 3 + docs/Google/PlayGames/GettingStarted.md | 239 ++ docs/Google/SignIn/Details.md | 4 + docs/Google/SignIn/GettingStarted.md | 143 ++ docs/Google/TagManager/Details.md | 16 + docs/Google/TagManager/GettingStarted.md | 168 ++ 54 files changed, 14978 insertions(+) create mode 100755 docs/Firebase/AdMob/Details.md create mode 100755 docs/Firebase/AdMob/GettingStarted.md create mode 100755 docs/Firebase/Analytics/Details.md create mode 100755 docs/Firebase/Analytics/GettingStarted.md create mode 100755 docs/Firebase/Auth/Details.md create mode 100755 docs/Firebase/Auth/GettingStarted.md create mode 100755 docs/Firebase/CloudFirestore/Details.md create mode 100755 docs/Firebase/CloudFirestore/GettingStarted.md create mode 100755 docs/Firebase/CloudMessaging/Details.md create mode 100755 docs/Firebase/CloudMessaging/GettingStarted.md create mode 100755 docs/Firebase/CrashReporting/Details.md create mode 100755 docs/Firebase/CrashReporting/GettingStarted.md create mode 100755 docs/Firebase/Crashlytics/Details.md create mode 100755 docs/Firebase/Crashlytics/GettingStarted.md create mode 100755 docs/Firebase/Database/Details.md create mode 100755 docs/Firebase/Database/GettingStarted.md create mode 100755 docs/Firebase/DynamicLinks/Details.md create mode 100755 docs/Firebase/DynamicLinks/GettingStarted.md create mode 100755 docs/Firebase/Invites/Details.md create mode 100755 docs/Firebase/Invites/GettingStarted.md create mode 100755 docs/Firebase/MLKit.ModelInterpreter/Details.md create mode 100755 docs/Firebase/MLKit.ModelInterpreter/GettingStarted.md create mode 100755 docs/Firebase/MLKit/Details.md create mode 100755 docs/Firebase/MLKit/GettingStarted.md create mode 100755 docs/Firebase/PerformanceMonitoring/Details.md create mode 100755 docs/Firebase/PerformanceMonitoring/GettingStarted.md create mode 100755 docs/Firebase/RemoteConfig/Details.md create mode 100755 docs/Firebase/RemoteConfig/GettingStarted.md create mode 100755 docs/Firebase/Storage/Details.md create mode 100755 docs/Firebase/Storage/GettingStarted.md create mode 100755 docs/Google/Analytics/Details.md create mode 100755 docs/Google/Analytics/GettingStarted.md create mode 100755 docs/Google/AppIndexing/Details.md create mode 100755 docs/Google/AppIndexing/GettingStarted.md create mode 100755 docs/Google/AppInvite/Details.md create mode 100755 docs/Google/AppInvite/GettingStarted.md create mode 100755 docs/Google/Cast/Details.md create mode 100755 docs/Google/Cast/GettingStarted.md create mode 100755 docs/Google/GoogleCloudMessaging/Details.md create mode 100755 docs/Google/GoogleCloudMessaging/GettingStarted.md create mode 100755 docs/Google/InstanceID/Details.md create mode 100755 docs/Google/InstanceID/GettingStarted.md create mode 100755 docs/Google/Maps/Details.md create mode 100755 docs/Google/Maps/GettingStarted.md create mode 100755 docs/Google/MobileAds/Details.md create mode 100755 docs/Google/MobileAds/GettingStarted.md create mode 100755 docs/Google/Places/Details.md create mode 100755 docs/Google/Places/GettingStarted.md create mode 100755 docs/Google/PlayGames/Details.md create mode 100755 docs/Google/PlayGames/GettingStarted.md create mode 100755 docs/Google/SignIn/Details.md create mode 100755 docs/Google/SignIn/GettingStarted.md create mode 100755 docs/Google/TagManager/Details.md create mode 100755 docs/Google/TagManager/GettingStarted.md diff --git a/docs/Firebase/AdMob/Details.md b/docs/Firebase/AdMob/Details.md new file mode 100755 index 00000000..33e7f12c --- /dev/null +++ b/docs/Firebase/AdMob/Details.md @@ -0,0 +1,22 @@ +AdMob by Google is an easy way to monetize mobile apps with targeted, in-app advertising. + +AdMob by Google is a mobile advertising platform that you can use to generate revenue from your app. Using AdMob with Firebase Analytics provides you with additional app usage data and analytics capabilities. + +## Key capabilities + +| | | +|:-:|-| +| **Earn more from AdMob's in-app ads** | Show ads from millions of Google advertisers in real time, or use AdMob Mediation to earn from over 40 premium networks through the AdMob platform to simplify your ad operations, improve competition, and earn more, for free. AdMob mediation has [ad network optimization](https://support.google.com/admob/answer/3379794) built in, which automatically adjusts the positions of your other ad networks in your mediation stack to ensure you maximize your revenue. | +| **Improve user experience** | Native and video ads create a positive user experience as you monetize by matching the look and feel of your app. Choose from different ad templates, customize them, and experiment with different layouts on the fly without republishing your app. | +| **Scale fast** | When your app's a global or domestic hit, you can monetize users quickly with AdMob, by showing ads to users in more than 200 markets. More than one app? AdMob house ads is a free tool that enables you to cross-promote your apps to your userbase, across your family of apps. | +| **Access monetization reports** | AdMob is the premier monetization platform for mobile. While generating ad revenue, AdMob also produces its own monetization reports that you can use to make smarter decisions about product strategy. | + +## How does it work? + +AdMob by Google helps you monetize your mobile app through in-app advertising. Ads can be displayed as banner ads, interstitial ads, video ads, or native ads — which are seamlessly added to platform native UI components. On Android, you can additionally display in-app purchase ads, which allow users to purchase advertised products from within your app. + +Before you can display ads within your app, you'll need to create an AdMob account and activate one or more Ad Unit IDs. This is a unique identifier for the places in your app where ads are displayed. If you are already using AdMob in your app, all of your existing Ad Unit IDs continue to work after you've added Firebase to your app. + +AdMob uses the Google Mobile Ads SDK. The Google Mobile Ads SDK helps app developers gain insights about their users, drive more in-app purchases, and maximize ad revenue. In order to do so, the default integration of the Mobile Ads SDK collects information such as device information, publisher-provided location information, and general in-app purchase information such as item purchase price and currency. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/admob/) to see original Firebase documentation._ \ No newline at end of file diff --git a/docs/Firebase/AdMob/GettingStarted.md b/docs/Firebase/AdMob/GettingStarted.md new file mode 100755 index 00000000..4ce619ab --- /dev/null +++ b/docs/Firebase/AdMob/GettingStarted.md @@ -0,0 +1,2230 @@ +# Table of content + +- [Table of content](#table-of-content) +- [Get Started](#get-started) + - [Use Google Mobile Ads SDK without Firebase](#use-google-mobile-ads-sdk-without-firebase) + - [Add Firebase to your app](#add-firebase-to-your-app) + - [Configure AdMob in your app](#configure-admob-in-your-app) + - [Initialize the Google Mobile Ads SDK](#initialize-the-google-mobile-ads-sdk) + - [Always test with test ads](#always-test-with-test-ads) +- [Banner Ads](#banner-ads) + - [Create a BannerView](#create-a-bannerview) + - [Interface Builder](#interface-builder) + - [Programmatically](#programmatically) + - [Configure BannerView properties](#configure-bannerview-properties) + - [Call LoadRequest method](#call-loadrequest-method) + - [Ad events](#ad-events) + - [Registering for banner events using IBannerViewDelegate interface](#registering-for-banner-events-using-ibannerviewdelegate-interface) + - [Implementing banner events](#implementing-banner-events) + - [Use cases](#use-cases) + - [Adding a banner to the view hierarchy once an ad is received](#adding-a-banner-to-the-view-hierarchy-once-an-ad-is-received) + - [Animating a banner ad](#animating-a-banner-ad) + - [Pausing and resuming the app](#pausing-and-resuming-the-app) + - [Banner sizes](#banner-sizes) + - [Smart Banners](#smart-banners) +- [Interstitial Ads](#interstitial-ads) + - [Create a Interstitial object](#create-a-interstitial-object) + - [Load an ad](#load-an-ad) + - [Show the ad](#show-the-ad) + - [Use IInterstitialDelegate interface or Interstitial events to reload](#use-iinterstitialdelegate-interface-or-interstitial-events-to-reload) + - [Ad events](#ad-events) + - [Registering for interstitial events using IInterstitialDelegate interface](#registering-for-interstitial-events-using-iinterstitialdelegate-interface) + - [Implementing interstitial events](#implementing-interstitial-events) + - [Some best practices](#some-best-practices) + - [Consider whether interstitial ads are the right type of ad for your app.](#consider-whether-interstitial-ads-are-the-right-type-of-ad-for-your-app) + - [Remember to pause the action when displaying an interstitial ad.](#remember-to-pause-the-action-when-displaying-an-interstitial-ad) + - [Allow for adequate loading time.](#allow-for-adequate-loading-time) + - [Don't flood the user with ads.](#dont-flood-the-user-with-ads) + - [Don't use the `InterstitialDelegate.DidReceiveAd` or `Interstitial.AdReceived` events to show the interstitial.](#dont-use-the-interstitialdelegatedidreceivead-or-interstitialadreceived-events-to-show-the-interstitial) +- [Native Ads Advanced (Unified)](#native-ads-advanced-unified) + - [Loading ads](#loading-ads) + - [Initialize the ad loader](#initialize-the-ad-loader) + - [Implement the ad loader delegate](#implement-the-ad-loader-delegate) + - [Request the ad](#request-the-ad) + - [When to request ads](#when-to-request-ads) + - [Determining when loading has finished](#determining-when-loading-has-finished) + - [Handling failed requests](#handling-failed-requests) + - [Get notified of native ad events](#get-notified-of-native-ad-events) + - [Native ad options](#native-ad-options) + - [`NativeAdViewOptions`](#nativeadviewoptions) + - [`VideoOptions`](#videooptions) + - [`MultipleAdsAdLoaderOptions`](#multipleadsadloaderoptions) + - [Displaying a system-defined native ad format](#displaying-a-system-defined-native-ad-format) + - [UnifiedNativeAdView](#unifiednativeadview) + - [The AdChoices overlay](#the-adchoices-overlay) + - [Ad attribution](#ad-attribution) + - [Native video](#native-video) + - [MediaView](#mediaview) + - [VideoController](#videocontroller) +- [Native Ads Advanced](#native-ads-advanced) + - [Loading ads](#loading-ads) + - [Initialize a AdLoader](#initialize-a-adloader) + - [Implement the ad loader delegate](#implement-the-ad-loader-delegate) + - [Request the ad](#request-the-ad) + - [When to request ads](#when-to-request-ads) + - [Determining when loading has finished](#determining-when-loading-has-finished) + - [Handling failed requests](#handling-failed-requests) + - [Get notified of native ad events](#get-notified-of-native-ad-events) + - [Native ad options](#native-ad-options) + - [`NativeAdViewOptions`](#nativeadviewoptions) + - [`VideoOptions`](#videooptions) + - [`MultipleAdsAdLoaderOptions`](#multipleadsadloaderoptions) + - [Displaying a system-defined native ad format](#displaying-a-system-defined-native-ad-format) + - [The ad view classes](#the-ad-view-classes) + - [The AdChoices overlay](#the-adchoices-overlay) + - [Ad attribution](#ad-attribution) + - [Native video](#native-video) + - [MediaView](#mediaview) + - [VideoController](#videocontroller) +- [Rewarded Video Ads](#rewarded-video-ads) + - [Request rewarded video](#request-rewarded-video) + - [Set up event notifications](#set-up-event-notifications) + - [Display rewarded video](#display-rewarded-video) + - [Reload a rewarded video](#reload-a-rewarded-video) +- [Targeting](#targeting) + - [Request](#request) + - [Location](#location) + - [Child-directed setting](#child-directed-setting) + - [Users under the age of consent](#users-under-the-age-of-consent) + - [Ad content filtering](#ad-content-filtering) + - [Content URL](#content-url) + - [Load an ad with targeting](#load-an-ad-with-targeting) + - [FAQ](#faq) +- [Reporting](#reporting) +- [Global Settings](#global-settings) + - [Video ad volume control](#video-ad-volume-control) + - [Changing audio session](#changing-audio-session) + - [In-app purchase reporting](#in-app-purchase-reporting) + - [Crash reporting](#crash-reporting) +- [Requesting Consent from European Users](#requesting-consent-from-european-users) + - [Tips for using the Consent SDK](#tips-for-using-the-consent-sdk) + - [If you select 12 or fewer ad technology providers and don't use mediation](#if-you-select-12-or-fewer-ad-technology-providers-and-dont-use-mediation) + - [If you select more than 12 ad technology providers and don't use mediation](#if-you-select-more-than-12-ad-technology-providers-and-dont-use-mediation) + - [If you use AdMob mediation](#if-you-use-admob-mediation) + - [Update consent status](#update-consent-status) + - [Collect consent](#collect-consent) + - [Google-rendered consent form](#google-rendered-consent-form) + - [Load consent form](#load-consent-form) + - [Show consent form](#show-consent-form) + - [Publisher-managed consent collection](#publisher-managed-consent-collection) + - [Storing publisher managed consent](#storing-publisher-managed-consent) + - [Change or revoke consent](#change-or-revoke-consent) + - [Users under the age of consent](#users-under-the-age-of-consent) + - [Testing](#testing) + - [Forward consent to the Google Mobile Ads SDK](#forward-consent-to-the-google-mobile-ads-sdk) + - [FAQ](#faq) + +# Get Started + +AdMob uses the Google Mobile Ads SDK. The Google Mobile Ads SDK helps app developers gain insights about their users, drives more in-app purchases, and maximizes ad revenue. To do so, the default integration of the Mobile Ads SDK collects information such as device information, publisher-provided location information, and general in-app purchase information (such as item purchase price and currency). + +> ![note_icon] **_Note: The Mobile Ads SDK does not collect payment card information._** + +## Use Google Mobile Ads SDK without Firebase + +If you don't plan to include Firebase in your app, you can use Google Mobile Ads SDK as standalone. To do so, just install the `Xamarin.Google.iOS.MobileAds` NuGet and jump to [Initialize the Google Mobile Ads SDK section](#Initialize-the-Google-Mobile-Ads-SDK) directly. + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][1], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][2]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][2] again at any time. +5. In [Firebase console][1], go to AdMob section and link your recently created app to AdMob. + +## Configure AdMob in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Xamarin Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Open `GoogleService-Info.plist` file and change `IS_ADS_ENABLED` value to `Yes`. +4. Add the following line of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` namespace): + +```csharp +App.Configure (); +``` + +## Initialize the Google Mobile Ads SDK + +At app launch, initialize the Google Mobile Ads SDK by calling `Configure` method: + +```csharp +// Get your Application Id here: https://apps.admob.com/#account/appmgmt: +MobileAds.Configure ("ca-app-pub-XXXXXXXXXXXXXXXX~NNNNNNNNNN"); +``` + +Initializing the Google Mobile Ads SDK at app launch allows the SDK to fetch app-level settings and perform configuration tasks as early as possible. This can help reduce latency for the initial ad request. Initialization requires an app ID. App IDs are unique identifiers given to mobile apps when they're registered in the AdMob console. + +To find your app ID, click the [App management][3] option under the settings dropdown (located in the upper right hand corner) on the AdMob account page. App IDs have the form **ca-app-pub-XXXXXXXXXXXXXXXX~NNNNNNNNNN**. + +### Always test with test ads + +The sample code in these guides contains an ad unit ID and you're free to request ads with it. It's been specially configured to return test ads rather than production ads for every request, which makes it safe to use. + +However, once you register an app in the AdMob UI and create your own ad unit IDs for use in your app, you'll need to explicitly configure your device as a test device when you're developing. **This is extremely important**. Testing with real ads (even if you never tap on them) is against AdMob policy and can cause your account to be suspended. See Test Ads for information on how you can make sure you always get test ads when developing. + +--- + +# Banner Ads + +Banner ads are rectangular image or text ads that occupy a spot within an app's layout. They stay on screen while users are interacting with the app, and can refresh automatically after a certain period of time. If you're new to mobile advertising, they're a great place to start. + +This guide shows you how to integrate banner ads from AdMob into an iOS app. In addition to code snippets and instructions, it includes information about sizing banners properly and links to additional resources. + +## Create a BannerView + +AdMob banners are displayed in `BannerView` objects, so the first step toward integrating AdMob ads is to include a `BannerView` in your view hierarchy. This is typically done in one of two ways. + +### Interface Builder + +A `BannerView` can be added to a storyboard or xib file like any typical view. When using this method, be sure to add **width** and **height** constraints to match the ad size you'd like to display. For example, when displaying a standard banner (320x50), use a width constraint of 320 points, and a height constraint of 50 points. + +### Programmatically + +A BannerView can also be instantiated directly. Here's an example of how to create a BannerView with the standard banner size of 320x50: + +```csharp +using Google.MobileAds; + +namespace YourNamespace +{ + public class YourViewController + { + BannerView bannerView; + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + + bannerView = new BannerView (AdSizeCons.Banner); + View.AddSubview (bannerView); + } + } +} +``` + +## Configure BannerView properties + +In order to load and display ads, `BannerView` requires a few properties be set. + +* RootViewController: This view controller is used to present an overlay when the ad is clicked. It should normally be set to the view controller that contains the `BannerView`. +* AdUnitID: This is the AdMob ad unit ID from which the `BannerView` should load ads. + +Here's a code example showing how to set the two properties in the `ViewDidLoad` method of a `UIViewController`: + +```csharp +public override void ViewDidLoad () +{ + base.ViewDidLoad (); + + /// + bannerView = new BannerView (AdSizeCons.Banner) { + AdUnitID = "ca-app-pub-3940256099942544/2934735716", + RootViewController = this + }; + /// + + View.AddSubview (bannerView); +} +``` + +> ![note_icon] Note: Ad unit IDs are created in the AdMob UI, and represent a place in your app where ads appear. If you show banner ads in two view controllers, for example, you can create an ad unit for each one. + +## Call LoadRequest method + +Once the `BannerView` is in place and its properties configured, it's time to load an ad. This is done by calling the `LoadRequest` method, which takes a `Request` object as its argument: + +```csharp +public override void ViewDidLoad () +{ + base.ViewDidLoad (); + + bannerView = new BannerView (AdSizeCons.Banner) { + AdUnitID = "ca-app-pub-3940256099942544/2934735716", + RootViewController = this + }; + View.AddSubview (bannerView); + + /// + bannerView.LoadRequest (Request.GetDefaultRequest ()); + /// +} +``` + +`Request` objects represent a single ad request, and contain properties for things like targeting information. + +## Ad events + +Through the use of `IBannerViewDelegate` interface or `BannerView` object events, you can listen for lifecycle events, such as when an ad is closed or the user leaves the app. If you decide to use Ad events, you must use the `IBannerViewDelegate` interface or `BannerView` object events, you cannot use both or create a mix of them. If you plan to use `BannerView` object events, you can jump to [Implementing banner events section](#Implementing-banner-events) directly. + +### Registering for banner events using IBannerViewDelegate interface + +To register for banner ad events using `IBannerViewDelegate` interface, set the `Delegate` property on `BannerView` to an object that implements the `IBannerViewDelegate` interface. Generally, the class that implements banner ads also acts as the delegate class, in which case, the `Delegate` property can be set to `this`: + +```csharp +public class YourViewController : UIViewController, IBannerViewDelegate +{ + BannerView bannerView; + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + + bannerView = new BannerView (AdSizeCons.Banner) { + AdUnitID = "ca-app-pub-3940256099942544/2934735716", + RootViewController = this + }; + View.AddSubview (bannerView); + + /// + bannerView.Delegate = this; + /// + } +} +``` + +### Implementing banner events + +Each of the methods in `IBannerViewDelegate` interface are marked as **optional**, so you only need to implement the methods you need. This also applies for `BannerView` object events, you only need to implement the events you need. This example implements each method and event and logs a message to the console: + +```csharp +// For Console class +using System; +// For Export attribute +using Fundation; + +/// Tells the delegate an ad request loaded an ad. +[Export ("adViewDidReceiveAd:")] +public void DidReceiveAd (BannerView view) +{ + Console.WriteLine ($"{nameof (DidReceiveAd)} method."); +} + +bannerView.AdReceived += (sender, e) => { + Console.WriteLine ("AdReceived event."); +}; + +/// Tells the delegate an ad request failed. +[Export ("adView:didFailToReceiveAdWithError:")] +public void DidFailToReceiveAd (BannerView view, RequestError error) +{ + Console.WriteLine ($"{nameof (DidFailToReceiveAd)} method with error: {error.LocalizedDescription}."); +} + +bannerView.ReceiveAdFailed += (sender, e) => { + Console.WriteLine ($"ReceiveAdFailed event with error: {e.Error.LocalizedDescription}."); +}; + +/// Tells the delegate that a full screen view will be presented in response +/// to the user clicking on an ad. +[Export ("adViewWillPresentScreen:")] +public void WillPresentScreen (BannerView adView) +{ + Console.WriteLine ($"{nameof (WillPresentScreen)} method."); +} + +bannerView.WillPresentScreen += (sender, e) => { + Console.WriteLine ("WillPresentScreen event."); +}; + +/// Tells the delegate that the full screen view will be dismissed. +[Export ("adViewWillDismissScreen:")] +public void WillDismissScreen (BannerView adView) +{ + Console.WriteLine ($"{nameof (WillDismissScreen)} method."); +} + +bannerView.WillDismissScreen += (sender, e) => { + Console.WriteLine ("WillDismissScreen event."); +}; + +/// Tells the delegate that the full screen view has been dismissed. +[Export ("adViewDidDismissScreen:")] +public void DidDismissScreen (BannerView adView) +{ + Console.WriteLine ($"{nameof (DidDismissScreen)} method."); +} + +bannerView.ScreenDismissed += (sender, e) => { + Console.WriteLine ("ScreenDismissed event."); +}; + +/// Tells the delegate that a user click will open another app (such as +/// the App Store), backgrounding the current app. +[Export ("adViewWillLeaveApplication:")] +public void WillLeaveApplication (BannerView adView) +{ + Console.WriteLine ($"{nameof (WillLeaveApplication)} method."); +} + +bannerView.WillLeaveApplication += (sender, e) => { + Console.WriteLine ("WillLeaveApplication event."); +}; +``` + +## Use cases + +Here are some example use cases for these ad event methods. + +### Adding a banner to the view hierarchy once an ad is received + +You may choose to hold off on adding a `BannerView` to the view hierarchy until an ad is received. You can do this by listening for the `DidReceiveAd`/`AdReceived` event: + +```csharp +[Export ("adViewDidReceiveAd:")] +public void DidReceiveAd (BannerView view) +{ + View.AddSubview (bannerView); +} + +// or + +bannerView.AdReceived += (sender, e) => { + View.AddSubview (bannerView); +}; +``` + +The `AddSubview` method automatically removes a view from its parent if it already has one, so it's safe to make this call every time. + +### Animating a banner ad + +You can also use the `DidReceiveAd`/`AdReceived` event to animate a banner ad once it's returned as shown in the following example: + +```csharp +[Export ("adViewDidReceiveAd:")] +public void DidReceiveAd (BannerView view) +{ + bannerView.Alpha = 0; + UIView.Animate (1, () => bannerView.Alpha = 1); +} + +// or + +bannerView.AdReceived += (sender, e) => { + bannerView.Alpha = 0; + UIView.Animate (1, () => bannerView.Alpha = 1); +}; +``` + +### Pausing and resuming the app + +The `IBannerViewDelegate` interface has methods to notify you of events such as when a click causes an overlay to be presented or dismissed, or invokes an external browser. Same functionality applies for `BannerView` events. If you want to know that these events happened because of ads, then register for these `IBannerViewDelegate` methods or `BannerView` events. + +But to catch all types of overlay presentations or external browser invocations, not just ones that come from ad clicks, your app is better off listening for the equivalent methods on `UIViewController` or `UIApplication`. Here is a table showing the equivalent iOS methods that are invoked at the same time as GADBannerViewDelegate methods: + +| IBannerViewDelegate method/BannerView event | iOS method | +|:-------------------------------------------------:|:----------------------------------------------:| +| **WillPresentScreen**/**WillPresentScreen** | UIViewController's **ViewWillDisappear** | +| **WillDismissScreen**/**WillDismissScreen** | UIViewController's **ViewWillAppear** | +| **DidDismissScreen**/**ScreenDismissed** | UIViewController's **ViewDidAppear** | +| **WillLeaveApplication**/**WillLeaveApplication** | UIApplicationDelegate's **DidEnterBackground** | + +## Banner sizes + +The following banner sizes are supported: + +| Size (WxH) | Description | Availability | AdSize constant | +|:-----------------------:|:--------------------:|:--------------------:|:-------------------------------------------------------------:| +| 320x50 | Standard banner | Phones and tablets | kGADAdSizeBanner | +| 320x100 | Large banner | Phones and tablets | kGADAdSizeLargeBanner | +| 300x250 | IAB medium rectangle | Phones and tablets | kGADAdSizeMediumRectangle | +| 468x60 | IAB full-size banner | Tablets | kGADAdSizeFullBanner | +| 728x90 | IAB leaderboard | Tablets | kGADAdSizeLeaderboard | +| Screen width x 32,50,90 | Smart banner | Phones and tablets | kGADAdSizeSmartBannerPortrait, kGADAdSizeSmartBannerLandscape | + +If an app tries to load a banner that's too big for its layout, the SDK won't display it. Instead, an error message will be written to the log. + +## Smart Banners + +Smart Banners are ad units that render screen-wide banner ads on any screen size across different devices in either orientation. Smart Banners help deal with increasing screen fragmentation across different devices by "smartly" detecting the width of the phone in its current orientation, and making the ad view that size. + +Smart Banners on iPhones have a height of 50 points in portrait and 32 points in landscape. On iPads, height is 90 points in both portrait and landscape. + +When an image ad isn't large enough to take up the entire allotted space, the image will be centered, and the space on either side will be filled in. + +To use Smart Banners, just specify `AdSizeCons.SmartBannerPortrait` or `AdSizeCons.SmartBannerLandscape` for the ad size: + +```csharp +BannerView bannerView = new BannerView (AdSizeCons.SmartBannerPortrait); +``` + +Since the addition of the safe area for iOS 11, for full-width banners you should also add constraints for the edges of the banner to the edges of the safe area. + +--- + +# Interstitial Ads + +Interstitial ads are full-screen ads that are overlaid on top of an app. They are generally displayed at natural app transition points such as in between game levels. When an app shows an interstitial ad, the user has the choice to either tap on the ad and continue to its destination or close it and return to the app. + +This guide shows you how to integrate interstitials from AdMob into an iOS app. + +## Create a Interstitial object +Interstitials ads are requested and shown by `Interstitial` objects. The first step in using one is to instantiate it and set its ad unit ID. For example, here's how to create an `Interstitial` in the `ViewDidLoad` method of a `UIViewController`: + +```csharp +using Google.MobileAds; + +namespace YourNamespace +{ + public class YourViewController : UIViewController + { + Interstitial interstitial; + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + interstitial = new Interstitial ("ca-app-pub-3940256099942544/4411468910"); + } + } +} +``` + +`Interstitial` is a single-use object that will load and display one interstitial ad. To display multiple interstitial ads, an app needs to create an `Interstitial` object for each one. + +## Load an ad + +To load an interstitial, call the `Interstitial` object's `LoadRequest` method. This method accepts a `Request` object as its single argument: + +```csharp +public override void ViewDidLoad () +{ + base.ViewDidLoad (); + interstitial = new Interstitial ("ca-app-pub-3940256099942544/4411468910"); + + /// + interstitial.LoadRequest (Request.GetDefaultRequest ()); + /// +} +``` + +## Show the ad + +Interstitials should be displayed during natural pauses in the flow of an app. Between levels of a game is a good example, or after the user completes a task. To show an interstitial, check the `IsReady` property on `Interstitial` instance to verify that it's done loading, then call `PresentFromRootViewController` method. Here's an example of how to do this in one of the action methods in a `UIViewController`: + +```csharp +public void DoSomething (NSObject sender) +{ + // ... + + if (interstitial.IsReady) + interstitial.PresentFromRootViewController (this); + else + Console.WriteLine ("Ad wasn't ready..."); +} +``` + +The message **"Cannot present interstitial. It is not ready"** indicates that the interstitial is still loading or has failed to load. To avoid this warning, use the `IsReady` property to check if the interstitial ad is ready to be presented prior to calling `PresentFromRootViewController` method. + +## Use IInterstitialDelegate interface or Interstitial events to reload + +`Interstitial` is a one-time-use object. That means once an interstitial is shown, `HasBeenUsed` property returns `true` and the interstitial can't be used to load another ad. To request another interstitial, you'll need to create a new `Interstitial` object. If you try to re-use an interstitial object, you'll get the error **"Request Error: Will not send request because interstitial object has been used"**. + +The best place to allocate another interstitial is in the `DidDismissScreen` method of the `IInterstitialDelegate` interface so that the next interstitial starts loading as soon as the previous one is dismissed. You may even consider breaking out interstitial initialization into its own helper method: + +```csharp +// For Export attribute +using Foundation; +using Google.MobileAds; + +namespace YourNamespace +{ + public class YourViewController : UIViewController, IInterstitialDelegate + { + Interstitial interstitial; + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + interstitial = CreateAndLoadInterstitial (); + } + + Interstitial CreateAndLoadInterstitial () + { + var newInterstitial = new Interstitial ("ca-app-pub-3940256099942544/4411468910") { + Delegate = this + }; + newInterstitial.LoadRequest (Request.GetDefaultRequest ()); + return newInterstitial; + } + + [Export ("interstitialDidDismissScreen:")] + public void DidDismissScreen (Interstitial ad) + { + interstitial = CreateAndLoadInterstitial (); + } + } +} +``` + +You can achieve the same result using `Interstitial` events: + +```csharp +using Google.MobileAds; + +namespace YourNamespace +{ + public class YourViewController : UIViewController + { + Interstitial interstitial; + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + interstitial = CreateAndLoadInterstitial (); + } + + Interstitial CreateAndLoadInterstitial () + { + var newInterstitial = new Interstitial ("ca-app-pub-3940256099942544/4411468910"); + newInterstitial.ScreenDismissed += NewInterstitial_ScreenDismissed; + newInterstitial.LoadRequest (Request.GetDefaultRequest ()); + return newInterstitial; + } + + void NewInterstitial_ScreenDismissed (object sender, EventArgs e) + { + interstitial = CreateAndLoadInterstitial (); + } + } +} +``` + +By preloading another interstitial immediately after the previous one is dismissed, your app is prepared to show an interstitial again at the next logical break point. + +## Ad events + +Through the use of `IInterstitialDelegate` interface or `Interstitial` object events, you can listen for lifecycle events, such as when an ad is closed or the user leaves the app. If you decide to use Ad events, you must use the `IInterstitialDelegate` interface or `Interstitial` object events, you cannot use both or create a mix of them. + +### Registering for interstitial events using IInterstitialDelegate interface + +To register for interstitial ad events, set the `Delegate` property on `Interstitial` to an object that implements the `IInterstitialDelegate`interface. Generally, the class that implements interstitial ads also acts as the delegate class, in which case the `Delegate` property can be set to `this` as follows: + +```csharp +// For Export attribute +using Google.MobileAds; + +namespace YourNamespace +{ + public class YourViewController : UIViewController, IInterstitialDelegate + { + Interstitial interstitial; + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + interstitial = new Interstitial ("ca-app-pub-3940256099942544/4411468910") { + Delegate = this + }; + } + } +} +``` + +### Implementing interstitial events + +Each of the methods in `IInterstitialDelegate` interface are marked as **optional**, so you only need to implement the methods you need. This also applies for `Interstitial` object events, you only need to implement the events you need. This example implements each method and logs a message to the console: + +```csharp +// For Console class +using System; +// For Export attribute +using Fundation; + +/// Tells the delegate an ad request succeeded. +[Export ("interstitialDidReceiveAd:")] +public void DidReceiveAd (Interstitial ad) +{ + Console.WriteLine ($"{nameof (DidReceiveAd)} method."); +} + +interstitial.AdReceived += (sender, e) => { + Console.WriteLine ("AdReceived event."); +}; + +/// Tells the delegate an ad request failed. +[Export ("interstitial:didFailToReceiveAdWithError:")] +public void DidFailToReceiveAd (Interstitial sender, RequestError error) +{ + Console.WriteLine ($"{nameof (DidFailToReceiveAd)} method with error: {error.LocalizedDescription}."); +} + +interstitial.ReceiveAdFailed += (sender, e) => { + Console.WriteLine ($"ReceiveAdFailed event with error: {e.Error.LocalizedDescription}."); +}; + +/// Tells the delegate that an interstitial will be presented. +[Export ("interstitialWillPresentScreen:")] +public void WillPresentScreen (Interstitial ad) +{ + Console.WriteLine ($"{nameof (WillPresentScreen)} method."); +} + +interstitial.WillPresentScreen += (sender, e) => { + Console.WriteLine ("WillPresentScreen event."); +}; + +/// Tells the delegate the interstitial failed to present. +[Export ("interstitialDidFailToPresentScreen:")] +public void DidFailToPresentScreen (Interstitial ad) +{ + Console.WriteLine ($"{nameof (DidFailToPresentScreen)} method."); +} + +interstitial.FailedToPresentScreen += (sender, e) => { + Console.WriteLine ("FailedToPresentScreen event."); +}; + +/// Tells the delegate the interstitial is to be animated off the screen. +[Export ("interstitialWillDismissScreen:")] +public void WillDismissScreen (Interstitial ad) +{ + Console.WriteLine ($"{nameof (WillDismissScreen)} method."); +} + +interstitial.WillDismissScreen += (sender, e) => { + Console.WriteLine ("WillDismissScreen event."); +}; + +/// Tells the delegate the interstitial had been animated off the screen. +[Export ("interstitialDidDismissScreen:")] +public void DidDismissScreen (Interstitial ad) +{ + Console.WriteLine ($"{nameof (DidDismissScreen)} method."); +} + +interstitial.ScreenDismissed += (sender, e) => { + Console.WriteLine ("ScreenDismissed event."); +}; + +/// Tells the delegate that a user click will open another app +/// (such as the App Store), backgrounding the current app. +[Export ("interstitialWillLeaveApplication:")] +public void WillLeaveApplication (Interstitial ad) +{ + Console.WriteLine ($"{nameof (WillLeaveApplication)} method."); +} + +interstitial.WillLeaveApplication += (sender, e) => { + Console.WriteLine ("WillLeaveApplication event."); +}; +``` + +## Some best practices + +### Consider whether interstitial ads are the right type of ad for your app. + +Interstitial ads work best in apps with natural transition points. The conclusion of a task within an app, like sharing an image or completing a game level, creates such a point. Because the user is expecting a break in the action, it's easy to present an interstitial ad without disrupting their experience. Make sure you consider at which points in your app's workflow you'll display interstitial ads and how the user is likely to respond. + +### Remember to pause the action when displaying an interstitial ad. + +There are a number of different types of interstitial ads: text, image, video, and more. It's important to make sure that when your app displays an interstitial ad, it also suspends its use of some resources to allow the ad to take advantage of them. For example, when you make the call to display an interstitial ad, be sure to pause any audio output being produced by your app. You can resume playing sounds in the `DidDismissScreen` method of `IInterstitialDelegate` interface or in the `ScreenDismissed` event handler, which will be invoked when the user has finished interacting with the ad. In addition, consider temporarily halting any intense computation tasks (such as a game loop) while the ad is being displayed. This will make sure the user doesn't experience slow or unresponsive graphics or stuttered video. + +### Allow for adequate loading time. + +Just as it's important to make sure you display interstitial ads at an appropriate time, it's also important to make sure the user doesn't have to wait for them to load. Loading the ad in advance by calling `LoadRequest` method before you intend to call `PresentFromRootViewController` can ensure that your app has a fully loaded interstitial ad at the ready when the time comes to display one. + +### Don't flood the user with ads. + +While increasing the frequency of interstitial ads in your app might seem like a great way to increase revenue, it can also degrade the user experience and lower clickthrough rates. Make sure that users aren't so frequently interrupted that they're no longer able to enjoy the use of your app. + +### Don't use the `InterstitialDelegate.DidReceiveAd` or `Interstitial.AdReceived` events to show the interstitial. + +This can cause a poor user experience. Instead, pre-load the ad before you need to show it. Then check the `IsReady` method on `Interstitial` to find out if it is ready to be shown. + +--- + +# Native Ads Advanced (Unified) + +> ![note_icon] _Native Ads Advanced is currently in a limited beta release. If you are interested in participating, reach out to your account manager._ + +Native ads are ad assets that are presented to users via UI components that are native to the platform. They're shown using the same classes you already use in your storyboards, and can be formatted to match your app's visual design. When an ad loads, your app receives an ad object that contains its assets, and the app (rather than the SDK) is then responsible for displaying them. + +This guide will show you how to use the Google Mobile Ads SDK to implement [native ads][6] in an iOS application, as well as some important things to consider along the way. + +## Loading ads + +There are two [system-defined formats for native ads][7]: app install and content. + +Both types of ads are represented by one class: `UnifiedNativeAd`. An instance of this class contains the assets for the native ad. Note that depending on the type of ad represented by the `UnifiedNativeAd`, some fields will not be populated (i.e., they will be `null`). + +Native ads are loaded via `AdLoader` objects, which send messages to their delegates according to the `IAdLoaderDelegate` interface. + +### Initialize the ad loader + +Before you can load an ad, you have to initialize the ad loader. The following code demonstrates how to initialize a `AdLoader`: + +```csharp +adLoader = new AdLoader ("ca-app-pub-3940256099942544/3986624511", + this, + new [] { AdLoaderType.UnifiedNative }, + new [] { /* ad loader options objects */ }) { + Delegate = this +}; +``` + +You'll need an ad unit ID (you can use the test ID), constants to pass in the `adTypes` array to specify which native formats you want to request, and any options you wish to set in the options parameter. The list of possible values for the `options` parameter can be found below in the Setting **native ad options** section below. + +The adTypes array should contain the following constant: + +* AdLoaderType.UnifiedNative + +### Implement the ad loader delegate + +The ad loader delegate needs to implement protocols specific your ad type. For unified native ads: + +* `IUnifiedNativeAdLoaderDelegate`: This protocol includes a message that's sent to the delegate when a unified native ad has loaded: + + ```csharp + public void DidReceiveUnifiedNativeAd (AdLoader adLoader, UnifiedNativeAd nativeAd); + ``` + +### Request the ad + +Once your `AdLoader` is initialized, call its `LoadRequest` method to request an ad: + +```csharp +adLoader.LoadRequest (Request.GetDefaultRequest ()); +``` + +The `LoadRequest` method in `AdLoader` accepts the same `Request` objects as banners and interstitials. You can use request objects to [add targeting information][8], just as you would with other ad types. + +### When to request ads + +Apps displaying native ads are free to request them in advance of when they'll actually be displayed. In many cases, this is the recommended practice. An app displaying a list of items with native ads mixed in, for example, can load native ads for the whole list, knowing that some will be shown only after the user scrolls the view and some may not be displayed at all. + +> ![note_icon] _While prefetching ads is a great technique, it's important that you don't keep old ads around forever without displaying them. Any native ad objects that have been held without display for longer than an hour should be discarded and replaced with new ads from a new request_ + +> ![note_icon] _**Note:**_ _When reusing a `AdLoader`, make sure you wait for each request to complete before calling `LoadRequest` again._ + +### Determining when loading has finished + +After an app calls `LoadRequest`, it can get the results of the request via calls to: + +* `DidFailToReceiveAd` in `IAdLoaderDelegate` +* `DidReceiveUnifiedNativeAd` in `IUnifiedNativeAdLoaderDelegate` + +A request for a single ad will result in one call to one of those methods. + +A request for multiple ads will result in at least one callback to the above methods, but no more than the maximum number of ads requested. + +> ![note_icon] _**Note:**_ _Requests for multiple native ads don't currently work for AdMob ad unit IDs that have been configured for mediation. Publishers using mediation should avoid using the `MultipleAdsAdLoaderOptions` class when making requests._ + +In addition, `IAdLoaderDelegate` offers the `DidFinishLoading` callback. This delegate method indicates that an ad loader has finished loading ads and no other ads or errors will be reported for the request. Here's an example of how to use it when loading several native ads at one time: + +```csharp +using Google.MobileAds + +public partial class ViewController : UIViewController, IUnifiedNativeAdLoaderDelegate, IVideoControllerDelegate { + AdLoader adLoader; + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + + var multipleAdsOptions = new MultipleAdsAdLoaderOptions { NumberOfAds = 5 }; + adLoader = new AdLoader ("YOUR_AD_UNIT_ID", this, new [] { AdLoaderType.UnifiedNative }, new [] { multipleAdsOptions }) { + Delegate = this + }; + adLoader.LoadRequest (Request.GetDefaultRequest ()); + } + + public void DidReceiveUnifiedNativeAd (AdLoader adLoader, UnifiedNativeAd nativeAd) + { + // A unified native ad has loaded, and can be displayed. + } + + // @optional - (void)adLoaderDidFinishLoading:(GADAdLoader *)adLoader; + [Export ("adLoaderDidFinishLoading:")] + void DidFinishLoading (AdLoader adLoader) + { + // The adLoader has finished loading ads, and a new request can be sent. + } +} +``` + +### Handling failed requests + +The above protocols extend the `IAdLoaderDelegate` protocol, which defines a message sent when ads fail to load. You can use the `RequestError` object to determine the cause of the error. + +```csharp +public void DidFailToReceiveAd (AdLoader adLoader, RequestError error); +``` + +### Get notified of native ad events + +To be notified of events related to the native ad interactions, you can use the `IUnifiedNativeAdDelegate` interface methods by setting the `UnifiedNativeAd.Delegate` property or use the `UnifiedNativeAd` events but you cannot use both or create a mix of them: + +```csharp +// If you decide to implement the IUnifiedNativeAdDelegate interface +nativeAd.Delegate = this; +``` + +Then implement `IUnifiedNativeAdDelegate` interface or set the `UnifiedNativeAd` events to receive the following calls: + +```csharp +[Export ("nativeAdDidRecordImpression:")] +void DidRecordImpression (UnifiedNativeAd nativeAd) +{ + // The native ad was shown. +} + +nativeAd.ImpressionRecorded += (sender, e) => { + // The native ad was shown. +}; + +[Export ("nativeAdDidRecordClick:")] +void DidRecordClick (UnifiedNativeAd nativeAd) +{ + // The native ad was clicked on. +} + +nativeAd.ClickRecorded += (sender, e) => { + // The native ad was clicked on. +}; + +[Export ("nativeAdWillPresentScreen:")] +void WillPresentScreen (UnifiedNativeAd nativeAd) +{ + // The native ad will present a full screen view. +} + +nativeAd.WillPresentScreen += (sender, e) => { + // The native ad will present a full screen view. +}; + +// @optional -(void)nativeAdWillDismissScreen:(GADUnifiedNativeAd * _Nonnull)nativeAd; +void WillDismissScreen (UnifiedNativeAd nativeAd) +{ + // The native ad will dismiss a full screen view. +} + +nativeAd.WillDismissScreen += (sender, e) => { + // The native ad will dismiss a full screen view. +}; + +[Export ("nativeAdDidDismissScreen:")] +void DidDismissScreen (UnifiedNativeAd nativeAd) +{ + // The native ad did dismiss a full screen view. +} + +nativeAd.ScreenDismissed += (sender, e) => { + // The native ad did dismiss a full screen view. +}; + +[Export ("nativeAdWillLeaveApplication:")] +void WillLeaveApplication (UnifiedNativeAd nativeAd) +{ + // The native ad will cause the application to become inactive and + // open a new application. +} + +nativeAd.WillLeaveApplication += (sender, e) => { + // The native ad will cause the application to become inactive and + // open a new application. +}; +``` + +## Native ad options + +The last parameter included in the creation of the `AdLoader` above is an optional array of objects. + +```csharp +adLoader = new AdLoader ("ca-app-pub-3940256099942544/3986624511", + this, + new [] { AdLoaderType.UnifiedNative }, + --> new [] { /* ad loader options objects */ }); +``` + +This optional array holds one or more instances of a `AdLoaderOptions` subclass (`NativeAdImageAdLoaderOptions`), which are objects that an app can use to indicate its preferences for how native ads should be loaded and behave. + +`NativeAdImageAdLoaderOptions` contains properties relating to images in Native Advanced ads. Apps can control how a `AdLoader` handles Native Ads Advanced image assets by creating a `NativeAdImageAdLoaderOptions` object, setting its properties (`DisableImageLoading`, `PreferredImageOrientation`, and `HhouldRequestMultipleImages`), and passing it in during initialization. + +`NativeAdImageAdLoaderOptions` has the following propeties: + +* `DisableImageLoading` + + Image assets for native ads are returned via instances of `NativeAdImage`, which contains `Image` and `ImageUrl` properties. If `DisableImageLoading` is set to `false`, which is the default, the SDK will fetch image assets automatically and populate both the `Image` and the `ImageUrl` properties for you. If it's set to `true`, the SDK will only populate `ImageUrl`, allowing you to download the actual images at your discretion. + +* `PreferredImageOrientation` + + Some creatives have multiple images available to match different device orientations. Apps can request images for a particular orientation by setting this property to one of the orientation constants: + + * `NativeAdImageAdLoaderOptionsOrientation.Any` + * `NativeAdImageAdLoaderOptionsOrientation.Landscape` + * `NativeAdImageAdLoaderOptionsOrientation.Portrait` + + > ![note_icon] `If you use `PreferredImageOrientation` to specify a preference for landscape or portrait image orientation, the SDK will place images matching that orientation first in image asset arrays and place non-matching images after them. Since some ads will only have one orientation available, publishers should make sure that their apps can handle both landscape and portrait images.` + + If this property is not set, the default value of `NativeAdImageAdLoaderOptionsOrientation.Any` will be used. + +* `ShouldRequestMultipleImages` + + Some image assets will contain a series of images rather than just one. By setting this value to `true`, your app indicates that it's prepared to display all the images for any assets that have more than one. By setting it to `false` (the default) your app instructs the SDK to provide just the first image for any assets that contain a series. + + If no `AdLoaderOptions` objects are passed in when initializing a `AdLoader`, the default value for each option will be used. + +### `NativeAdViewOptions` + +`NativeAdViewAdOptions` objects are used to indicate preferences for how native ad views should represent ads. They have a single property: `PreferredAdChoicesPosition`, which you can use to specify the location where the AdChoices icon should be placed. The icon can appear at any corner of the ad, and defaults to `AdChoicesPosition.TopRightCorner`. The possible values for this property are: + +* AdChoicesPosition.TopRightCorner +* AdChoicesPosition.TopLeftCorner +* AdChoicesPosition.BottomRightCorner +* AdChoicesPosition.BottomLeftCorner + +Here's an example showing how to place the AdChoices icon in the top left corner of an ad: + +```csharp +NativeAdViewAdOptions adViewAdOptions = new NativeAdViewAdOptions { PreferredAdChoicesPosition = AdChoicesPosition.TopLeftCorner }; +adLoader = new AdLoader ("YOUR_AD_UNIT_ID", this, new [] { AdLoaderType.UnifiedNative }, new [] { adViewAdOptions }); +``` + +### `VideoOptions` + +`VideoOptions` objects are used to indicate how native video assets should be displayed. They offer a single property: `StartMuted`. + +This boolean indicates whether video assets should begin playback in a muted state. The default value is `true`. + +### `MultipleAdsAdLoaderOptions` + +`MultipleAdsAdLoaderOptions` objects allow publishers to instruct an ad loader to load multiple ads in a single request. Ads loaded in this way are guaranteed to be unique. `MultipleAdsAdLoaderOptions` has a single property, `NumberOfAds`, which represents the number of ads the ad loader should attempt to return for the request. By default this value is one, and it's capped at a maximum of five (even if an app requests more ads, at most five will be returned). The actual number of ads returned is not guaranteed, but will be between zero and `NumberOfAds`. + +## Displaying a system-defined native ad format + +When an ad loads, your app receives an ad object via one of the `IAdLoaderDelegate` protocol messages. Your app is then responsible for displaying the ad (though it doesn't necessarily have to do so immediately). To make displaying system-defined ad formats easier, the SDK offers some useful resources. + +### UnifiedNativeAdView + +For the `UnifiedNativeAd`, there is a corresponding "ad view" class: `UnifiedNativeAdView`. This ad view class is a `UIView` that publishers should use to display the ad. A single `UnifiedNativeAdView`, for example, can display a single instance of a `UnifiedNativeAd`. Each of the `UIView` objects used to display that ad's assets should be subviews of that `UnifiedNativeAdView` object. + +If you were displaying an app install ad in a `UITableView`, for example, the view hierarchy for one of the cells might look like this: + +![UITable](https://developers.google.com/admob/images/ios_app_install_layout.png) + +The `UnifiedNativeAdView` class also provides `IBOutlets` used to register the view used for each individual asset, and a method to register the `UnifiedNativeAd` object itself. Registering the views in this way allows the SDK to automatically handle tasks such as: + +* Recording clicks. +* Recording impressions (when the first pixel is visible on the screen). +* Displaying the AdChoices overlay. + +### The AdChoices overlay + +For indirect native ads (delivered via AdMob backfill or through Ad Exchange or AdSense), an AdChoices overlay is added by the SDK. Please, leave space in [your preferred corner](#NativeAdViewOptions) of your native ad view for the automatically inserted AdChoices logo. Also, it's important that the AdChoices overlay be easily seen, so choose background colors and images appropriately. For more information on the overlay's appearance and function, see [Guidelines for programmatic native ads using native styles][5]. + +### Ad attribution + +When displaying programmatic native ads, you must display an ad attribution to denote that the view is an advertisement. + +## Native video + +In addition to images, text, and numbers, some native ads contain video assets. Not every ad will have one and apps are not required to display videos when they're included with an ad. + +### MediaView + +Video assets are displayed to users via `MediaView`. This is a `UIView` that can be defined in a xib file or constructed dynamically. It should be placed within the view hierarchy of a `NativeAdView`, as with any other asset view. + +Unlike other asset views, however, apps do not need to manually populate a `MediaView` with its asset. The SDK handles this automatically as follows: + +* If a video asset is available, it is buffered and starts playing inside the MediaView. +* If the ad does not contain a video asset, the first image asset is downloaded and placed inside the `MediaView` instead. + +This autopopulation of the `MediaView` with an available image asset does not always work when you're using mediation. Because not all mediation adapters guarantee that they'll create a media view for every ad, it's possible for a blank one to be returned for mediated ads. If you're using mediation, you should check the `HasVideoContent` property of an ad's `VideoController` to see if it contains a video asset, before displaying the `MediaView`. If there is no video content, you should display an image view that you populate manually with a relevant image. + +### VideoController + +The `VideoController` class is used to retrieve information about video assets. `NativeAppInstallAd` and `NativeContentAd` both offer a `VideoController` property that exposes the `VideoController` for each ad: + +```csharp +VideoController vc1 = myAppInstallAd.VideoController; +VideoController vc2 = myContentAd.VideoController; +``` + +This property is never `null`, even when the ad doesn't contain a video asset. + +`VideoController` offers the following methods for querying video state: + +* HasVideoContent - True if the ad includes a video asset, false otherwise. +* AspectRatio - The aspect ratio of the video (width/height) or zero (if no video asset is present). + +Apps can also set a `IVideoControllerDelegate` for the `VideoController` to be notified of events in the lifecycle of a video asset, also, you can use `VideoController` events instead of `IVideoControllerDelegate` methods. `IVideoControllerDelegate` offers a single optional method, `DidEndVideoPlayback`, as well as `VideoController` offers a `VideoPlaybackEnded` event, which is sent when a video completes playback. + +Here's an example of `IViewControllerDelegate` in action: + +```csharp +public class YourViewController : UIViewController, IUnifiedNativeAdLoaderDelegate, IVideoControllerDelegate +{ + public override void ViewDidLoad () + { + base.ViewDidLoad (); + } + + public void DidReceiveUnifiedNativeAd (AdLoader adLoader, UnifiedNativeAd nativeAd) + { + nativeAppInstallAd.VideoController.Delegate = this; + } + + [Export ("videoControllerDidEndVideoPlayback:")] + public void DidEndVideoPlayback (VideoController videoController) + { + // Here apps can take action knowing video playback is finished. + // This is handy for things like unmuting audio, and so on. + } +} +``` + +Here's an example of `ViewController` events in action: + +```csharp +public class YourViewController : UIViewController, IUnifiedNativeAdLoaderDelegate +{ + public override void ViewDidLoad () + { + base.ViewDidLoad (); + nativeAppInstallAd.VideoController.VideoPlaybackEnded += (sender, e) => { + // Here apps can take action knowing video playback is finished. + // This is handy for things like unmuting audio, and so on. + }; + } + + public void DidReceiveUnifiedNativeAd (AdLoader adLoader, UnifiedNativeAd nativeAd) + { + + } +} +``` + +--- + +# Native Ads Advanced + +> ![note_icon] **_Note:_** _Native Ads Advanced is currently in a limited beta release. If you are interested in participating, reach out to your account manager to discuss the possibility. This feature will be made available to all publishers at the conclusion of the beta._ + +Native ads are ad assets that are presented to users via UI components that are native to the platform. They're shown using the same classes you already use in your storyboards, and can be formatted to match your app's visual design. When an ad loads, your app receives an ad object that contains its assets, and the app (rather than the SDK) is then responsible for displaying them. + +## Loading ads + +There are two [system-defined formats for native ads][4]: app install and content. + +App install ads are represented by `NativeAppInstallAd`, and content ads are represented by `NativeContentAd`. Instances of the classes contain the assets for the native ad. + +Native Advanced ads are loaded via `AdLoader` objects, which send messages to their delegates according to the `IAdLoaderDelegate` interface. + +### Initialize a AdLoader + +Before you can load an ad, you have to initialize the ad loader. The following code demonstrates how to initialize a `AdLoader` for an app install ad: + +```csharp +var adLoader = new AdLoader ("ca-app-pub-3940256099942544/3986624511", + this, + new NSObject [] { /* ad type constants */ }, + new AdLoaderOptions [] { /* ad loader options objects */ }); +adLoader.Delegate = this; +``` + +You'll need an ad unit ID (you can use the test ID), constants to pass in the `adTypes` array to specify which native formats you want to request, and any options you wish to set in the options parameter. The list of possible values for the `options` parameter can be found below in the Setting **native ad options** section below. + +The adTypes array should contain one or more of the following constants: + +* AdLoaderType.NativeAppInstall +* AdLoaderType.NativeContent + +### Implement the ad loader delegate + +The ad loader delegate needs to implement protocols specific your ad type. For native ads: + +* `INativeAppInstallAdLoaderDelegate`: This interface includes a message that's sent to the delegate when an app install ad has loaded: + + ```csharp + public void DidReceiveNativeAppInstallAd (AdLoader adLoader, NativeAppInstallAd nativeAppInstallAd); + ``` + +* `INativeContentAdLoaderDelegate` This protocol defines a message sent when a content ad has loaded: + + ```csharp + public void DidReceiveNativeContentAd (AdLoader adLoader, NativeContentAd nativeContentAd); + ``` + +### Request the ad + +Once your `AdLoader` is initialized, call its `LoadRequest` method to request an ad: + +```csharp +adLoader.LoadRequest (Request.GetDefaultRequest ()); +``` + +The `LoadRequest` method in `AdLoader` accepts the same `Request` objects as banners and interstitials. You can use request objects to add targeting information just as you would with other ad types. + +### When to request ads + +Apps displaying native ads are free to request them in advance of when they'll actually be displayed. In many cases, this is the recommended practice. An app displaying a list of items with native ads mixed in, for example, can load native ads for the whole list, knowing that some will be shown only after the user scrolls the view and some may not be displayed at all. + +> ![note_icon] _While prefetching ads is a great technique, it's important that you don't keep old ads around forever without displaying them. Any native ad objects that have been held without display for longer than an hour should be discarded and replaced with new ads from a new request._ + +> ![note_icon] **_Note:_** _When reusing a `AdLoader`, make sure you wait for each request to complete before calling `LoadRequest` again._ + +### Determining when loading has finished + +After an app calls `LoadRequest`, it can get the results of the request via calls to: + +* `DidFailToReceiveAd` in `IAdLoaderDelegate` +* `DidReceiveNativeAppInstallAd` in `INativeAppInstallAdLoaderDelegate` +* `DidReceiveNativeContentAd` in `INativeContentAdLoaderDelegate` + +A request for a single ad will result in one call to one of those methods. + +A request for multiple ads will result in at least one callback to the above methods, but no more than the maximum number of ads requested. + +> ![note_icon] _**Note:**_ _Requests for multiple native ads don't currently work for AdMob ad unit IDs that have been configured for mediation. Publishers using mediation should avoid using the `MultipleAdsAdLoaderOptions` class when making requests._ + +In addition, `IAdLoaderDelegate` offers the `DidFinishLoading` callback. This delegate method indicates that an ad loader has finished loading ads and no other ads or errors will be reported for the request. Here's an example of how to use it when loading several native ads at one time: + +```csharp +using Google.MobileAds + +public partial class ViewController : UIViewController, INativeAppInstallAdLoaderDelegate, INativeContentAdLoaderDelegate, IVideoControllerDelegate { + AdLoader adLoader; + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + + var multipleAdsOptions = new MultipleAdsAdLoaderOptions { NumberOfAds = 5 }; + adLoader = new AdLoader ("YOUR_AD_UNIT_ID", this, new [] { AdLoaderType.UnifiedNative }, new [] { multipleAdsOptions }) { + Delegate = this + }; + adLoader.LoadRequest (Request.GetDefaultRequest ()); + } + + public void DidReceiveNativeAppInstallAd (AdLoader adLoader, NativeAppInstallAd nativeAppInstallAd) + { + // An app install ad has loaded, and can be displayed. + } + + public void DidReceiveNativeContentAd (AdLoader adLoader, NativeContentAd nativeContentAd) + { + // A content ad has loaded, and can be displayed. + } + + // @optional - (void)adLoaderDidFinishLoading:(GADAdLoader *)adLoader; + [Export ("adLoaderDidFinishLoading:")] + void DidFinishLoading (AdLoader adLoader) + { + // The adLoader has finished loading ads, and a new request can be sent. + } +} +``` + +### Handling failed requests + +The above protocols extend the `IAdLoaderDelegate` protocol, which defines a message sent when ads fail to load. You can use the `RequestError` object to determine the cause of the error. + +```csharp +public void DidFailToReceiveAd (AdLoader adLoader, RequestError error); +``` + +### Get notified of native ad events + +To be notified of events related to the native ad interactions, you can use the `IUnifiedNativeAdDelegate` interface methods by setting the `UnifiedNativeAd.Delegate` property or use the `UnifiedNativeAd` events but you cannot use both or create a mix of them: + +```csharp +// If you decide to implement the IUnifiedNativeAdDelegate interface +nativeAd.Delegate = this; +``` + +Then implement `IUnifiedNativeAdDelegate` interface or set the `UnifiedNativeAd` events to receive the following calls: + +```csharp +[Export ("nativeAdDidRecordImpression:")] +void DidRecordImpression (UnifiedNativeAd nativeAd) +{ + // The native ad was shown. +} + +nativeAd.ImpressionRecorded += (sender, e) => { + // The native ad was shown. +}; + +[Export ("nativeAdDidRecordClick:")] +void DidRecordClick (UnifiedNativeAd nativeAd) +{ + // The native ad was clicked on. +} + +nativeAd.ClickRecorded += (sender, e) => { + // The native ad was clicked on. +}; + +[Export ("nativeAdWillPresentScreen:")] +void WillPresentScreen (UnifiedNativeAd nativeAd) +{ + // The native ad will present a full screen view. +} + +nativeAd.WillPresentScreen += (sender, e) => { + // The native ad will present a full screen view. +}; + +// @optional -(void)nativeAdWillDismissScreen:(GADUnifiedNativeAd * _Nonnull)nativeAd; +void WillDismissScreen (UnifiedNativeAd nativeAd) +{ + // The native ad will dismiss a full screen view. +} + +nativeAd.WillDismissScreen += (sender, e) => { + // The native ad will dismiss a full screen view. +}; + +[Export ("nativeAdDidDismissScreen:")] +void DidDismissScreen (UnifiedNativeAd nativeAd) +{ + // The native ad did dismiss a full screen view. +} + +nativeAd.ScreenDismissed += (sender, e) => { + // The native ad did dismiss a full screen view. +}; + +[Export ("nativeAdWillLeaveApplication:")] +void WillLeaveApplication (UnifiedNativeAd nativeAd) +{ + // The native ad will cause the application to become inactive and + // open a new application. +} + +nativeAd.WillLeaveApplication += (sender, e) => { + // The native ad will cause the application to become inactive and + // open a new application. +}; +``` + +## Native ad options + +The last parameter included in the creation of the `AdLoader` above is an optional array of objects: + +```csharp +var adLoader = new AdLoader ("ca-app-pub-3940256099942544/3986624511", + this, + new NSObject [] { /* ad type constants */ }, + --> new AdLoaderOptions [] { /* ad loader options objects */ }); +``` + +This optional array holds one or more instances of a `AdLoaderOptions` subclass, objects that an app can use to indicate its preferences for how native ads should be loaded and behave. + +`NativeAdImageAdLoaderOptions` contains properties relating to images in Native Advanced ads. Apps can control how a `AdLoader` handles Native Ads Advanced image assets by creating a `NativeAdImageAdLoaderOptions` object, setting its properties (DisableImageLoading, PreferredImageOrientation, and ShouldRequestMultipleImages), and passing it in during initialization. + +* `DisableImageLoading` + + Image assets for native ads are returned via instances of `NativeAdImage`, which contains `Image` and `ImageUrl` properties. If `DisableImageLoading` is set to `false`, which is the default, the SDK will fetch image assets automatically and populate both the `Image` and the `ImageUrl` properties for you. If it's set to `true`, the SDK will only populate `ImageUrl`, allowing you to download the actual images at your discretion. + +* `PreferredImageOrientation` + + Some creatives have multiple images available to match different device orientations. Apps can request images for a particular orientation by setting this property to one of the orientation constants: + + * `NativeAdImageAdLoaderOptionsOrientation.Any` + * `NativeAdImageAdLoaderOptionsOrientation.Landscape` + * `NativeAdImageAdLoaderOptionsOrientation.Portrait` + + > ![note_icon] `If you use `PreferredImageOrientation` to specify a preference for landscape or portrait image orientation, the SDK will place images matching that orientation first in image asset arrays and place non-matching images after them. Since some ads will only have one orientation available, publishers should make sure that their apps can handle both landscape and portrait images.` + + If this property is not set, the default value of `NativeAdImageAdLoaderOptionsOrientation.Any` will be used. + +* `ShouldRequestMultipleImages` + + Some image assets will contain a series of images rather than just one. By setting this value to `true`, your app indicates that it's prepared to display all the images for any assets that have more than one. By setting it to `false` (the default) your app instructs the SDK to provide just the first image for any assets that contain a series. + + If no `AdLoaderOptions` objects are passed in when initializing a `AdLoader`, the default value for each option will be used. + +### `NativeAdViewOptions` + +`NativeAdViewAdOptions` objects are used to indicate preferences for how native ad views should represent ads. They have a single property: `PreferredAdChoicesPosition`, which you can use to specify the location where the AdChoices icon should be placed. The icon can appear at any corner of the ad, and defaults to `AdChoicesPosition.TopRightCorner`. The possible values for this property are: + +* AdChoicesPosition.TopRightCorner +* AdChoicesPosition.TopLeftCorner +* AdChoicesPosition.BottomRightCorner +* AdChoicesPosition.BottomLeftCorner + +Here's an example showing how to place the AdChoices icon in the top left corner of an ad: + +```csharp +NativeAdViewAdOptions adViewAdOptions = new NativeAdViewAdOptions { PreferredAdChoicesPosition = AdChoicesPosition.TopLeftCorner }; +adLoader = new AdLoader ("YOUR_AD_UNIT_ID", this, new [] { AdLoaderType.UnifiedNative }, new [] { adViewAdOptions }); +``` + +### `VideoOptions` + +`VideoOptions` objects are used to indicate how native video assets should be displayed. They offer a single property: `StartMuted`. + +This boolean indicates whether video assets should begin playback in a muted state. The default value is `true`. + +### `MultipleAdsAdLoaderOptions` + +`MultipleAdsAdLoaderOptions` objects allow publishers to instruct an ad loader to load multiple ads in a single request. Ads loaded in this way are guaranteed to be unique. `MultipleAdsAdLoaderOptions` has a single property, `NumberOfAds`, which represents the number of ads the ad loader should attempt to return for the request. By default this value is one, and it's capped at a maximum of five (even if an app requests more ads, at most five will be returned). The actual number of ads returned is not guaranteed, but will be between zero and `NumberOfAds`. + +## Displaying a system-defined native ad format + +When an ad loads, your app receives an ad object via one of the `IAdLoaderDelegate` protocol messages. Your app is then responsible for displaying the ad (though it doesn't necessarily have to do so immediately). To make displaying system-defined ad formats easier, the SDK offers some useful resources. + +### The ad view classes + +For each of the system-defined formats, there is a corresponding "ad view" class: `NativeAppInstallAdView` for app install ads, and `NativeContentAdView` for content ads. These ad view classes are `UIView`s that publishers should use to display ads of the corresponding format. A single `NativeAppInstallAdView`, for example, can display a single instance of a `NativeAppInstallAd`. Each of the `UIView`s used to display that ad's assets should be children of that `NativeAppInstallAdView` object. + +If you were displaying an app install ad in a `UITableView`, for example, the view hierarchy for one of the cells might look like this: + +![UITable](https://developers.google.com/admob/images/ios_app_install_layout.png) + +The ad view classes also provide `Outlet` attributes used to register the view used for each individual asset, and a method to register the `NativeAd` object itself. Registering the views in this way allows the SDK to automatically handle tasks such as: + +* Recording clicks. +* Recording impressions (when the first pixel is visible on the screen). +* Displaying the AdChoices overlay. + +### The AdChoices overlay + +An AdChoices overlay is added to each ad view by the SDK. Leave space in [your preferred corner](#NativeAdViewOptions) of your native ad view for the automatically inserted AdChoices logo. Also, it's important that the AdChoices overlay be easily seen, so choose background colors and images appropriately. For more information on the overlay's appearance and function, see [Guidelines for programmatic native ads using native styles][5]. + +### Ad attribution + +When displaying programmatic native ads, you must display an ad attribution to denote that the view is an advertisement. + +## Native video + +In addition to images, text, and numbers, some native ads contain video assets. Not every ad will have one and apps are not required to display videos when they're included with an ad. + +### MediaView + +Video assets are displayed to users via `MediaView`. This is a `UIView` that can be defined in a xib file or constructed dynamically. It should be placed within the view hierarchy of a `NativeAdView`, as with any other asset view. + +Unlike other asset views, however, apps do not need to manually populate a `MediaView` with its asset. The SDK handles this automatically as follows: + +* If a video asset is available, it is buffered and starts playing inside the MediaView. +* If the ad does not contain a video asset, the first image asset is downloaded and placed inside the `MediaView` instead. + +This autopopulation of the `MediaView` with an available image asset does not always work when you're using mediation. Because not all mediation adapters guarantee that they'll create a media view for every ad, it's possible for a blank one to be returned for mediated ads. If you're using mediation, you should check the `HasVideoContent` property of an ad's `VideoController` to see if it contains a video asset, before displaying the `MediaView`. If there is no video content, you should display an image view that you populate manually with a relevant image. + +### VideoController + +The `VideoController` class is used to retrieve information about video assets. `NativeAppInstallAd` and `NativeContentAd` both offer a `VideoController` property that exposes the `VideoController` for each ad: + +```csharp +VideoController vc1 = myAppInstallAd.VideoController; +VideoController vc2 = myContentAd.VideoController; +``` + +This property is never `null`, even when the ad doesn't contain a video asset. + +`VideoController` offers the following methods for querying video state: + +* HasVideoContent - True if the ad includes a video asset, false otherwise. +* AspectRatio - The aspect ratio of the video (width/height) or zero (if no video asset is present). + +Apps can also set a `IVideoControllerDelegate` for the `VideoController` to be notified of events in the lifecycle of a video asset, also, you can use `VideoController` events instead of `IVideoControllerDelegate` methods. `IVideoControllerDelegate` offers a single optional method, `DidEndVideoPlayback`, as well as `VideoController` offers a `VideoPlaybackEnded` event, which is sent when a video completes playback. + +Here's an example of `IViewControllerDelegate` in action: + +```csharp +public class YourViewController : UIViewController, IVideoControllerDelegate +{ + public override void ViewDidLoad () + { + base.ViewDidLoad (); + nativeAppInstallAd.VideoController.Delegate = this; + } + + [Export ("videoControllerDidEndVideoPlayback:")] + public void DidEndVideoPlayback (VideoController videoController) + { + // Here apps can take action knowing video playback is finished. + // This is handy for things like unmuting audio, and so on. + } +} +``` + +Here's an example of `ViewController` events in action: + +```csharp +public class YourViewController : UIViewController +{ + public override void ViewDidLoad () + { + base.ViewDidLoad (); + nativeAppInstallAd.VideoController.VideoPlaybackEnded += (sender, e) => { + // Here apps can take action knowing video playback is finished. + // This is handy for things like unmuting audio, and so on. + }; + } +} +``` + +--- + +# Rewarded Video Ads + +Rewarded video ads are full-screen video ads that users have the option of watching in full in exchange for in-app rewards. + +This guide shows you how to integrate rewarded video ads from AdMob into an iOS app. + +## Request rewarded video + +`RewardBasedVideoAd` has a singleton design, so the following example shows a request to load an ad being made to the shared instance: + +```csharp +RewardBasedVideoAd.SharedInstance.LoadRequest (Request.GetDefaultRequest (), "ca-app-pub-3940256099942544/1712485313"); +``` + +To allow videos to be preloaded, we recommend calling `LoadRequest` method as early as possible (for example, in your app delegate's `FinishedLaunching` method). + +## Set up event notifications + +To set up event notification, you can use `RewardBasedVideoAd` events or implement the `IRewardBasedVideoAdDelegate` interface. + +If you plan to implement the `RewardBasedVideoAd` events, you are required to set the events prior to loading an ad. If you plan to implement `IRewardBasedVideoAdDelegate` interface instead of `RewardBasedVideoAd` events, you are required to set the `Delegate` prior to loading an ad. Insert the line shown before your `LoadRequest` call: + +```csharp +RewardBasedVideoAd.SharedInstance.Delegate = this; +``` + +`IRewardBasedVideoAdDelegate` notifies you of rewarded video lifecycle events. You are required to set the delegate prior to loading an ad. The most important method in this interface is `DidRewardUser` or `UserRewarded` if you are using `RewardBasedVideoAd` events, which is called when the user should be rewarded for watching a video. You may optionally implement other methods or events. + +Here is an example that logs each method available in `IRewardBasedVideoAdDelegate` interface, as well as each event in `RewardBasedVideoAd` class: + +```csharp +// For Export attribute +using Foundation; + +public void DidRewardUser (RewardBasedVideoAd rewardBasedVideoAd, AdReward reward) +{ + Console.WriteLine ($"Reward received with currency {reward.Type}, amount {reward.Amount}"); +} + +RewardBasedVideoAd.SharedInstance.UserRewarded += (sender, e) => { + Console.WriteLine ($"Reward received with currency {e.Reward.Type}, amount {e.Reward.Amount}"); +}; + +/// + +[Export ("rewardBasedVideoAd:didFailToLoadWithError:")] +public void DidFailToLoad (RewardBasedVideoAd rewardBasedVideoAd, NSError error) +{ + Console.WriteLine ($"Reward based video ad failed to load with error: {error.LocalizedDescription}."); +} + +RewardBasedVideoAd.SharedInstance.FailedToLoad += (sender, e) => { + Console.WriteLine ($"Reward based video ad failed to load with error: {e.Error.LocalizedDescription}."); +}; + +/// + +[Export ("rewardBasedVideoAdDidReceiveAd:")] +public void DidReceiveAd (RewardBasedVideoAd rewardBasedVideoAd) +{ + Console.WriteLine ("Reward based video ad is received."); +} + +RewardBasedVideoAd.SharedInstance.AdReceived += (sender, e) => { + Console.WriteLine ("Reward based video ad is received."); +}; + +/// + +[Export ("rewardBasedVideoAdDidOpen:")] +public void DidOpen (RewardBasedVideoAd rewardBasedVideoAd) +{ + Console.WriteLine ("Opened reward based video ad."); +} + +RewardBasedVideoAd.SharedInstance.Opened += (sender, e) => { + Console.WriteLine ("Opened reward based video ad."); +}; + +/// + +[Export ("rewardBasedVideoAdDidStartPlaying:")] +public void DidStartPlaying (RewardBasedVideoAd rewardBasedVideoAd) +{ + Console.WriteLine ("Reward based video ad started playing."); +} + +RewardBasedVideoAd.SharedInstance.PlayingStarted += (sender, e) => { + Console.WriteLine ("Reward based video ad started playing."); +}; + +/// + +[Export ("rewardBasedVideoAdDidClose:")] +public void DidClose (RewardBasedVideoAd rewardBasedVideoAd) +{ + Console.WriteLine ("Reward based video ad is closed."); +} + +RewardBasedVideoAd.SharedInstance.Closed += (sender, e) => { + Console.WriteLine ("Reward based video ad is closed."); +}; + +/// + +[Export ("rewardBasedVideoAdWillLeaveApplication:")] +public void WillLeaveApplication (RewardBasedVideoAd rewardBasedVideoAd) +{ + Console.WriteLine ("Reward based video ad will leave application."); +} + +RewardBasedVideoAd.SharedInstance.WillLeaveApplication += (sender, e) => { + Console.WriteLine ("Reward based video ad will leave application."); +}; +``` + +## Display rewarded video + +It is a best practice to ensure a rewarded video ad has completed loading before attempting to display it. The `IsReady` property indicates that a rewarded video ad request has been successfully fulfilled: + +```csharp +if (RewardBasedVideoAd.SharedInstance.IsReady) { + RewardBasedVideoAd.SharedInstance.PresentFromRootViewController (this); +} +``` + +## Reload a rewarded video + +A handy place to load a new rewarded video ad after the previous one is in `DidClose` interface method or `Closed` event: + +```csharp +[Export ("rewardBasedVideoAdDidClose:")] +public void DidClose (RewardBasedVideoAd rewardBasedVideoAd) +{ + RewardBasedVideoAd.SharedInstance.LoadRequest (Request.GetDefaultRequest (), "ca-app-pub-3940256099942544/1712485313"); +} + +RewardBasedVideoAd.SharedInstance.Closed += (sender, e) => { + RewardBasedVideoAd.SharedInstance.LoadRequest (Request.GetDefaultRequest (), "ca-app-pub-3940256099942544/1712485313"); +}; +``` + +--- + + # Targeting + +This guide explains how to provide targeting information to an ad request. + +## Request + +The `Request` object collects targeting information to be sent with an ad request. + +### Location + +If a user has granted your app location permissions, location data is automatically passed to the SDK. The SDK uses this data to improve ad targeting without requiring any code changes in your app. You can, of course, [enable or disable location data for ads][9]. + +Autopopulated location information is not forwarded to mediation networks and it may also be disabled entirely. Therefore, the SDK provides the ability to set location manually. + +After [getting the user's location][10], you can specify location-targeting information in the `Request` as follows: + +```csharp +var request = Request.GetDefaultRequest (); + +if (locationManager.Location != null) { + var currentLocation = locationManager.Location; + request.SetLocation ((nfloat)currentLocation.Coordinate.Latitude, (nfloat)currentLocation.Coordinate.Longitude, (nfloat)currentLocation.HorizontalAccuracy); +} +``` + +Out of respect for user privacy, Google asks that you only specify location if that information is already used by your app. + +### Child-directed setting + +For purposes of the Children's Online Privacy Protection Act (COPPA), there is a method called `Tag`. + +As an app developer, you can indicate whether you want Google to treat your content as child-directed when you make an ad request. When you indicate that you want Google to treat your content as child-directed, we take steps to disable IBA and remarketing ads on that ad request. The setting options are as follows: + +* Set `Tag` to `true` to indicates that you want your content treated as child-directed for purposes of COPPA. +* Set `Tag` to `false` to indicate that you don't want your content treated as child-directed for purposes of COPPA. +* Do not set `Tag` if you do not wish to indicate how you would like your content treated with respect to COPPA. + +```csharp +var request = Request.GetDefaultRequest (); +request.Tag (true); +``` + +By setting this tag, you certify that this notification is accurate and you are authorized to act on behalf of the owner of the app. You understand that abuse of this setting may result in termination of your Google account. + +### Users under the age of consent + +You can mark your ad requests to receive treatment for users in the European Economic Area (EEA) under the age of consent. This feature is designed to help facilitate compliance with the[ General Data Protection Regulation (GDPR)][11]. Note that you may have other legal obligations under GDPR. Please review the European Union’s guidance and consult with your own legal counsel. Please remember that Google's tools are designed to facilitate compliance and do not relieve any particular publisher of its obligations under the law. [Learn more about how the GDPR affects publishers][12]. + +When using this feature, a Tag For Users under the Age of Consent in Europe (TFUA) parameter will be included in the ad request. This parameter disables personalized advertising, including remarketing, for that specific ad request. It also disables requests to third-party ad vendors, such as ad measurement pixels and third-party ad servers. + +The setting can be used with all versions of the Google Mobile Ads SDK by using the `tag_for_under_age_of_consent` network extra. + +* Set `tag_for_under_age_of_consent` to `true` to indicate that you want the ad request to be handled in a manner suitable for users under the age of consent. +* Not setting `tag_for_under_age_of_consent` indicates that you don't want the ad request to be handled in a manner suitable for users under the age of consent. + +The following example indicates that you want TFUA included in your ad request: + +```csharp +var data = new Dictionary () { { "tag_for_under_age_of_consent", true } }; + +var request = Request.GetDefaultRequest (); +var extras = new Extras (); +extras.AdditionalParameters = NSDictionary.FromObjectsAndKeys (data.Values.ToArray (), data.Keys.ToArray (), data.Keys.Count); +request.RegisterAdNetworkExtras (extras); +``` + +> ![note_icon] _**Note:**_ _This `tag_for_under_age_of_consent` parameter is currently NOT forwarded to ad network mediation adapters. It is your responsibility to ensure that each third-party ad network in your application serves ads that are appropriate for users under the age of consent per GDPR._ + +### Ad content filtering + +Apps can set a maximum ad content rating for their ad requests using the max_ad_content_rating network extra. AdMob ads returned for these requests will have a content rating at or below that level. The possible values for this network extra are based on [digital content label classifications][13], and should be one of the following strings: + +* G +* PG +* T +* MA + +The following code configures an `Request` object to specify that ad content returned should correspond to a Digital Content Label designation no higher than G. + +```csharp +var data = new Dictionary () { { "max_ad_content_rating", "G" } }; + +var request = Request.GetDefaultRequest (); +var extras = new Extras (); +extras.AdditionalParameters = NSDictionary.FromObjectsAndKeys (data.Values.ToArray (), data.Keys.ToArray (), data.Keys.Count); +request.RegisterAdNetworkExtras (extras); +``` + +### Content URL + +When requesting an ad, apps may pass the URL of the content they are serving. This enables keyword targeting to match the ad with the content. + +For example, if your app serves blog articles and is requesting an ad while showing content from the article http://googleadsdeveloper.blogspot.com/2016/03/rewarded-video-support-for-admob.html, then you can pass this URL to target relevant keywords: + +```csharp +var request = Request.GetDefaultRequest (); +request.ContentUrl = "http://googleadsdeveloper.blogspot.com/2016/03/rewarded-video-support-for-admob.html"; +``` + +## Load an ad with targeting + +Once all of your request targeting information is set, call `LoadRequest` on `BannerView` with your `Request` instance. + +```csharp +var request = Request.GetDefaultRequest (); +request.Tag (true); +request.ContentUrl = "http://googleadsdeveloper.blogspot.com/2016/03/rewarded-video-support-for-admob.html"; +adView.LoadRequest (request); +``` + +## FAQ + +* **Can I release my app with `Request.TestDevices`?** + * Yes. Test ads are only shown on specific devices that you specify, so all of your users will still receive production ads. + +* **What targeting gets used when an ad automatically refreshes?** + * On ad refresh, the previously specified `Request` object is used for targeting again. To set new targeting, explicitly call `LoadRequest` on `BannerView` with a new `Request` object. + +--- + +# Reporting + +To learn more about this, please, read the following [documentation][14]. + +--- + +# Global Settings + +The `MobileAds` class provides global settings for controlling certain information collected by the Mobile Ads SDK. + +## Video ad volume control + +If your app has its own volume controls (such as custom music or sound effect volumes), disclosing app volume to the Google Mobile Ads SDK allows video ads to respect app volume settings. This ensures users receive video ads with the expected audio volume. + +The device volume, controlled through volume buttons or OS-level volume slider, determines the volume for device audio output. However, apps can independently adjust volume levels relative to the device volume to tailor the audio experience. You can report the relative app volume to the Google Mobile Ads SDK by setting the `ApplicationVolume` property. Valid ad volume values range from 0.0 (silent) to 1.0 (current device volume). Here's an example of how to report the relative app volume to the SDK: + +```csharp +// Set app volume to be half of the current device volume. +MobileAds.SharedInstance.ApplicationVolume = 0.5f; +``` + +To inform the Google Mobile Ads SDK that the app volume has been muted, set the `ApplicationMuted` property, as shown below: + +```csharp +MobileAds.SharedInstance.ApplicationMuted = true; +``` + +Unmuting the app volume reverts it to its previously set level. By default, the app volume for the Google Mobile Ads SDK is set to 1 (the current device volume). + +> ![note_icon] _**Note:**_ _Video ads that are ineligible to be shown with muted audio are not returned for ad requests made when the app volume is reported as muted or set to a value of 0. This may restrict a subset of the broader video ads pool from serving._ + +## Changing audio session + +Audio sessions allow you to express to the system your intentions for your app's audio behavior. Additional information on audio sessions can be found in Apple's [Audio Session Programming Guide][15]. The available options for managing the Google Mobile Ads SDK audio is via the `AudioVideoManager` property. + +If you don't use audio in your app, you don't need to use these APIs. The Google Mobile Ads SDK will automatically manage the audio session category when it plays audio. If you do play audio in your app and you want tighter control of how and when Google Mobile Ads SDK plays audio, these APIs can help. + +On the audio video manager, you can set the `AudioSessionIsApplicationManaged` property to `true` if you want to take responsibility for managing the audio session category yourself. + +If you will manage the audio session category, you should implement `IAudioVideoManagerDelegate` and set the `Delegate` property on the audio video manager or use audio video manager to be notified of ads video and audio playback events. You should then change the audio session category to the relevant category as per Apple's Audio Session Programming Guide linked above. + +> ![note_icon] **_Note:_** _By default, the Mobile Ads SDK will set the audio session category to AVAudioSessionCategoryAmbient when playing ads muted. If you prefer to have your app use `AVAudioSessionCategoryOptionDuckOthers` in this scenario, you must implement the `IAudioVideoManagerDelegate` interface and set the audio video manager `AudioSessionIsApplicationManaged` to `true`._ + +Here is a simplified code sample which shows the recommended approach if your app plays music, using above APIs: + +```csharp +void SetUp () +{ + // If you decide to implement IAudioVideoManagerDelegate interface + MobileAds.SharedInstance.AudioVideoManager.Delegate = this; + + // If you decide to use events + MobileAds.SharedInstance.AudioVideoManager.WillPlayAudio += (sender, e) => { + // The mobile ads SDK is notifying your app that it will play audio. You + // could optionally pause music depending on your apps design. + MyAppObject.SharedInstance.PauseAllMusic (); + }; + + MobileAds.SharedInstance.AudioVideoManager.PlayingAudioStopped += (sender, e) => { + // The mobile ads SDK is notifying your app that it has stopped playing + // audio. Depending on your design, you could resume music here. + MyAppObject.SharedInstance.ResumeAllMusic (); + }; + + MobileAds.SharedInstance.AudioVideoManager.AudioSessionIsApplicationManaged = true; +} + +void MyAppWillStartPlayingMusic () +{ + // Mutes all Google video ads. + MobileAds.SharedInstance.AudioVideoManager.AudioSessionIsApplicationManaged = true; + MobileAds.SharedInstance.ApplicationMuted = true; +} + +void MyAppDidStopPlayingMusic () +{ + // Un-mutes all of our video ads. + MobileAds.SharedInstance.AudioVideoManager.AudioSessionIsApplicationManaged = false; + MobileAds.SharedInstance.ApplicationMuted = false; +} + +// If you decide to implement IAudioVideoManagerDelegate interface +#region AudioVideoManager Delegate + +[Export ("audioVideoManagerWillPlayAudio:")] +public void WillPlayAudio (AudioVideoManager audioVideoManager) +{ + // The mobile ads SDK is notifying your app that it will play audio. You + // could optionally pause music depending on your apps design. + MyAppObject.SharedInstance.PauseAllMusic (); +} + +[Export ("audioVideoManagerDidStopPlayingAudio:")] +public void DidStopPlayingAudio (AudioVideoManager audioVideoManager) +{ + // The mobile ads SDK is notifying your app that it has stopped playing + // audio. Depending on your design, you could resume music here. + MyAppObject.SharedInstance.ResumeAllMusic (); +} + +#endregion +``` + +## In-app purchase reporting + +To help grow your in-app purchase revenue and maximize the total revenue generated by your app, the Mobile Ads SDK now automatically collects general in-app purchase (IAP) information (such as item purchase price and currency). Now, you won't have to implement extra logic to track IAP conversions yourself. If you're an AdMob developer, we recommend that you leverage this new functionality to enjoy the benefits of AdMob's smart monetization offerings. This IAP reporting setting must always be enabled if you are running in-app purchase house ads (currently in limited beta). This is necessary for IAP house ad conversions to be reported. + +In our latest SDK release, in-app purchase reporting is enabled by default. If you'd like, however, you can disable it by using the `DisableAutomatedInAppPurchaseReporting` method (unless you are running IAP house ads, as noted above). The best moment to call this method is when the app launches: + +```csharp +public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) +{ + ... + + MobileAds.DisableAutomatedInAppPurchaseReporting (); + + return true; +} +``` + +## Crash reporting + +The Mobile Ads SDK inspects exceptions that occur in an iOS app and records them if they are caused by the SDK. These exceptions are collected so we can prevent them in future SDK versions. + +In our latest SDK release, crash reporting is enabled by default. If you don't want SDK-related exceptions to be recorded, you can disable this feature by calling the `DisableSDKCrashReporting` method. The best place to call this method is when the app launches: + +```csharp +public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) +{ + ... + + MobileAds.DisableSDKCrashReporting (); + + return true; +} +``` + +--- + +# Requesting Consent from European Users + +Under the Google [EU User Consent Policy][16], you must make certain disclosures to your users in the European Economic Area (EEA) and obtain their consent to use cookies or other local storage, where legally required, and to use personal data (such as AdID) to serve ads. This policy reflects the requirements of the EU ePrivacy Directive and the General Data Protection Regulation (GDPR). + +To support publishers in meeting their duties under this policy, Google offers a Consent SDK. The Consent SDK is an open-source library that provides utility functions for collecting consent from your users. + +Ads served by Google can be categorized as personalized or non-personalized, both requiring consent from users in the EEA. By default, ad requests to Google serve personalized ads, with ad selection based on the user's previously collected data. Google also supports configuring ad requests to serve non-personalized ads. [Learn more about personalized and non-personalized ads][17]. + +This guide describes how to use the Consent SDK to obtain consent from users. It also describes how to forward consent to the Google Mobile Ads SDK once you have obtained consent. + +## Tips for using the Consent SDK + +Prior to using any other methods in the Consent SDK, you should [update consent status](#update-consent-status) to make sure the Consent SDK has the latest information regarding the ad technology providers you've selected in the AdMob UI. If the list of ad technology providers has changed since the user last provided consent, the consent state is set back to an unknown state. + +### If you select 12 or fewer ad technology providers and don't use mediation + +You can use the Consent SDK to present a [Google-rendered consent form](#google-rendered-consent-form) to your users. The consent form displays a list of the ad technology providers you've selected in the AdMob UI. The Consent SDK stores the user consent response. + +If a user has consented to receive only non-personalized ads, you'll need to [forward consent to the Google Mobile Ads SDK](#forward-consent-to-the-google-mobile-ads-sdk). + +### If you select more than 12 ad technology providers and don't use mediation + +You can use the Consent SDK to dynamically retrieve the full list of ad technology providers from AdMob, as explained in [publisher-managed consent collection](#publisher-managed-consent-collection). However, you'll need to determine how the list of providers should be made available to your users and present your own consent form to your users. + +Once the user has made a consent choice, you can ask the Consent SDK to store the user's consent choice as explained in [Storing publisher managed consent](#storing-publisher-managed-consent). + +If a user has consented to receive only non-personalized ads, you'll need to [forward consent to the Google Mobile Ads SDK](#forward-consent-to-the-google-mobile-ads-sdk). + +### If you use AdMob mediation + +You can use the Consent SDK to dynamically retrieve the full list of ad technology providers from AdMob, as explained in [publisher-managed consent collection](#publisher-managed-consent-collection). You'll need to determine which additional ad technology providers from other ad networks need to be presented to your users for consent. + +As an app developer, you'll need to collect user consent for both the ad technology providers returned by the Consent SDK and the providers from other ad networks. You'll also need to manually store user consent responses and [forward consent to the Google Mobile Ads SDK](#forward-consent-to-the-google-mobile-ads-sdk) if the user consented to receive only non-personalized ads. + +> ![warning_icon] _**Warning:**_ _We recommend **against** using the Consent SDK to store publisher-managed consent if you collect consent for additional ad technology providers beyond those returned by the Consent SDK. The Consent SDK only tracks changes to the list of ad technology providers from AdMob, and cannot track changes to any additional providers you may add to your consent form._ + +Google currently is unable to obtain and handle consent for mediation networks, so you'll need to obtain and handle consent for each ad network separately. We are actively working with all of our [open source and versioned][18] mediation networks to provide updated documentation with details on how to forward consent. Documentation is already live for the following mediation networks: + +* [AppLovin](https://developers.google.com/admob/ios/mediation/applovin#eu_consent_and_gdpr) +* [Chartboost](https://developers.google.com/admob/ios/mediation/chartboost#eu_consent_and_gdpr) +* [Facebook](https://developers.google.com/admob/ios/mediation/facebook#eu_consent_and_gdpr) +* [IronSource](https://developers.google.com/admob/ios/mediation/ironsource#eu_consent_and_gdpr) +* [MoPub](https://developers.google.com/admob/ios/mediation/mopub#eu_consent_and_gdpr) +* [myTarget](https://developers.google.com/admob/ios/mediation/mytarget#eu_consent_and_gdpr) +* [Tapjoy](https://developers.google.com/admob/ios/mediation/tapjoy#eu_consent_and_gdpr) +* [Unity Ads](https://developers.google.com/admob/ios/mediation/unity#eu_consent_and_gdpr) +* [Vungle](https://developers.google.com/admob/ios/mediation/vungle#eu_consent_and_gdpr) + +## Update consent status + +When using the Consent SDK, it is recommended that you determine the status of a user's consent at **every app launch**. To do this, call `RequestConsentInfoUpdate` method on an instance of `ConsentInformation`: + +```csharp +using Google.MobileAds.Consent; + +... + +public override void ViewDidLoad () +{ + base.ViewDidLoad (); + + ConsentInformation.SharedInstance.RequestConsentInfoUpdate (new [] { "pub-0123456789012345" }, HandleConsentInformationUpdate;); + + void HandleConsentInformationUpdate (NSError error) + { + if (error != null) { + // Consent info update failed. + return; + } + + // Consent info update succeeded. The shared PACConsentInformation + // instance has been updated. + } +} +``` + +The call to `RequestConsentInfoUpdate` requires two arguments: + +* An array of publisher IDs that your app requests ads from. [Find your publisher ID.][19] +* A block that accepts an `NSError` as an input parameter, which provides information on a failed consent update request. + +An async/await version of this: + +```csharp +try { + await ConsentInformation.SharedInstance.RequestConsentInfoUpdateAsync (new [] { "pub-0123456789012345" }); + + // Consent info update succeeded. The shared PACConsentInformation + // instance has been updated. +} catch (NSErrorException ex) { + // Consent info update failed. +} +``` + +If consent information is successfully updated, `ConsentInformation.SharedInstance.ConsentStatus` provides the updated consent status. It may have the values listed below: + +| Consent State | Definition | +|-------------------------------|---------------------------------------------------------------------------------------------| +| ConsentStatus.Personalized | The user has granted consent for personalized ads. | +| ConsentStatus.NonPersonalized | The user has granted consent for non-personalized ads. | +| ConsentStatus.Unknown | The user has neither granted nor declined consent for personalized or non-personalized ads. | + +Once consent information is successfully updated, you can also check `ConsentInformation.SharedInstance.IsRequestLocationInEeaOrUnknown` to see if the user is located in the European Economic Area or the request location is unknown. + +If the `IsRequestLocationInEeaOrUnknown` property is `false`, the user is not located in the European Economic Area and consent is not required under the [EU User Consent Policy][16]. You can make ad requests to the Google Mobile Ads SDK. + +If the `IsRequestLocationInEeaOrUnknown` property is `true`: + +* If the `ConsentStatus` is `ConsentStatus.Personalized` or `ConsentStatus.NonPersonalized`, the user has already provided consent. You can now [forward consent to the Google Mobile Ads SDK](#forward-consent-to-the-google-mobile-ads-sdk). +* If the user has an `ConsentStatus.Unknown` consent, see the Collect consent section below, which describes the use of utility methods to collect consent. + +## Collect consent + +Google's Consent SDK provides two ways to collect consent from a user: + +* Present a [Google-rendered consent form](#google-rendered-consent-form) to the user. +* Request the list of ad technology providers and collect consent yourself with the [Publisher-managed consent collection](#publisher-managed-consent-collection) option. + +Remember to provide users with the option to [Change or revoke consent](#change-or-revoke-consent). + +### Google-rendered consent form + + + +The Google-rendered consent form is a full-screen configurable form that displays over your app content. You can configure the form to present the user with combinations of the following options: + +* Consent to view personalized ads +* Consent to view non-personalized ads +* Use a paid version of the app instead of viewing ads + +You should review the consent text carefully: what appears by default is a message that **might** be appropriate if you use Google to monetize your app; but we cannot provide legal advice on the consent text that is appropriate for you. To update consent text of the Google-rendered consent form, modify the `consentform.html` file located at Xamarin Bulld Download cache. By default, the file is located at: + +* **Windows:** `$(LocalAppData)\XamarinBuildDownloadCache\GCnsnt-$(Version)\googleads-consent-sdk-ios-$(Version)\PersonalizedAdConsent\PersonalizedAdConsent\PersonalizedAdConsent.bundle` +* **Mac:** `$(HOME)\Library\Caches\XamarinBuildDownload\GCnsnt-$(Version)\googleads-consent-sdk-ios-$(Version)\PersonalizedAdConsent\PersonalizedAdConsent\PersonalizedAdConsent.bundle` + +> ![note_icon] _**Important:**_ _The Google-rendered consent form is not supported if any of your publisher IDs use the commonly used set of ad technology providers. Attempting to load the Google-rendered consent form will always fail in this case._ +> +> _If your publisher IDs use a custom set of providers, and the custom set of ad technology providers exceeds 12, the form removes the personalized ads option. To collect personalized consent for more than 12 ad technology providers, you must use the [Publisher-managed consent collection](#publisher-managed-consent-collection) option._ + +The Google rendered consent form is configured and displayed using the `ConsentForm` class. The following code demonstrates how to build a `ConsentForm` with all three form options: + +```csharp +// TODO: Replace with your app's privacy policy url. +var url = new NSUrl ("https://www.your.com/privacyurl"); +var form = new ConsentForm (url) { + ShouldOfferPersonalizedAds = true, + ShouldOfferNonPersonalizedAds = true, + ShouldOfferAdFree = true +}; +``` + +The properties of `ConsentForm` are described in further detail below: + +* `ShouldOfferPersonalizedAds` + * Indicates whether the consent form should show a personalized ad option. Defaults to YES. + +* `ShouldOfferNonPersonalizedAds` + * Indicates whether the consent form should show a non-personalized ad option. Defaults to YES. + +* `ShouldOfferAdFree` + * Indicates whether the consent form should show an ad-free app option. Defaults to NO. + +### Load consent form + +Once you have created and configured a `ConsentForm` object, load the consent form by invoking the `Load` method of `ConsentForm`, as shown below: + +```csharp +form.Load (HandleLoadCompletionHandler); + +void HandleLoadCompletionHandler (NSError error) +{ + if (error != null) { + // Handle error. + return; + } + + // Load successful. +} + +// async/await version + +try { + await form.LoadAsync (); + // Load successful. +} catch (NSErrorException ex) { + // Handle error. +} +``` + +### Show consent form + +To present the user with the Google-rendered consent form, call `Present` on a loaded ConsentForm, as demonstrated below: + +```csharp +form.Present (this, HandleDismissCompletion); + +void HandleDismissCompletion (NSError error, bool userPrefersAdFree) +{ + if (error == null) { + // Handle error. + return; + } + + if (userPrefersAdFree) { + // User prefers to use a paid version of the app. + } else { + // Check the user's consent choice. + var status = ConsentInformation.SharedInstance.ConsentStatus; + } +} + +// async/await version + +var dismissCompletionResult = await form.PresentAsync (this); + +if (dismissCompletionResult.Error == null) { + // Handle error. + return; +} + +if (dismissCompletionResult.UserPrefersAdFree) { + // User prefers to use a paid version of the app. +} else { + // Check the user's consent choice. + var status = ConsentInformation.SharedInstance.ConsentStatus; +} +``` + +The call to `Present` requires two arguments: + +* A `UIViewController` to present from. +* A block that accepts an `NSError` and a `bool` as input parameters. The `NSError` provides information if there was an error showing the consent form. The `userPrefersAdFree` `bool` has a value of `true` when the user chose to use a paid version of the app in lieu of viewing ads. + +After the user selects an option and closes the form, the Consent SDK saves the user's choice and calls the block. You can read the user's choice and [forward consent to the Google Mobile Ads SDK](#forward-consent-to-the-google-mobile-ads-sdk). + +### Publisher-managed consent collection + +If you choose to get consent yourself, you can use the `AdProviders` property of the `ConsentInformation` class to get the ad technology providers associated with the publisher IDs used in your app. Note that consent is required for the full list of ad technology providers configured for your publisher IDs. + +> ![note_icon] _**Note:**_ _Before you access the `AdProviders` property of `ConsentInformation`, you must wait for the successful update of user's consent status as described in the Update consent status section._ + +```csharp +var adProviders = ConsentInformation.SharedInstance.AdProviders; +``` + +You can then use the list of ad providers to obtain consent yourself. + +### Storing publisher managed consent + +Upon getting consent, record the `ConsentStatus` corresponding to the user's response using the `Status` property of the `ConsentInformation` class. + +```csharp +ConsentInformation.SharedInstance.ConsentStatus = ConsentStatus.Personalized; +``` + +After reporting consent to the Consent SDK, you can [forward consent to the Google Mobile Ads SDK](#forward-consent-to-the-google-mobile-ads-sdk). + +### Change or revoke consent + +> ![note_icon] _**Key Point:**_ _It is important that the user is able to change or revoke the consent they have provided at any time._ + +To allow users to update their consent, simply repeat the steps outlined in the [Collect consent](#collect-consent) section when the user chooses to update their consent status. + +### Users under the age of consent + +If a publisher is aware that the user is under the age of consent, all ad requests must set TFUA (Tag For Users under the Age of Consent in Europe). To include this tag on all ad requests made from your app, set the `TagForUnderAgeOfConsent` property to `true`. This setting takes effect for all future ad requests: + +```csharp +ConsentInformation.SharedInstance.TagForUnderAgeOfConsent = true; +``` + +Once the TFUA setting is enabled, the Google rendered consent form will fail to load. All ad requests that include TFUA will be made ineligible for personalized advertising and remarketing. TFUA disables requests to third-party ad technology providers, such as ad measurement pixels and third-party ad servers. + +To remove TFUA from ad requests, set the `TagForUnderAgeOfConsent` property to `false`. + +## Testing + +The Consent SDK has different behaviors depending on the value of `ConsentInformation.SharedInstance.RequestLocationInEeaOrUnknown`. For example, the consent form fails to load if the user is not located in the EEA. + +To enable easier testing of your app both inside and outside the EEA, the Consent SDK supports debug options that you can set prior to calling any other methods in the Consent SDK. + +1. Grab your device's advertising ID. You can write the following code to log your advertising ID: + ```csharp + using AdSupport; + + Console.WriteLine ($"Advertising ID: {ASIdentifierManager.SharedManager.AdvertisingIdentifier.AsString ()}"); + ``` + + And then check the console to get it: + + ``` + Advertising ID: 41E538F6-9C98-4EF2-B3EE-D7BD8CAF8339 + ``` +2. Whitelist your device to be a debug device using the advertising ID from the console: + ```csharp + ConsentInformation.SharedInstance.DebugIdentifiers = new [] { "41E538F6-9C98-4EF2-B3EE-D7BD8CAF8339" }; + ``` + + > ![warning_icon] _**Warning:**_ _We highly discourage directly setting debugIdentifiers to the current device's advertising ID. You don't want to run the risk of releasing your app with code in place that overwrites every user's geography._ + + > ![note_icon] _**Note:**_ _Simulators are automatically added as debug devices and don't need to be whitelisted._ +3. Finally, set the debugGeography to your preferred geography for testing purposes + ```csharp + // Geography appears as in EEA for debug devices. + ConsentInformation.SharedInstance.DebugGeography = DebugGeography.Eea; + + // Geography appears as not in EEA for debug devices. + ConsentInformation.SharedInstance.DebugGeography = DebugGeography.NotEea; + ``` + +After completing these steps, calls to [update consent status](#update-consent-status) will take into account your debug geography. + +## Forward consent to the Google Mobile Ads SDK + +> ![note_icon] _**Note:**_ _The code in this section can be used with any version of the Google Mobile Ads SDK. It can also be used regardless of whether you used the Consent SDK to gather consent._ + +The default behavior of the Google Mobile Ads SDK is to serve personalized ads. If a user has consented to receive only non-personalized ads, you can configure an GADRequest object with the following code to specify that only non-personalized ads should be requested: + +```csharp +var data = new Dictionary { { "npa", "1" } }; + +var request = Request.GetDefaultRequest (); +var extras = new Extras { + AdditionalParameters = NSDictionary.FromObjectsAndKeys (data.Values.ToArray (), data.Keys.ToArray (), data.Keys.Count) +}; +request.RegisterAdNetworkExtras (extras); +``` + +> ![note_icon] _**Note:**_ _Google's [EU User Consent Policy][16] requires that you collect consent for the full list of ad technology providers configured for your publisher IDs before displaying personalized ads, even if you are using a third-party mediation solution to send ad request to Google._ + +If non-personalized ads are requested, the ad request URL currently includes `&npa=1`. However, note that this is an internal implementation detail of the Google Mobile Ads SDK and is subject to change. + +## FAQ + +**How many ad technology providers does the Consent SDK support?** + +The Consent SDK does not impose a limit on the number of ad technology providers a publisher chooses to enable. However, the Google-rendered consent form supports a maximum of 12 ad technology providers. Publishers with more than 12 ad technology providers can create and manage their own consent form using the ad technology partners fetched from the Consent SDK. + +**Does the list of ad technology providers returned by the SDK automatically update if I change my selection in the AdMob UI?** + +Yes, if you make changes to the list of ad technology providers in the AdMob UI, the changes will propagate to Google’s ad servers in approximately one hour. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/admob/ios/quick-start) to see original Firebase documentation._ + +[1]: https://firebase.google.com/console/ +[2]: http://support.google.com/firebase/answer/7015592 +[3]: https://apps.admob.com/#account/appmgmt: +[4]: https://support.google.com/admob/answer/6240809 +[5]: https://support.google.com/dfp_premium/answer/6075370 +[6]: https://support.google.com/admob/answer/7187428 +[7]: https://support.google.com/dfp_premium/answer/6366881?visit_id=1-636620822163502235-512919786&rd=1 +[8]: https://developers.google.com/mobile-ads-sdk/docs/dfp/ios/targeting +[9]: https://support.google.com/admob/answer/6373176 +[10]: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html +[11]: https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32016R0679https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html +[12]: https://support.google.com/admob/answer/7666366 +[13]: https://support.google.com/admob/answer/7562142 +[14]: https://developers.google.com/admob/ios/reporting +[15]: https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/Introduction/Introduction.html +[16]: https://www.google.com/about/company/user-consent-policy.html +[17]: https://support.google.com/admob/answer/7676680 +[18]: https://developers.google.com/admob/ios/mediation/#choosing_your_mediation_networks +[19]: https://support.google.com/admob/answer/2784578 +[note_icon]: https://cdn3.iconfinder.com/data/icons/UltimateGnome/22x22/apps/gnome-app-install-star.png +[warning_icon]: https://cdn2.iconfinder.com/data/icons/freecns-cumulus/32/519791-101_Warning-20.png +[deprecated_icon]: https://cdn2.iconfinder.com/data/icons/freecns-cumulus/16/519643-144_Forbidden-20.png "Deprecated" diff --git a/docs/Firebase/Analytics/Details.md b/docs/Firebase/Analytics/Details.md new file mode 100755 index 00000000..181e2470 --- /dev/null +++ b/docs/Firebase/Analytics/Details.md @@ -0,0 +1,20 @@ +Firebase Analytics is a free app measurement solution that provides insight on app usage and user engagement. + +At the heart of Firebase is Firebase Analytics, a free and unlimited analytics solution. Analytics integrates across Firebase features and provides you with unlimited reporting for up to 500 distinct events that you can define using the Firebase SDK. Analytics reports help you understand clearly how your users behave, which enables you to make informed decisions regarding app marketing and performance optimizations. + +## Key capabilities + +| | | +|-:|-| +| **Unlimited Reporting:** | Firebase Analytics provides unlimited reporting on up to 500 distinct events. | +| **Audience Segmentation:** | Custom audiences can be defined in the Firebase console based on device data, custom events, or user properties. These audiences can be used with other Firebase features when targeting new features or notifications messages. | + +## How does it work? + +Firebase Analytics helps you understand how people use your iOS app. The SDK automatically captures a number of events and user properties and also allows you to define your own custom events to measure the things that uniquely matter to your business. Once the data is captured, it's available in a dashboard through the Firebase console. This dashboard provides detailed insights about your data — from summary data such as active users and demographics, to more detailed data such as identifying your most purchased items. + +Analytics also integrates with a number of other Firebase features. For example, it automatically logs events that correspond to notification messages sent via the Notifications composer and provides reporting on the impact of each campaign. + +Firebase Analytics helps you understand how your users behave, so you can make informed decisions about how to market your app. See the performance of your campaigns across organic and paid channels to understand which methods are most effective at driving high-value users. If you need to perform custom analysis or join your data with other sources you can link your Analytics data to BigQuery, which allows for more complex analysis like querying large data sets and joining multiple data sources. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/analytics/) to see original Firebase documentation._ diff --git a/docs/Firebase/Analytics/GettingStarted.md b/docs/Firebase/Analytics/GettingStarted.md new file mode 100755 index 00000000..92da4304 --- /dev/null +++ b/docs/Firebase/Analytics/GettingStarted.md @@ -0,0 +1,308 @@ +# Get Started with Firebase Analytics for iOS + +Firebase Analytics collects usage and behavior data for your app. The SDK logs two primary types of information: + +* **Events:** What is happening in your app, such as user actions, system events, or errors. +* **User properties:** Attributes you define to describe segments of your userbase, such as language preference or geographic location. + +## Table of Content + +- [Get Started with Firebase Analytics for iOS](#get-started-with-firebase-analytics-for-ios) + - [Table of Content](#table-of-content) + - [Add Firebase to your app](#add-firebase-to-your-app) + - [Configure Analytics in your app](#configure-analytics-in-your-app) +- [Log events](#log-events) + - [View events in the dashboard](#view-events-in-the-dashboard) +- [Set User Properties](#set-user-properties) +- [Use Analytics in a WebView on iOS](#use-analytics-in-a-webview-on-ios) + - [Implement Javascript handler](#implement-javascript-handler) + - [Implement native interface](#implement-native-interface) +- [Debugging Events](#debugging-events) + - [Enabling debug mode](#enabling-debug-mode) +- [Track Screenviews](#track-screenviews) + - [Automatically track screens](#automatically-track-screens) + - [Manually track screens](#manually-track-screens) +- [Extend Google Analytics for Firebase with Cloud Functions](#extend-google-analytics-for-firebase-with-cloud-functions) + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][1], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][2]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][2] again at any time. + +## Configure Analytics in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Xamarin Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Open `GoogleService-Info.plist` file and change `IS_ANALYTICS_ENABLED` value to `Yes`. +4. Add the following line of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` namespace): + +```csharp +App.Configure (); +``` + +--- + +# Log events + +Events provide insight on what is happening in your app, such as user actions, system events, or errors. + +Analytics automatically logs some [events][3] for you; you don't need to add any code to receive them. If your app needs to collect additional data, you can log up to 500 different Analytics Event types in your app. There is no limit on the total volume of events your app logs. + +After you have configured Analytics in your app, you can begin to log events with the `Analytics.LogEvent` method. You can find some constants names ready to be used with your log: + +* Suggested events: see the `EventNamesConstants` class. +* Prescribed parameters: see the `ParameterNamesConstants` class. + +It is very easy to log an event, the following example demonstrates how to log an event with constants values (don't forget to import `Firebase.Analytics` namespace): + +```csharp +NSString [] keys = { ParameterNamesConstants.ContentType, ParameterNamesConstants.ItemId }; +NSObject [] values = { new NSString ("cont"), new NSString ("1") }; +var parameters = NSDictionary.FromObjectsAndKeys (values, keys, keys.Length); +Analytics.LogEvent (EventNamesConstants.SelectContent, parameters); + +// Or + +var parameters = new Dictionary { + { ParameterNamesConstants.ContentType, "cont" }, + { ParameterNamesConstants.ItemId, "1" } +}; +Analytics.LogEvent (EventNamesConstants.SelectContent, parameters); +``` + +In addition to the prescribed parameters, you can add the following parameters to any event: + +* **Custom parameters**: Custom parameters can be [registered][15] for reporting in your Analytics reports. They can also be used as filters in [audience][4] definitions that can be applied to every report. Custom parameters are also included in data [exported to BigQuery][5] if your app is linked to a BigQuery project. +* **`ParameterNamesConstants.Value` parameter**: `ParameterNamesConstants.Value` is a general purpose parameter that is useful for accumulating a key metric that pertains to an event. Examples include revenue, distance, time, and points. + +If your application has specific needs not covered by a suggested event type, you can log your own custom events as shown in this example: + +```csharp +NSString [] keys = { new NSString ("Name") }; +NSObject [] values = { new NSString ("Image name") }; +var parameters = NSDictionary.FromObjectsAndKeys (values, keys, keys.Length); +Analytics.LogEvent ("share_image", parameters); + +// Or + +var parameters = new Dictionary { { "Name", "Image Name" } }; +Analytics.LogEvent ("share_image", parameters); +``` + +> ![note_icon] **_Note: Data logged to Analytics can take hours to be refreshed on reports._** + +### View events in the dashboard + +You can view aggregrated statistics about your Analytics events in the Firebase console dashboards. These dashboards update periodically throughout the day. + +You can access this data in the Firebase console as follows: + +1. In the [Firebase console][1], open your project. +2. Select **Analytics** from the menu to view the Analytics reporting dashboard. + +The **Events** tab shows the [event reports][10] that are automatically created for each distinct type of Analytics event logged by your app. Read more about the [Analytics reporting dashboard][11] in the Firebase Help Center. + +--- + +# Set User Properties + +User properties are attributes you define to describe segments of your userbase, such as language preference or geographic location. + +Analytics automatically logs some [user properties][6]; you don't need to add any code to enable them. If your app needs to collect additional data, you can set up to 25 different Analytics User Properties in your app. + +> ![note_icon] **_Note: The Age, Gender, and Interests properties are automatically collected only if your app links to the Ad Support framework. Linking to this framework also automatically collects the Advertising Identifier (IDFA)._** + +You can set Analytics user properties to describe the users of your app. You can analyze behaviors of various user segments by applying these properties as filters to your reports. + +To set a user property you need to: + +1. [Register][7] the property in the **Analytics** page of the [Firebase console][1]. +2. Add code to set an Analytics user property with the `Analytics.SetUserProperty` method. You can use the name and value of your choosing for each property (don't forget to import `Firebase.Analytics` namespace): + +```csharp +// Pass null as value if you want to remove a registered user property +Analytics.SetUserProperty ("your value", "your property name"); +``` + +> ![note_icon] ***Note:*** *Once the property is registered, it can take several hours for data collected with the property to be included in reports. When the new data is available, the user property can be used as a report filter or audience definition.* + +You can access this data in the Firebase console as follows: + +1. In the [Firebase console][1], open your project. +2. Select **Analytics** from the menu to view the Analytics reporting dashboard. + +The **User Properties** tab shows a list of user properties that you have defined for your app. You can use these properties as a filter on many of the reports available in Firebase Analytics. Read more about the [Analytics reporting dashboard][11] in the Firebase Help Center. + +> ![note_icon] _**Note:**_ _Data in the Analytics reporting dashboard refreshes periodically throughout the day._ + +--- + +# Use Analytics in a WebView on iOS + +Calls to log events or set user properties fired from within a WebView must be forwarded to native code before they can be sent to Firebase Analytics. + +## Implement Javascript handler + +The first step in using Google Analytics for Firebase in a WebView is to create JavaScript functions to forward events and user properties to native code. The following example shows how to do this in a way that is compatible with both Android and iOS native code: + +```javascript +function logEvent(name, params) { + if (!name) { + return; + } + + if (window.AnalyticsWebInterface) { + // Call Android interface + window.AnalyticsWebInterface.logEvent(name, JSON.stringify(params)); + } else if (window.webkit + && window.webkit.messageHandlers + && window.webkit.messageHandlers.firebase) { + // Call iOS interface + var message = { + command: 'logEvent', + name: name, + parameters: params + }; + window.webkit.messageHandlers.firebase.postMessage(message); + } else { + // No Android or iOS interface found + console.log("No native APIs found."); + } +} + +function setUserProperty(name, value) { + if (!name || !value) { + return; + } + + if (window.AnalyticsWebInterface) { + // Call Android interface + window.AnalyticsWebInterface.setUserProperty(name, value); + } else if (window.webkit + && window.webkit.messageHandlers + && window.webkit.messageHandlers.firebase) { + // Call iOS interface + var message = { + command: 'setUserProperty', + name: name, + value: value + }; + window.webkit.messageHandlers.firebase.postMessage(message); + } else { + // No Android or iOS interface found + console.log("No native APIs found."); + } +} +``` + +## Implement native interface + +To invoke native iOS code from JavaScript, create a message handler class conforming to the `IWKScriptMessageHandler` interface. You can make Firebase Analytics calls inside of the `DidReceiveScriptMessage` callback: + +```csharp +public void DidReceiveScriptMessage (WKUserContentController userContentController, WKScriptMessage message) +{ + var messageBody = message.Body as NSDictionary; + + switch (messageBody ["command"].ToString ()) { + case "setUserProperty": + Analytics.SetUserProperty (messageBody ["value"].ToString (), messageBody ["name"].ToString ()); + break; + case "logEvent": + Analytics.LogEvent (messageBody ["name"].ToString (), messageBody ["parameters"] as NSDictionary); + break; + } +} +``` + +Finally, add the message handler to the webview's user content controller: + +```csharp +webView.Configuration.UserContentController.AddScriptMessageHandler (this, "firebase"); +``` + +--- + +# Debugging Events + +DebugView enables you to see the raw event data logged by your app on development devices in near real-time. This is very useful for validation purposes during the instrumentation phase of development and can help you discover errors and mistakes in your analytics implementation and confirm that all events and user properties are being logged correctly. + +## Enabling debug mode + +Generally, events logged by your app are batched together over the period of approximately one hour and uploaded together. This approach conserves the battery on end users’ devices and reduces network data usage. However, for the purposes of validating your analytics implementation (and, in order to view your analytics in the DebugView report), you can enable Debug mode on your development device to upload events with a minimal delay. + +To enable Analytics Debug mode on your development device, follow these steps: + +* Visual Studio for Mac + * Open your project settings + * Go to Run/Configuration and select your desired configuration + * In **Extra mlaunch Arguments**, type the command line showed below. + +* Visual Studio for Windows + * Open your project settings + * Go **iOS Run Options** + * In **Extra mlaunch Arguments**, type the command line showed below. + +``` +--argument=-FIRDebugEnabled +``` + +This behavior persists until you explicitly disable Debug mode by specifying the following command line argument: + +``` +--argument=-FIRDebugDisabled +``` + +To learn more about this, please, read the following [documentation][12]. + +--- + +# Track Screenviews + +Google Analytics for Firebase tracks screen transitions and attaches information about the current screen to events, enabling you to track metrics such as user engagement or user behavior per screen. Much of this data collection happens automatically, but you can also manually track screen names. Manually tracking screens is useful if your app does not use a separate `UIViewController` for each screen you may wish to track, such as in a game. + +### Automatically track screens + +Analytics automatically tracks some information about screens in your application, such as the class name of the `UIViewController` that is currently in focus. When a screen transition occurs, Analytics logs a `screen_view` event that identifies the new screen. Events that occur on these screens are automatically tagged with the parameter `firebase_screen_class` (for example, `MenuViewController`) and a generated `firebase_screen_id`. If your app uses a distinct `UIViewController` for each screen, Analytics can automatically track every screen transition and generate a report of user engagement broken down by screen. If your app doesn't, you can still get these reports by manually setting the screen name with the API. + +### Manually track screens + +You can manually set the screen name and optionally override the class name when screen transitions occur. After setting the screen name, events that occur on these screens are additionally tagged with the parameter `firebase_screen`. For example, you could name a screen "Main Menu" or "Friends List". The following example shows how to manually set the screen name: + +```csharp +Firebase.Analytics.Analytics.SetScreenNameAndClass (screenName:, screenClass); +``` + +The screen name and screen class stay the same until the `UIViewController` changes or you make a new call to `SetScreenNameAndClass`. + +--- + +# Extend Google Analytics for Firebase with Cloud Functions + +Google Analytics for Firebase provides event reports that help you understand how users interact with your app. With Cloud Functions, you can access conversion events you have logged and trigger functions based on those events. + +> ![note_icon] _Only events marked as conversion events are currently supported by Cloud Functions. You can specify which events are conversion events in the [Events][13] tab of the Firebase console **Analytics** pane._ + +To learn more about this, please, read the following [documentation][14]. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/analytics/ios/start) to see original Firebase documentation._ + +[1]: https://firebase.google.com/console/ +[2]: http://support.google.com/firebase/answer/7015592 +[3]: https://support.google.com/firebase/answer/6317485 +[4]: https://support.google.com/firebase/answer/6317509?hl=en&ref_topic=6317489 +[5]: https://support.google.com/firebase/answer/6318765 +[6]: https://support.google.com/firebase/answer/6317486 +[7]: https://support.google.com/firebase/answer/6317519?hl=en&ref_topic=6317489#create-property +[10]: https://support.google.com/firebase/answer/6317522?hl=en&ref_topic=6317489 +[11]: https://support.google.com/firebase/answer/6317517?hl=en&ref_topic=6317489 +[12]: https://firebase.google.com/docs/analytics/debugview +[13]: https://console.firebase.google.com/project/_/analytics/app/_/events +[14]: https://firebase.google.com/docs/analytics/extend-with-functions +[15]: https://support.google.com/firebase/answer/7397304?hl=en&ref_topic=6317489 +[note_icon]: https://cdn3.iconfinder.com/data/icons/UltimateGnome/22x22/apps/gnome-app-install-star.png +[warning_icon]: https://cdn2.iconfinder.com/data/icons/freecns-cumulus/32/519791-101_Warning-20.png diff --git a/docs/Firebase/Auth/Details.md b/docs/Firebase/Auth/Details.md new file mode 100755 index 00000000..b6297f84 --- /dev/null +++ b/docs/Firebase/Auth/Details.md @@ -0,0 +1,26 @@ +Most apps need to know the identity of a user. Knowing a user's identity allows an app to securely save user data in the cloud and provide the same personalized experience across all of the user's devices. + +Firebase Authentication provides backend services, easy-to-use SDKs, and ready-made UI libraries to authenticate users to your app. It supports authentication using passwords, popular federated identity providers like Google, Facebook and Twitter, and more. + +Firebase Authentication integrates tightly with other Firebase services, and it leverages industry standards like OAuth 2.0 and OpenID Connect, so it can be easily integrated with your custom backend. + +## Key capabilities + +| | | +|-:|-| +| **Email and password based authentication** | Authenticate users with their email addresses and passwords. The Firebase Authentication SDK provides methods to create and manage users that use their email addresses and passwords to sign in. Firebase Authentication also handles sending password reset emails. | +| **Federated identity provider integration** | Authenticate users by integrating with federated identity providers. The Firebase Authentication SDK provides methods that allow users to sign in with their Google, Facebook, Twitter, and GitHub accounts. | +| **Phone number authentication** | Authenticate users by sending SMS messages to their phones. | +| **Custom auth system integration** | Connect your app's existing sign-in system to the Firebase Authentication SDK and gain access to Firebase Realtime Database and other Firebase services. | +| **Anonymous auth** | Use Firebase features that require authentication without requiring users to sign in first by creating temporary anonymous accounts. If the user later chooses to sign up, you can upgrade the anonymous account to a regular account, so the user can continue where they left off. | + + +## How does it work? + +![FirebaseAuth_HowItWorks](https://firebase.google.com/docs/auth/images/auth-providers.png) + +To sign a user into your app, you first get authentication credentials from the user. These credentials can be the user's email address and password, or an OAuth token from a federated identity provider. Then, you pass these credentials to the Firebase Authentication SDK. Our backend services will then verify those credentials and return a response to the client. + +After a successful sign in, you can access the user's basic profile information, and you can control the user's access to data stored in other Firebase products. You can also use the provided authentication token to verify the identity of users in your own backend services. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/auth/) to see original Firebase documentation._ \ No newline at end of file diff --git a/docs/Firebase/Auth/GettingStarted.md b/docs/Firebase/Auth/GettingStarted.md new file mode 100755 index 00000000..79e30c93 --- /dev/null +++ b/docs/Firebase/Auth/GettingStarted.md @@ -0,0 +1,1982 @@ +# Firebase Auth on iOS + +You can use Firebase Authentication to allow users to sign in to your app using one or more sign-in methods, including email address and password sign-in, and federated identity providers such as Google Sign-in and Facebook Login. This tutorial gets you started with Firebase Authentication by showing you how to add email address and password sign-in to your app. + +## Table of content + +- [Firebase Auth on iOS](#firebase-auth-on-ios) + - [Table of content](#table-of-content) + - [Add Firebase to your app](#add-firebase-to-your-app) + - [Configure Auth in your app](#configure-auth-in-your-app) + - [Listen for authentication state](#listen-for-authentication-state) + - [Sign up new users](#sign-up-new-users) + - [Sign in existing users](#sign-in-existing-users) + - [Get user information](#get-user-information) +- [Manage Users in Firebase](#manage-users-in-firebase) + - [Create a user](#create-a-user) + - [Get the currently signed-in user](#get-the-currently-signed-in-user) + - [Sign out a user](#sign-out-a-user) + - [Get a user's profile](#get-a-users-profile) + - [Get a user's provider-specific profile information](#get-a-users-provider-specific-profile-information) + - [Update a user's profile](#update-a-users-profile) + - [Set a user's email address](#set-a-users-email-address) + - [Send a user a verification email](#send-a-user-a-verification-email) + - [Set a user's password](#set-a-users-password) + - [Send a password reset email](#send-a-password-reset-email) + - [Delete a user](#delete-a-user) + - [Re-authenticate a user](#re-authenticate-a-user) + - [Import user accounts](#import-user-accounts) + - [Authenticate with Firebase using Password-Based Accounts](#authenticate-with-firebase-using-password-based-accounts) + - [Create an account](#create-an-account) + - [Sign in a user with an email address and password](#sign-in-a-user-with-an-email-address-and-password) +- [Authenticate Using Google Sign-In](#authenticate-using-google-sign-in) +- [Authenticate Using Facebook Login](#authenticate-using-facebook-login) +- [Authenticate Using Twitter Login](#authenticate-using-twitter-login) +- [Authenticate Using GitHub](#authenticate-using-github) +- [Authenticate with Firebase on iOS using a Phone Number](#authenticate-with-firebase-on-ios-using-a-phone-number) + - [Security concerns](#security-concerns) + - [1. Enable Phone Number sign-in for your Firebase project](#1-enable-phone-number-sign-in-for-your-firebase-project) + - [2. Enable app verification](#2-enable-app-verification) + - [Start receiving silent notifications](#start-receiving-silent-notifications) + - [Set up reCAPTCHA verification](#set-up-recaptcha-verification) + - [3. Send a verification code to the user's phone](#3-send-a-verification-code-to-the-users-phone) + - [4. Sign in the user with the verification code](#4-sign-in-the-user-with-the-verification-code) + - [Appendix: Using phone sign-in without swizzling](#appendix--using-phone-sign-in-without-swizzling) +- [Authenticate with Firebase Using a Custom Authentication System](#authenticate-with-firebase-using-a-custom-authentication-system) +- [Authenticate with Firebase Anonymously](#authenticate-with-firebase-anonymously) +- [Passing State in Email Actions](#passing-state-in-email-actions) + - [Passing state/continue URL in email actions](#passing-state/continue-url-in-email-actions) + - [Configuring Firebase Dynamic Links](#configuring-firebase-dynamic-links) + - [Handling email actions in a web application](#handling-email-actions-in-a-web-application) + - [Handling email actions in a mobile application](#handling-email-actions-in-a-mobile-application) + - [Convert an anonymous account to a permanent account](#convert-an-anonymous-account-to-a-permanent-account) +- [Link Multiple Auth Providers to an Account](#link-multiple-auth-providers-to-an-account) + - [Link auth provider credentials to a user account](#link-auth-provider-credentials-to-a-user-account) + - [Unlink an auth provider from a user account](#unlink-an-auth-provider-from-a-user-account) +- [Create custom email action handlers](#create-custom-email-action-handlers) +- [Extend Firebase Authentication with Cloud Functions](#extend-firebase-authentication-with-cloud-functions) + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][1], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][2]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][2] again at any time. + +## Configure Auth in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Xamarin Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Add the following line of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` namespace): + +```csharp +App.Configure (); +``` + +## Listen for authentication state + +For each of your app's views that need information about the signed-in user, attach a listener to the `Auth` object. This listener gets called whenever the user's sign-in state changes. + +Attach the listener in the view controller's `ViewWillAppear` method: + +```csharp +var handle = Auth.DefaultInstance.AddAuthStateDidChangeListener (HandleAuthStateDidChangeListener); + +void HandleAuthStateDidChangeListener (Auth auth, User user) +{ + // ... +} +``` + +And detach the listener in the view controller's `ViewWillDisappear` method: + +```csharp +Auth.DefaultInstance.RemoveAuthStateDidChangeListener (handle); +``` + +## Sign up new users + +Create a form that allows new users to register with your app using their email address and a password. When a user completes the form, validate the email address and password provided by the user, then pass them to the `CreateUser` method: + +```csharp +Auth.DefaultInstance.CreateUser (email, password, HandleAuthResult); + +void HandleAuthResult (User user, NSError error) +{ + // ... +} + +// async/await version + +try { + User user = await Auth.DefaultInstance.CreateUserAsync (email, password); +} catch (NSErrorException ex) { + // ... +} +``` + +## Sign in existing users + +Create a form that allows existing users to sign in using their email address and password. When a user completes the form, call the `SignIn` method: + +```csharp +Auth.DefaultInstance.SignIn (email, password, HandleAuthResult); + +void HandleAuthResult (User user, NSError error) +{ + // ... +} + +// async/await version + +try { + User user = await Auth.DefaultInstance.SignInAsync (email, password); +} catch (NSErrorException ex) { + // ... +} +``` + +## Get user information + +After a user signs in successfully, you can get information about the user. For example, in your authentication state listener: + +```csharp +if (user != null) { + // The user's ID, unique to the Firebase project. + // Do NOT use this value to authenticate with your backend server, + // if you have one. Use GetToken method instead. + var uid = user.Uid; + var email = user.Email; + var photoUrl = user.PhotoUrl; +} +``` + +--- + +# Manage Users in Firebase + +## Create a user + +You create a new user in your Firebase project by calling the `CreateUser` method or by signing in a user for the first time using a federated identity provider, such as [Google Sign-In](#authenticate-using-google-sign-in) or [Facebook Login](#authenticate-using-facebook-login). + +You can also create new password-authenticated users from the Authentication section of the [Firebase console][1], on the Users page. + +## Get the currently signed-in user + +The recommended way to get the current user is by setting a listener on the Auth object: + +```csharp +var handle = Auth.DefaultInstance.AddAuthStateDidChangeListener (HandleAuthStateDidChangeListener); + +void HandleAuthStateDidChangeListener (Auth auth, User user) +{ + // ... +} +``` + +To remove the listener, pass the `handle` to `RemoveAuthStateDidChangeListener`: + +```csharp +Auth.DefaultInstance.RemoveAuthStateDidChangeListener (handle); +``` + +By using a listener, you ensure that the Auth object isn't in an intermediate state—such as initialization—when you get the current user. + +You can also get the currently signed-in user by using the `CurrentUser` property. If a user isn't signed in, `CurrentUser` is `null`: + +```csharp +var user = Auth.DefaultInstance.CurrentUser; +if (user != null) { + // User is signed in. +} else { + // No user is signed in. +} +``` + +> ![note_icon] **_Note_**: _`CurrentUser` might also be `null` because the auth object has not finished initializing. If you use a listener to keep track of the user's sign-in status, you don't need to handle this case._ + +## Sign out a user + +To sign out a user, just call `SignOut` method: + +```csharp +NSError error; +var signedOut = Auth.DefaultInstance.SignOut (out error); + +if (!signedOut) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that SignOut method with credentials could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.KeychainError: + default: + // Print error + break; + } +} + +// Do your magic to handle successful signout +``` + +## Get a user's profile + +To get a user's profile information, use the properties of an instance of `User`. For example: + +```csharp +var user = Auth.DefaultInstance.CurrentUser; +string providerId = user.ProviderId; +string name = user.DisplayName; +string email = user.Email; +NSUrl photoUrl = user.PhotoUrl; + +// The user's ID, unique to the Firebase project. Do NOT use this value to +// authenticate with your backend server, if you have one. Use +// GetToken method instead. +string uid = user.UID; +``` + +## Get a user's provider-specific profile information + +To get the profile information retrieved from the sign-in providers linked to a user, use the `ProviderData` property. For example: + +```csharp +var user = Auth.DefaultInstance.CurrentUser; +foreach (var profile in user.ProviderData) { + string providerId = profile.ProviderId; + string name = profile.DisplayName; + string email = profile.Email; + NSUrl photoUrl = profile.PhotoUrl; +} +``` + +## Update a user's profile + +You can update a user's basic profile information—the user's display name and profile photo Url—with the `UserProfileChangeRequest` class. For example: + +```csharp +var user = Auth.DefaultInstance.CurrentUser; +var changeRequest = user.ProfileChangeRequest (); +changeRequest.DisplayName = name; +changeRequest.PhotoUrl = new NSUrl (photoUrl); + +changeRequest.CommitChanges (HandleUserProfileChange); + +void HandleUserProfileChange (NSError error) +{ + if (error != null) { + // An error happened. + return; + } + + // Profile updated. +} + +// async/await version + +try { + await changeRequest.CommitChangesAsync (); + // Profile updated. +} catch (NSErrorException ex) { + // An error happened. +} + +``` + +### Set a user's email address + +You can set a user's email address with the `UpdateEmail` method. For example: + +```csharp +var user = Auth.DefaultInstance.CurrentUser; +user.UpdateEmail (email, HandleUserProfileChange); + +void HandleUserProfileChange (NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that UpdateEmail method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.EmailAlreadyInUse: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.RequiresRecentLogin: + default: + // Print error + break; + } + + return; + } + + // Email updated. +} + +// async/await version + +try { + await user.UpdateEmailAsync (email); + // Email updated. +} catch (NSErrorException ex) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)ex.Error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)ex.Error.Code); + + // Posible error codes that UpdateEmail method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.EmailAlreadyInUse: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.RequiresRecentLogin: + default: + // Print error + break; + } +} + +``` + +> ![note_icon] **_Important_**: _To set a user's password, the user must have signed in recently. See [Re-authenticate a user](#re-authenticate-a-user) section._ + +## Send a user a verification email + +You can send an address verification email to a user with the `SendEmailVerification` method. For example: + +```csharp +var user = Auth.DefaultInstance.CurrentUser; +user.SendEmailVerification (HandleSendEmailVerification); + +void HandleSendEmailVerification (NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that SendEmailVerification method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.UserNotFound: + default: + // Print error + break; + } + + return; + } + + // Email sent. +} + +// async/await version +try { + await user.SendEmailVerificationAsync (); + // Email sent. +} catch (NSErrorException ex) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)ex.Error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)ex.Error.Code); + + // Posible error codes that SendEmailVerification method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.UserNotFound: + default: + // Print error + break; + } +} +``` + +You can customize the email template that is used in Authentication section of the [Firebase console][1], on the Email Templates page. See [Email Templates][12] in Firebase Help Center. + +It is also possible to pass state via a [continue URL](#passing-state-in-email-actions) to redirect back to the app when sending a verification email. + +Additionally you can localize the verification email by updating the language code on the Auth instance before sending the email. For example: + +```csharp +Auth.DefaultInstance.LanguageCode = "es"; + +// To apply the default app language instead of explicitly setting it. +Auth.DefaultInstance.UseAppLanguage (); +``` + +## Set a user's password + +You can set a user's password with the `UpdatePassword` method. For example: + +```csharp +var user = Auth.DefaultInstance.CurrentUser; +user.UpdatePassword (password, HandleUserProfileChange); + +void HandleUserProfileChange (NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that UpdatePassword method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.RequiresRecentLogin: + case AuthErrorCode.WeakPassword: + default: + // Print error + break; + } + + return; + } + + // Password updated. +} + +//async/await version + +try { + await user.UpdatePasswordAsync (password); + // Password updated. +} catch (NSErrorException ex) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)ex.Error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)ex.Error.Code); + + // Posible error codes that UpdatePassword method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.RequiresRecentLogin: + case AuthErrorCode.WeakPassword: + default: + // Print error + break; + } +} +``` + +> ![note_icon] **_Important_**: _To set a user's password, the user must have signed in recently. See [Re-authenticate a user](#re-authenticate-a-user) section._ + +## Send a password reset email + +You can send a password reset email to a user with the `SendPasswordReset` method. For example: + +```csharp +Auth.DefaultInstance.SendPasswordReset (email, HandleSendPasswordReset); + +void HandleSendPasswordReset (NSError error) +{ + if (error != null) { + // An error happened. + } else { + // Password reset email sent. + } +} + +// async/await version + +try { + await Auth.DefaultInstance.SendPasswordReset (email); + // Password reset email sent. +} catch (NSErrorException ex) { + // An error happened. +} +``` + +You can customize the email template that is used in Authentication section of the [Firebase console][1], on the Email Templates page. See [Email Templates][12] in Firebase Help Center. + +It is also possible to pass state via a [continue URL](#passing-state-in-email-actions) to redirect back to the app when sending a verification email. + +Additionally you can localize the verification email by updating the language code on the Auth instance before sending the email. For example: + +```csharp +Auth.DefaultInstance.LanguageCode = "es"; + +// To apply the default app language instead of explicitly setting it. +Auth.DefaultInstance.UseAppLanguage (); +``` + +You can also send password reset emails from the Firebase console. + +## Delete a user + +You can delete a user account with the `Delete` method. For example: + +```csharp +var user = Auth.DefaultInstance.CurrentUser; +user.Delete (HandleUserProfileChange); + +void HandleUserProfileChange (NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that Delete method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.RequiresRecentLogin: + default: + // Print error + break; + } + + return; + } + + // Account deleted. +} + +// async/await version + +try { + await user.DeleteAsync (); +} catch (NSErrorException ex) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)ex.Error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)ex.Error.Code); + + // Posible error codes that Delete method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.RequiresRecentLogin: + default: + // Print error + break; + } +} +``` + +You can also delete users from the Authentication section of the [Firebase console][1], on the Users page. + +> ![note_icon] **_Important_**: _To set a user's password, the user must have signed in recently. See [Re-authenticate a user](#re-authenticate-a-user) section._ + +### Re-authenticate a user + +Some security-sensitive actions (such as **deleting an account**, **setting a primary email address**, and **changing a password**) require that the user has recently signed in. If you perform one of these actions, and the user signed in too long ago, the action fails with the `AuthErrorCode.RequiresRecentLogin` error. When this happens, re-authenticate the user by getting new sign-in credentials from the user and passing the credentials to `Reauthenticate` method. For example: + +```csharp +var user = Auth.DefaultInstance.CurrentUser; +AuthCredential credential; + +// Prompt the user to re-provide their sign-in credentials + +user.Reauthenticate (credential, HandleUserProfileChange); + +void HandleUserProfileChange (NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that Reauthenticate method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.InvalidCredential: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.WrongPassword: + case AuthErrorCode.UserMismatch: + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.EmailAlreadyInUse: + case AuthErrorCode.UserDisabled: + default: + // Print error + break; + } + + return; + } + + // User re-authenticated. +} + +// async/await version + +try { + await user.ReauthenticateAsync (credential); + // User re-authenticated. +} catch (NSErrorException ex) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)ex.Error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)ex.Error.Code); + + // Posible error codes that Reauthenticate method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.InvalidCredential: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.WrongPassword: + case AuthErrorCode.UserMismatch: + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.EmailAlreadyInUse: + case AuthErrorCode.UserDisabled: + default: + // Print error + break; + } +} +``` + +## Import user accounts + +You can import user accounts from a file into your Firebase project by using the Firebase CLI's `auth:import` command. For example: + +``` +firebase auth:import users.json --hash-algo=scrypt --rounds=8 --mem-cost=14 +``` + +--- + +# Authenticate with Firebase using Password-Based Accounts + +You can use Firebase Authentication to let your users authenticate with Firebase using their email addresses and passwords, and to manage your app's password-based accounts. + +## Create a password-based account + +When a new user signs up using your app's sign-up form, complete any new account validation steps that your app requires, such as verifying that the new account's password was correctly typed and meets your complexity requirements. + +Create a new account by passing the new user's email address and password to `Auth.CreateUser` instance method (don't forget to import `Firebase.Auth` namespace): + +```csharp +Auth.DefaultInstance.CreateUser (email, password, HandleAuthResult); + +void HandleAuthResult (User user, NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that CreateUser method could throw + switch (errorCode) { + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.EmailAlreadyInUse: + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.WeakPassword: + default: + // Print error + break; + } + + return; + } + // Do your magic to handle authentication result +} + +// async/await version + +try { + User user = await Auth.DefaultInstance.CreateUserAsync (email, password); + // Do your magic to handle authentication result +} catch (NSErrorException ex) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)ex.Error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)ex.Error.Code); + + // Posible error codes that CreateUser method could throw + switch (errorCode) { + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.EmailAlreadyInUse: + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.WeakPassword: + default: + // Print error + break; + } +} +``` + +If the new account was successfully created, the user is signed in, and you can get the user's account data from the user object that's passed to the callback method. + +> ![note_icon] _**Note:**_ _To protect your project from abuse, Firebase limits the number of new email/password and anonymous sign-ups that your application can have from the same IP address in a short period of time. You can request and schedule temporary changes to this quota from the [Firebase console][1]._ + +## Sign in a user with an email address and password + +When a user signs in to your app, pass the user's email address and password to `Auth.SignIn` instance method (don't forget to import `Firebase.Auth` namespace): + +```csharp +Auth.DefaultInstance.SignIn (email, password, HandleAuthResult); + +void HandleAuthResult (User user, NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that SignIn method with email and password could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.UserDisabled: + case AuthErrorCode.WrongPassword: + default: + // Print error + break; + } + + return; + } + + // Do your magic to handle authentication result +} + +// async/await version + +try { + User user = await Auth.DefaultInstance.SignInAsync (email, password); + // Do your magic to handle authentication result +} catch (NSErrorException ex) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)ex.Error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)ex.Error.Code); + + // Posible error codes that SignIn method with email and password could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.UserDisabled: + case AuthErrorCode.WrongPassword: + default: + // Print error + break; + } +} +``` + +If the user successfully signs in, you can get the user's account data from the user object that's passed to the callback method. + +--- + +# Authenticate with Firebase Using Email Link in iOS + +You can use Firebase Authentication to sign in a user by sending them an email containing a link, which they can click to sign in. In the process, the user's email address is also verified. + +There are numerous benefits to signing in by email: + +* Low friction sign-up and sign-in. +* Lower risk of password reuse across applications, which can undermine security of even well-selected passwords. +* The ability to authenticate a user while also verifying that the user is the legitimate owner of an email address. +* A user only needs an accessible email account to sign in. No ownership of a phone number or social media account is required. +* A user can sign in securely without the need to provide (or remember) a password, which can be cumbersome on a mobile device. +* An existing user who previously signed in with an email identifier (password or federated) can be upgraded to sign in with just the email. For example, a user who has forgotten their password can still sign in without needing to reset their password. + +## Enable Email Link sign-in for your Firebase project + +To sign in users by email link, you must first enable the Email provider and Email link sign-in method for your Firebase project: + +* In the [Firebase console][1], open the **Auth** section. +* On the **Sign in method** tab, enable the **Email/Password** provider. Note that email/password sign-in must be enabled to use email link sign-in. +* In the same section, enable **Email link (passwordless sign-in)** sign-in method. +* Click **Save**. + +## Send an authentication link to the user's email address + +To initiate the authentication flow, present the user with an interface that prompts the user to provide their email address and then call `SendSignInLink` method to request that Firebase send the authentication link to the user's email. + +1. Construct the `ActionCodeSettings` object, which provides Firebase with instructions on how to construct the email link. Set the following fields: + * Url The deep link to embed and any additional state to be passed along. The link's domain has to be whitelisted in the Firebase Console list of authorized domains. + * IOSBundleId and AndroidPackageName : The apps to use when the sign-in link is opened on an Android or iOS device. Learn more on how to [configure Firebase Dynamic Links](#configuring-firebase-dynamic-links) to open email action links via mobile apps. + * HandleCodeInApp: Set to `true`. The sign-in operation has to always be completed in the app unlike other out of band email actions (password reset and email verifications). This is because, at the end of the flow, the user is expected to be signed in and their Auth state persisted within the app. + + ```csharp + var actionCodeSettings = new ActionCodeSettings { + Url = new NSUrl ("https://www.example.com"), + IOSBundleId = NSBundle.MainBundle.BundleIdentifier, + // The sign-in operation has to always be completed in the app. + HandleCodeInApp = true + }; + ``` + + To learn more on `ActionCodeSettings`, refer to the [Passing State in Email Actions](#passing-state/continue-url-in-email-actions) section. +2. Ask the user for their email. +3. Send the authentication link to the user's email, and save the user's email in case the user completes the email sign-in on the same device. + ```csharp + Auth.DefaultInstance.SendSignInLink (email, actionCodeSettings, HandleSendSignInLinkToEmail); + + void HandleSendSignInLinkToEmail (NSError error) + { + if (error != null) { + Console.WriteLine (error.LocalizedDescription); + return; + } + + // The link was successfully sent. Inform the user. + // Save the email locally so you don't need to ask the user for it again + // if they open the link on the same device. + NSUserDefaults.StandardUserDefaults.SetString (email, "Email"); + Console.WriteLine ("Check your email for link."); + } + + // Async/await + + try { + await Auth.DefaultInstance.SendSignInLinkAsync (email, actionCodeSettings); + + // The link was successfully sent. Inform the user. + // Save the email locally so you don't need to ask the user for it again + // if they open the link on the same device. + NSUserDefaults.StandardUserDefaults.SetString (email, "Email"); + Console.WriteLine ("Check your email for link."); + } catch (NSErrorException ex) { + Console.WriteLine (error.LocalizedDescription); + } + ``` + +## Complete sign in with the email link + +### Security concerns + +To prevent a sign-in link from being used to sign in as an unintended user or on an unintended device, Firebase Auth requires the user's email address to be provided when completing the sign-in flow. For sign-in to succeed, this email address must match the address to which the sign-in link was originally sent. + +You can streamline this flow for users who open the sign-in link on the same device they request the link, by storing their email address locally when you send the sign-in email. Then, use this address to complete the flow. + +After sign-in completion, any previous unverified mechanism of sign-in will be removed from the user and any existing sessions will be invalidated. For example, if someone previously created an unverified account with the same email and password, the user's password will be removed to prevent the impersonator who claimed ownership and created that unverified account from signing in again with the same account. + +### Completing sign-in in an iOS mobile app + +Firebase Authentication uses Firebase Dynamic Links to send the email link to a mobile device. For sign-in completion via mobile application, the application has to be configured to detect the incoming application link, parse the underlying deep link and then complete the sign-in. + +### Configuring Firebase Dynamic Links + +Firebase Auth uses [Firebase Dynamic Links][21] when sending a link that is meant to be opened in a mobile application. In order to use this feature, Dynamic Links need to be configured in the Firebase Console. + +1. Enable Firebase Dynamic Links: + 1. In the Firebase console, open the Dynamic Links section. + 2. If you have not yet accepted the Dynamic Links terms and created a Dynamic Links domain, do so now. + + If you already created a Dynamic Links domain, take note of it. A Dynamic Links domain typically looks like the following example: + + ```csharp + example.page.link + ``` + + You will need this value when you configure your iOS or Android app to intercept the incoming link. +2. Configuring iOS applications: + 1. If you plan on handling these links from your iOS appliction, the iOS bundle ID needs to be specified in the Firebase Console project settings. In addition, the App Store ID and the Apple Developer Team ID also need to be specified. + 2. You will also need to configure the FDL universal link domain as an Associated Domain in your application capabilities. + 3. If you plan to distribute your application to iOS versions 8 and under, you will need to set your iOS bundle ID as a custom scheme for incoming URLs. + 4. For more on this, refer to [Receiving iOS Dynamic Links][21] instructions. + +### Verify link and sign in + +After you receive the link as described above, verify that it is meant for email link authentication and complete the sign in. + +```csharp +if (Auth.DefaultInstance.IsSignIn (link)) { + Auth.DefaultInstance.SignInWithLink (email, link, HandleAuthDataResult); +} + +void HandleAuthDataResult (AuthDataResult authResult, NSError error) +{ + // ... +} + +// Async/await + +if (Auth.DefaultInstance.IsSignIn (link)) { + try { + var authResult = await Auth.DefaultInstance.SignInWithLinkAsync (email, link); + + // ... + } catch (NSErrorException ex) { + } +} +``` + +--- + +# Authenticate Using Google Sign-In + +You can let your users authenticate with Firebase using their Google Accounts by integrating Google Sign-In into your app. To be able to use this authentication, please, first integrate [Google Sign-In for iOS][3] to your app. + +Once you have integrated Sign-In into your app, follow these steps to complete your integration with Firebase: + +* If you haven't yet connected your app to your Firebase project, do so from the [Firebase console][1]. +* Enable Google Sign-In in the Firebase console: + * In the [Firebase console][1], open the **Auth** section. + * On the **Sign in method** tab, enable the **Google** sign-in method and click **Save**. +* In your code, in `ISignInDelegate.DidSignIn` method, get a Google ID token and Google access token from the `Authentication` object and exchange them for a Firebase credential and, finally, authenticate with Firebase using the credential: + +```csharp +public void DidSignIn (SignIn signIn, GoogleUser user, NSError error) +{ + if (error == null && user != null) { + // Get Google ID token and Google access token and exchange them for a Firebase credential + var authentication = user.Authentication; + var credential = GoogleAuthProvider.GetCredential (authentication.IdToken, authentication.AccessToken); + + // Authenticate with Firebase using the credential + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + Auth.DefaultInstance.SignInAndRetrieveDataWithCredential (authCredential, HandleAuthDataResult); + } else { + // Print error + } + + void HandleAuthDataResult (AuthDataResult authResult, NSError error) + { + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that SignIn method with email and password could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.UserDisabled: + case AuthErrorCode.WrongPassword: + default: + // Print error + break; + } + + return; + } + + // Do your magic to handle authentication result + } +} +``` + +--- + +# Authenticate Using Facebook Login + +You can let your users authenticate with Firebase using their Facebook accounts by integrating Facebook Login into your app. To be able to use this authentication, please, first integrate [Facebook iOS SDK][4] to your app. + +Once you have integrated Facebook into your app, follow these steps to complete your integration with Firebase: + +* If you haven't yet connected your app to your Firebase project, do so from the [Firebase console][1]. +* On the [Facebook for Developers][5] site, get the **App ID** and an **App Secret** for your app. +* Enable Facebook Login: + * In the [Firebase console][1], open the Auth section. + * On the **Sign in method** tab, enable the **Facebook** sign-in method and specify the **App ID** and **App Secret** you got from Facebook. + * Then, make sure your **OAuth redirect URI** (e.g. `my-app-12345.firebaseapp.com/__/auth/handler`) is listed as one of your **OAuth redirect URIs** in your Facebook app's settings page on the [Facebook for Developers][5] site in the **Products** > **Facebook Login** > **Settings**. +* In your code, after a user successfully signs in, in your `Completed` event or in your `DidComplete` method, get an access token for the signed-in user and exchange it for a Firebase credential and, finally, authenticate with Firebase using the credential: + +```csharp +BtnLogin.Completed += (object sender, LoginButtonCompletedEventArgs e) => { + if (e.Error != null) { + // Handle if there was an error + } + + if (e.Result.IsCancelled) { + // Handle if the user cancelled the login request + } + + // Get access token for the signed-in user and exchange it for a Firebase credential + var credential = FacebookAuthProvider.GetCredential (AccessToken.CurrentAccessToken.TokenString); + + // Authenticate with Firebase using the credential + Auth.DefaultInstance.SignInAndRetrieveDataWithCredential (authCredential, HandleAuthDataResult); + + void HandleAuthDataResult (AuthDataResult authResult, NSError error) + { + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that SignIn method with email and password could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.UserDisabled: + case AuthErrorCode.WrongPassword: + default: + // Print error + break; + } + + return; + } + + // Do your magic to handle authentication result + } +}; +``` + +--- + +# Authenticate Using Twitter Login + +You can let your users authenticate with Firebase using their Twitter accounts by integrating Twitter Login into your app. To be able to use this authentication, please, add [Xamarin.Auth NuGet][7] to your project and follow Xamarin.Auth [Getting Started][8] guide to integrate it into your app. + +Once you have integrated Xamarin.Auth into your app, follow these steps to complete your integration with Firebase: + +* [Register your app][9] as a developer application on Twitter and get your app's **API Key** and **API Secret**. +* Enable Twitter Login in the Firebase console: + * In the [Firebase console][1], open the **Auth** section. + * On the **Sign in method** tab, enable the **Twitter** sign-in method and specify the **API Key** and **API Secret** you got from Twitter. + * Then, make sure your Firebase **OAuth redirect URI** (e.g. `my-app-12345.firebaseapp.com/__/auth/handler`) is set as your **Callback URL** in your app's settings page on your [Twitter app's config][9]. + + Be sure to select **Enable Callback Locking** when you set your redirect URI in the Twitter console. + + > ![warning_icon] _Callback locking, which is disabled by default, is necessary to prevent Firebase security tokens from being intercepted by unauthorized parties._ +* After a user successfully signs in, in your implementation of `Completed`, exchange the Twitter auth token and Twitter auth token secret for a Firebase credential: + +```csharp +// Exchange the Twitter auth token and Twitter auth token secret for a Firebase credential +var credential = TwitterAuthProvider.GetCredential (token, secret); + +// Authenticate with Firebase using the credential +Auth.DefaultInstance.SignIn (credential, HandleAuthResult); + +void HandleAuthResult (User user, NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that SignIn method with email and password could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.UserDisabled: + case AuthErrorCode.WrongPassword: + default: + // Print error + break; + } + + return; + } + + // Do your magic to handle authentication result +} +``` + +***Optional:*** Add an email address to the user's profile. When users sign in to your app with Twitter, their email addresses aren't accessible to Firebase. If you want to add email addresses to the profiles of users that sign in with Twitter, prompt users to provide their email addresses, and then call `UpdateEmail` method as in the following example: + +```csharp +user.UpdateEmail (email, HandleUserProfileChange); + +void HandleUserProfileChange (NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that UpdateEmail method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.EmailAlreadyInUse: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.RequiresRecentLogin: + default: + // Print error + break; + } + + return; + } + + // Email updated. +} +``` + +--- + +# Authenticate Using GitHub + +You can let your users authenticate with Firebase using their GitHub accounts by integrating GitHub authentication into your app. To be able to use this authentication, please, add [Xamarin.Auth NuGet][7] to your project and follow Xamarin.Auth [Getting Started][8] guide to integrate it into your app. + +Once you have integrated Xamarin.Auth into your app, follow these steps to complete your integration with Firebase: + +* [Register your app][10] as a developer application on GitHub and get your app's OAuth 2.0 **Client ID** and **Client Secret**. +* Enable GitHub authentication in the Firebase console: + * In the [Firebase console][1], open the **Auth** section. + * On the **Sign in method** tab, enable the **GitHub** sign-in method and specify the OAuth 2.0 **Client ID** and **Client Secret** you got from GitHub. + * Then, make sure your Firebase **OAuth redirect URI** (e.g. `my-app-12345.firebaseapp.com/__/auth/handler`) is set as your **Authorization callback URL** in your app's settings page on your [GitHub app's config][11]. +* After a user successfully signs in with GitHub, in your implementation of `Completed`, exchange the OAuth 2.0 access token from GitHub for a Firebase credential: + +```csharp +// Exchange the OAuth 2.0 access token from GitHub for a Firebase credential +var credential = GitHubAuthProvider.GetCredential (token); + +// Authenticate with Firebase using the credential +Auth.DefaultInstance.SignIn (credential, HandleAuthResult); + +void HandleAuthResult (User user, NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that SignIn method with email and password could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.UserDisabled: + case AuthErrorCode.WrongPassword: + default: + // Print error + break; + } + + return; + } + + // Do your magic to handle authentication result +} +``` + +--- + +# Authenticate with Firebase on iOS using a Phone Number + +You can use Firebase Authentication to sign in a user by sending an SMS message to the user's phone. The user signs in using a one-time code contained in the SMS message. + +Phone number sign-in requires a physical device and won't work on a simulator. + +This document describes how to implement a phone number sign-in flow using the Firebase SDK. + +> ![note_icon] _**Note:**_ _Phone numbers that end users provide for authentication will be sent and stored by Google to improve our spam and abuse prevention across Google services, including but not limited to Firebase. Developers should ensure they have appropriate end-user consent prior to using the Firebase Authentication phone number sign-in service._ + +## Security concerns + +Authentication using only a phone number, while convenient, is less secure than the other available methods, because possession of a phone number can be easily transferred between users. Also, on devices with multiple user profiles, any user that can receive SMS messages can sign in to an account using the device's phone number. + +If you use phone number based sign-in in your app, you should offer it alongside more secure sign-in methods, and inform users of the security tradeoffs of using phone number sign-in. + +## 1. Enable Phone Number sign-in for your Firebase project + +To sign in users by SMS, you must first enable the Phone Number sign-in method for your Firebase project: + +1. In the [Firebase console][1], open the **Authentication** section. +2. On the **Sign-in Method** page, enable the **Phone Number** sign-in method. + +Firebase's phone number sign-in request quota is high enough that most apps won't be affected. However, if you need to sign in a very high volume of users with phone authentication, you might need to upgrade your pricing plan. See the [pricing][14] page. + +## 2. Enable app verification + +To use phone number authentication, Firebase must be able to verify that phone number sign-in requests are coming from your app. There are two ways Firebase Authentication accomplishes this: + +* **Silent APNs notifications:** When you sign in a user with their phone number for the first time on a device, Firebase Authentication sends a token to the device using a silent push notification. If your app successfully receives the notification from Firebase, phone number sign-in can proceed. + + For iOS 8.0 and newer, silent notifications do not require explicit user consent and is therefore unaffected by a user declining to receive APNs notifications in the app. Thus, the app does not need to request user permission to receive push notifications when implementing Firebase phone number auth. + +* **reCAPTCHA verification**: In the event that sending or receiving a silent push notification is not possible, such as when the user has disabled background refresh for your app, or when testing your app on an iOS simulator, Firebase Authentication uses reCAPTCHA verification to complete the phone sign-in flow. The reCAPTCHA challenge can often be completed without the user having to solve anything. + +When silent push notifications are properly configured, only a very small percentage of users will experience the reCAPTCHA flow. Nonetheless, you should ensure that phone number sign-in functions correctly whether or not silent push notifications are available. + +> ![note_icon] To ensure that both scenarios are working correctly, test your app on a physical iOS device with background app refresh both enabled and disabled. When background app refresh is disabled, you should be able to successfully sign in after completing the reCAPTCHA challenge. You can also test the reCAPTCHA flow by running your app on an iOS simulator, which always uses the reCAPTCHA flow. + +### Start receiving silent notifications + +To enable APNs notifications for use with Firebase Authentication: + +1. In Visual Studio, enable push notifications for your project by opening **Entitlements.plist** and enabling **Push Notifications**. +2. Upload your APNs authentication key to Firebase. If you don't already have an APNs authentication key, see [Configuring APNs with FCM][15]. + 1. Inside your project in the Firebase console, select the gear icon, select **Project Settings**, and then select the **Cloud Messaging** tab. + 2. In **APNs authentication key** under **iOS app configuration**, click the **Upload** button. + 3. Browse to the location where you saved your key, select it, and click **Open**. Add the key ID for the key (available in **Certificates**, **Identifiers** & **Profiles** in the [Apple Developer Member Center][16]) and click **Upload**. + +If you already have an APNs certificate, you can upload the certificate instead. + +### Set up reCAPTCHA verification + +To enable the Firebase SDK to use reCAPTCHA verification: + +1. Add custom URL schemes to your project: + + 1. Open your **Info.plist**: Select the **Advance** tab, and expand the URL Types section. + 2. Click the **Add URL Type** button, and add a **URL scheme** for your **reversed client ID**. To find this value, open the **GoogleService-Info.plist** configuration file, and look for the **REVERSED_CLIENT_ID** key. Copy the value of that key, and paste it into the **URL Schemes** box on the configuration page. Leave the other fields blank. + +2. **Optional:** If you want to customize the way your app presents the `SFSafariViewController` or `UIWebView` when displaying the reCAPTCHA to the user, create a custom class that conforms to the IAuthUIDelegate interface, and pass it to `VerifyPhoneNumber (string, IAuthUIDelegate, VerificationResultHandler)` overload method. + +## 3. Send a verification code to the user's phone + +To initiate phone number sign-in, present the user an interface that prompts them to provide their phone number, and then call `VerifyPhoneNumber` method to request that Firebase send an authentication code to the user's phone by SMS: + +1. Get the user's phone number. + + Legal requirements vary, but as a best practice and to set expectations for your users, you should inform them that if they use phone sign-in, they might receive an SMS message for verification and standard rates apply. + +2. Call `VerifyPhoneNumber`, passing to it the user's phone number: + + ```csharp + PhoneAuthProvider.DefaultInstance.VerifyPhoneNumber (phoneNumber, HandleVerificationResult); + + void HandleVerificationResult (string verificationId, NSError error) + { + if (error != null) { + Console.WriteLine (error.LocalizedDescription); + return; + } + + // Sign in using the verificationID and the code sent to the user + // ... + } + + // async/await version + + try { + string verificationId = await PhoneAuthProvider.DefaultInstance.VerifyPhoneNumberAsync (phoneNumber); + + // Sign in using the verificationID and the code sent to the user + // ... + } catch (NSErrorException ex) { + Console.WriteLine (ex.Error.LocalizedDescription); + } + ``` + + When you call `VerifyPhoneNumber`, Firebase sends a silent push notification to your app or issues a reCAPTCHA challenge to the user. After your app receives the notification or the user completes the reCAPTCHA challenge, Firebase sends an SMS message containing an authentication code to the specified phone number and passes a verification ID to your completion function. You will need both the verification code and the verification ID to sign in the user. + + The SMS message sent by Firebase can also be localized by specifying the auth language via the languageCode property on your Auth instance: + + ```csharp + Auth.DefaultInstance.LanguageCode = "es"; + ``` + +3. Save the verification ID and restore it when your app loads. By doing so, you can ensure that you still have a valid verification ID if your app is terminated before the user completes the sign-in flow (for example, while switching to the SMS app). + + You can persist the verification ID any way you want. A simple way is to save the verification ID with the `NSUserDefaults` object: + + ```csharp + NSUserDefaults.StandardUserDefaults.SetString (verificationId, "authVerificationID"); + ``` + + Then, you can restore the saved value: + + ```csharp + var verificationId = NSUserDefaults.StandardUserDefaults.StringForKey ("AuthVerificationID"); + ``` + +If the call to `VerifyPhoneNumber` succeeds, you can prompt the user to type the verification code when they receive it in the SMS message. + +> ![note_icon] _**Note:**_ _To prevent abuse, Firebase enforces a limit on the number of SMS messages that can be sent to a single phone number within a period of time. If you exceed this limit, phone number verification requests might be throttled. If you encounter this issue during development, use a different phone number for testing, or try the request again later._ + +## 4. Sign in the user with the verification code + +After the user provides your app with the verification code from the SMS message, sign the user in by creating a `PhoneAuthCredential` object from the verification code and verification ID and passing that object to `SignIn (AuthCredential, AuthResultHandler)` method. + +1. Get the verification code from the user. +2. Create a `PhoneAuthCredential` object from the verification code and verification ID: + + ```csharp + var credential = PhoneAuthProvider.DefaultInstance.GetCredential (verificationId, verificationCode); + ``` + +3. Sign in the user with the `PhoneAuthCredential` object: + + ```csharp + Auth.DefaultInstance.SignIn (credential, HandleAuthResult); + + void HandleAuthResult (User user, NSError error) + { + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that SignIn method with email and password could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.UserDisabled: + case AuthErrorCode.WrongPassword: + default: + // Print error + break; + } + + return; + } + + // Do your magic to handle authentication result + } + ``` + +### Test with whitelisted phone numbers + +You can whitelist phone numbers for development via the Firebase console. Whitelisting phone numbers provides these benefits: + +* Test phone number authentication without consuming your usage quota. +* Test phone number authentication without sending an actual SMS message. +* Run consecutive tests with the same phone number without getting throttled. This minimizes the risk of rejection during App store review process if the reviewer happens to use the same phone number for testing. +* Test readily in development environments without any additional effort, such as the ability to develop in an iOS simulator or an Android emulator without Google Play Services. +* Write integration tests without being blocked by security checks normally applied on real phone numbers in a production environment. + +Phone numbers to whitelist must meet these requirements: + +* Make sure you use fictional numbers that do not already exist. Firebase Authentication does not allow you to whitelist existing phone numbers used by real users. One option is to use 555 prefixed numbers as US test phone numbers, for example: +1 650-555-3434 +* Phone numbers have to be correctly formatted for length and other constraints. They will still go through the same validation as a real user's phone number. +* You can add up to 10 phone numbers for development. +* Use test phone numbers/codes that are hard to guess and change those frequently. + +#### Whitelist phone numbers and verification codes + +* In the [Firebase console][1], open the **Authentication section**. +* In the **Sign in method** tab, enable the Phone provider if you haven't already. +* Open the **Phone numbers for testing** accordion menu. +* Provide the phone number you want to test, for example: _+1 650-555-3434_. +* Provide the 6-digit verification code for that specific number, for example: _654321_. +* **Add** the number. If there's a need, you can delete the phone number and its code by hovering over the corresponding row and clicking the trash icon. + +#### Manual testing + +You can directly start using a whitelisted phone number in your application. This allows you to perform manual testing during development stages without running into quota issues or throttling. You can also test directly from an iOS simulator or Android emulator without Google Play Services installed. + +When you provide the whitelisted phone number and send the verification code, no actual SMS is sent. Instead, you need to provide the previously configured verification code to complete the sign in. + +On sign-in completion, a Firebase user is created with that phone number. The user has the same behavior and properties as a real phone number user, and can access Realtime Database/Cloud Firestore and other services the same way. The ID token minted during this process has the same signature as a real phone number user. + +> ![warning_icon] _Because the ID token for the whitelisted phone number has the same signature as a real phone number user, it is important to store these numbers securely and to continuously recycle them._ + +Another option is to [set a test role via custom claims][22] on these users to differentiate them as fake users if you want to further restrict access. + +#### Integration testing + +In addition to manual testing, Firebase Authentication provides APIs to help write integration tests for phone auth testing. These APIs disable app verification by disabling the reCAPTCHA requirement in web and silent push notifications in iOS. This makes automation testing possible in these flows and easier to implement. In addition, they help provide the ability to test instant verification flows on Android. + +> ![note_icon] _Make sure app verification is not disabled for production apps and that no whitelisted phone numbers are hardcoded in your production app._ + +On iOS, the `AppVerificationDisabledForTesting` setting has to be set to `true` before calling `VerifyPhoneNumber`. This is processed without requiring any APNs token or sending silent push notifications in the background, making it easier to test in a simulator. This also disables the reCAPTCHA fallback flow. + +Note that when app verification is disabled, using a non-whitelisted phone number will fail to complete sign in. Only whitelisted phone numbers can be used with this API. + +```csharp +var phoneNumber = "+16505554567"; + +// This test verification code is specified for the given test phone number in the developer console. +var testVerificationCode = "123456"; + +Auth.DefaultInstance.Settings.AppVerificationDisabledForTesting = true; +PhoneAuthProvider.DefaultInstance.VerifyPhoneNumber (phoneNumber, null, HandleVerificationResult); + +void HandleVerificationResult (string verificationId, NSError error) +{ + if (error != null) { + // Handle error + Console.WriteLine (error.LocalizedDescription); + return; + } + + var authCredential = PhoneAuthProvider.DefaultInstance.GetCredential (verificationId ?? "", testVerificationCode); + + Auth.DefaultInstance.SignInAndRetrieveDataWithCredential (authCredential, HandleAuthDataResult); +} + +void HandleAuthDataResult (AuthDataResult authResult, NSError error) +{ + if (error != null) { + // Handle error + Console.WriteLine (error.LocalizedDescription); + return; + } + + // Success +} + +// Async/await + +try { + var verificationId = await PhoneAuthProvider.DefaultInstance.VerifyPhoneNumberAsync (phoneNumber, null); + var authCredential = PhoneAuthProvider.DefaultInstance.GetCredential (verificationId ?? "", testVerificationCode); + var authResult = await Auth.DefaultInstance.SignInAndRetrieveDataWithCredentialAsync (authCredential); + + // Success +} catch (NSErrorException ex) { + // Handle error + Console.WriteLine (error.LocalizedDescription); +} +``` + +## Appendix: Using phone sign-in without swizzling + +Firebase Authentication uses method swizzling to automatically obtain your app's APNs token and to handle the silent push notifications that Firebase sends to your app, and to automatically intercept the custom scheme redirect from the reCAPTCHA verification page during verification. + +If you prefer not to use swizzling, you can disable it by adding the flag **FirebaseAppDelegateProxyEnabled** to your app's Info.plist file and setting it to **No**. Note that setting this flag to **No** also disables swizzling for other Firebase products, including Firebase Cloud Messaging. + +If you disable swizzling, you must explicitly pass the APNs device token and push notifications to Firebase Authentication. + +To obtain the APNs device token, implement the `AppDelegate`'s `RegisteredForRemoteNotifications` method, and in it, pass the device token to `Auth`'s `SetApnsToken` method: + +```csharp +public override void RegisteredForRemoteNotifications (UIApplication application, NSData deviceToken) +{ + // Pass device token to auth + Auth.DefaultInstance.SetApnsToken (deviceToken, AuthApnsTokenType.Production); // Production if you are ready to release your app, otherwise, use Sandbox. + + // Further handling of the device token if needed by the app + // ... +} +``` + +To handle push notifications, in the `AppDelegate`'s `DidReceiveRemoteNotification` method, check for Firebase auth related notifications by calling `Auth`'s `CanHandleNotification` method: + +```csharp +public override void DidReceiveRemoteNotification (UIApplication application, NSDictionary userInfo, Action completionHandler) +{ + // Pass notification to auth and check if they can handle it. + if (Auth.DefaultInstance.CanHandleNotification (userInfo)) { + completionHandler (UIBackgroundFetchResult.NoData); + return; + } + + // This notification is not auth related, developer should handle it. +} +``` + +To handle the custom scheme redirect URL, implement the `OpenUrl (UIApplication, NSUrl, string, NSObject)` overload method for devices running iOS 8 and older, and the `OpenUrl (UIApplication, NSUrl, NSDictionary)` overload method for devices running iOS 9 and newer, and in them, pass the URL to `Auth`'s `CanHandleURrl` method: + +```csharp +// Support for iOS 9 or later +public override bool OpenUrl (UIApplication app, NSUrl url, NSDictionary options) +{ + var openUrlOptions = new UIApplicationOpenUrlOptions (options); + return OpenUrl (app, url, openUrlOptions.SourceApplication, openUrlOptions.Annotation); +} + +// Support for iOS 8 or before +public override bool OpenUrl (UIApplication application, NSUrl url, string sourceApplication, NSObject annotation) +{ + if (Auth.DefaultInstance.CanHandleUrl (url)) { + return true; + } + + // URL not auth related, developer should handle it. +} +``` + +--- + +# Authenticate with Firebase Using a Custom Authentication System + +You can integrate Firebase Authentication with a custom authentication system by modifying your authentication server to produce custom signed tokens when a user successfully signs in. Your app receives this token and uses it to authenticate with Firebase. + +Follow these steps to complete your integration with Firebase: + +* Get your project's server keys: + * Go to the [Service Accounts][6] page in your project's settings. + * Click _Generate New Private Key_ at the bottom of the _Firebase Admin SDK section of the Service Accounts_ page + * The new service account's public/private key pair is automatically saved on your computer. Copy this file to your authentication server. +* When users sign in to your app, send their sign-in credentials (for example, their username and password) to your authentication server. Your server checks the credentials and returns a [custom token][17] if they are valid. +* After you receive the custom token from your authentication server, pass it to `SignIn` method to sign in the user: + +```csharp +Auth.DefaultInstance.SignIn (credential, HandleAuthResult); + +void HandleAuthResult (User user, NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that SignIn method with email and password could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.UserDisabled: + case AuthErrorCode.WrongPassword: + default: + // Print error + break; + } + + return; + } + + // Do your magic to handle authentication result +} +``` + +--- + +# Authenticate with Firebase Anonymously + +You can use Firebase Authentication to create and use temporary anonymous accounts to authenticate with Firebase. These temporary anonymous accounts can be used to allow users who haven't yet signed up to your app to to work with data protected by security rules. If an anonymous user decides to sign up to your app, you can [link their sign-in credentials to the anonymous account](#link-multiple-auth-providers-to-an-account) so that they can continue to work with their protected data in future sessions. + +Follow these steps to complete your integration with Firebase: + +* If you haven't yet connected your app to your Firebase project, do so from the [Firebase console][1]. +* Enable anonymous auth in the Firebase console: + * In the [Firebase console][1], open the **Auth** section. + * On the **Sign in method** tab, enable the **Anonymous** sign-in method and click **Save**. +* When a signed-out user uses an app feature that requires authentication with Firebase, sign in the user anonymously by using `Auth.SignInAnonymously` instance method: + +```csharp +Auth.DefaultInstance.SignInAnonymously (HandleAuthResultHandler); + +void HandleAuthResultHandler (User user, NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that SignInAnonymously method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.OperationNotAllowed: + default: + // Print error + break; + } + + return; + } + + // Do your magic to handle authentication result +} + +// async/await version + +try { + User user = await Auth.DefaultInstance.SignInAnonymouslyAsync (); + // Do your magic to handle authentication result +} catch (NSErrorException ex) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)ex.Error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)ex.Error.Code); + + // Posible error codes that SignInAnonymously method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.OperationNotAllowed: + default: + // Print error + break; + } +} +``` + +After a successful login, you can get the anonymous user's account data from the `User` object: + +```csharp +bool isAnonymous = user.IsAnonymous; +string uid = user.Uid; +``` + +--- + +# Passing State in Email Actions + +You can pass state via a continue URL when sending email actions for password resets or verifying a user's email. This provides the user the ability to be returned to the app after the action is completed. In addition, you can specify whether to handle the email action link directly from a mobile application when it is installed instead of a web page. + +This can be extremely useful in the following common scenarios: + +* A user, not currently logged in, may be trying to access content that requires the user to be signed in. However, the user might have forgotten their password and therefore trigger the reset password flow. At the end of the flow, the user expects to go back to the section of the app they were trying to access. +* An application may only offer access to verified accounts. For example, a newsletter app may require the user to verify their email before subscribing. The user would go through the email verification flow and expect to be returned to the app to complete their subscription. +* In general, when a user begins a password reset or email verification flow on an iOS app they expect to complete the flow within the app; the ability to pass state via continue URL makes this possible. + +Having the ability to pass state via a continue URL is a powerful feature that Firebase Auth provides and which can significantly enhance the user experience. + +## Passing state/continue URL in email actions + +In order to securely pass a continue URL, the domain for the URL will need to be whitelisted in the [Firebase console][1]. This is done in the **Authentication** section by adding this domain to the list of **OAuth redirect domains** if it is not already there. + +A `ActionCodeSettings` instance needs to be provided when sending a password reset email or a verification email. This interface takes the following parameters: + +| Parameter | Type | Description | +|----------------------------------|--------|-------------| +| **Url** | String | Sets the link (state/continue URL) which has different meanings in different contexts:
  • When the link is handled in the web action widgets, this is the deep link in the **continueUrl** query parameter.

  • When the link is handled in the app directly, this is the **continueUrl** query parameter in the deep link of the Dynamic Link.
| +| **IOSBundleID** | String | Sets the iOS bundle ID. This will try to open the link in an iOS app if it is installed. The iOS app needs to be registered in the Console. If no Bundle ID is provided, the value of this field is set to the bundle ID of the App's main bundle. | +| **AndroidPackageName** | String | Sets the Android package name. This will try to open the link in an android app if it is installed. | +| **AndroidInstallIfNotAvailable** | Bool | Specifies whether to install the Android app if the device supports it and the app is not already installed. If this field is provided without a packageName, an error is thrown explaining that the |packageName must be provided in conjunction with this field. | +| **AndroidMinimumVersion** | String | The minimum version of the app that is supported in this flow. If minimumVersion is specified, and an older version of the app is installed, the user is taken to the Play Store to upgrade the app. The Android app needs to be registered in the Console. | +| **HandleCodeInApp** | Bool | Whether the email action link will be opened in a mobile app or a web link first. The default is false. When set to true, the action code link will be be sent as a Universal Link or Android App Link and will be opened by the app if installed. In the false case, the code will be sent to the web widget first and then on continue will redirect to the app if installed. | + +The following example illustrates how to send an email verification link that will open in a mobile app first as a Firebase Dynamic Link (iOS app `com.example.ios` or Android app `com.example.android` where the app will install if not already installed and the minimum version is `12`). The deep link will contain the continue URL payload `https://www.example.com/?email=user@example.com`. + +```csharp +var user = Auth.DefaultInstance.CurrentUser; + +var actionCodeSettings = new ActionCodeSettings { + HandleCodeInApp = true, + Url = new NSUrl ($"https://www.example.com/?email={user.Email}"), + IOSBundleId = NSBundle.MainBundle.BundleIdentifier +}; +actionCodeSettings.SetAndroidPackageName ("com.example.android", true, "12"); + +user.SendEmailVerification (actionCodeSettings,HandleSendEmailVerification); + +void HandleSendEmailVerification (NSError error) +{ + if (error != null) { + // Error occurred. Inspect error.code and handle error. + return; + } + + // Email verification sent. +} + +// async/await version + +try { + await user.SendEmailVerificationAsync (actionCodeSettings); + // Email verification sent. +} catch (NSErrorException ex) { + // Error occurred. Inspect error.code and handle error. +} +``` + +## Configuring Firebase Dynamic Links + +Firebase Auth uses [Firebase Dynamic Links][18] when sending a link that is meant to be opened in a mobile application. In order to use this feature, Dynamic Links need to be configured in the Firebase Console. + +1. Enable Firebase Dynamic Links: + 1. In the [Firebase console][1], open the **Dynamic Links** section. + 2. If you have not yet accepted the Dynamic Links terms, select **Get Started**. Go back to the main Dynamic Links dashboard. + 3. Take note of your **Dynamic Link Domain**. It should look as follows: **abc123.app.goo.gl**. This will needed when configuring the Android or iOS app to intercept the incoming link. +2. Configuring iOS applications: + 1. If you plan on handling these links from your iOS appliction, the iOS bundle ID needs to be specified in the Firebase Console project settings. In addition, the App Store ID and the Apple Developer Team ID also need to be specified. + 2. You will also need to configure the FDL universal link domain as an Associated Domain in your application capabilities. + 3. If you plan to distribute your application to iOS versions 8 and under, you will need to set your iOS bundle ID as a custom scheme for incoming URLs. + +## Handling email actions in a web application + +You can specify whether you want to handle the action code link from a web application first and then redirect to another web page or mobile application after successful completion, provided the mobile application is available. This is done by setting `HandleCodeInApp` to `false` in the `ActionCodeSettings` object. While an iOS bundle ID or Android package name are not required, providing them will allow the user to redirect back to the specified app on email action code completion. + +The web URL used here, is the one configured in the email action templates section. A default one is provisioned for all projects. Refer to [customizing email handlers](#create–custom-email-action-handlers) to learn more on how to customize the email action handler. + +In this case, the link within the `continueURL` query parameter will be an FDL link whose payload is the `URL` specified in the ActionCodeSettings object. While you can intercept and handle the incoming link from your app without any additional dependency, we recommend using the FDL client library to parse the deep link for you. + +## Handling email actions in a mobile application + +You can specify whether you want to handle the action code link within your mobile application first, provided it is installed. With Android applications, you also have the ability to specify via the `AndroidInstallIfNotAvailable` that the app is to be installed if the device supports it and it is not already installed. If the link is clicked from a device that does not support the mobile application, it is opened from a web page instead. This is done by setting handleCodeInApp to true in the FIRActionCodeSettings (Obj-C) or ActionCodeSettings (Swift) object. The mobile application's Android package name or iOS bundle ID will also need to be specified.The fallback web URL used here, when no mobile app is available, is the one configured in the email action templates section. A default one is provisioned for all projects. Refer to [customizing email handlers](#create–custom-email-action-handlers) to learn more on how to customize the email action handler. + +In this case, the mobile app link sent to the user will be an FDL link whose payload is the action code URL, configured in the Console, with the query parameters `oobCode`, `mode`, `apiKey` and `continueUrl`. The latter will be the original URL specified in the `ActionCodeSettings` object. While you can intercept and handle the incoming link from your app without any additional dependency, we recommend using the FDL client library to parse the deep link for you. The action code can be applied directly from a mobile application similar to how it is handled from the web flow described in the [customizing email handlers](#create–custom-email-action-handlers) section. + +--- + +# Convert an anonymous account to a permanent account + +When an anonymous user signs up to your app, you might want to allow them to continue their work with their new account—for example, you might want to make the items the user added to their shopping cart before they signed up available in their new account's shopping cart. To do so, complete the following steps: + +1. When the user signs up, complete the sign-in flow for the user's authentication provider up to, but not including, calling one of the `Auth.SignIn` methods. +2. Get an `AuthCredential` for the new authentication provider +3. Pass the `AuthCredential` object to the sign-in user's `Link` method: + +```csharp +user.Link (credential, HandleAuthResult); + +void HandleAuthResult (User user, NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that Link method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.ProviderAlreadyLinked: + case AuthErrorCode.CredentialAlreadyInUse: + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.EmailAlreadyInUse: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.RequiresRecentLogin: + case AuthErrorCode.WeakPassword: + default: + // Print error + break; + } + + return; + } + + // Do your magic to handle link result +} + +// async/await version + +try { + User user = user.Link (credential, HandleAuthResult); + // Do your magic to handle link result +} catch (NSErrorException ex) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)ex.Error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)ex.Error.Code); + + // Posible error codes that Link method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.ProviderAlreadyLinked: + case AuthErrorCode.CredentialAlreadyInUse: + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.EmailAlreadyInUse: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.RequiresRecentLogin: + case AuthErrorCode.WeakPassword: + default: + // Print error + break; + } +} +``` + +If the call to `Link` succeeds, the user's new account can access the anonymous account's Firebase data. + +> ![note_icon] This technique can also be used to [link any two accounts](#link-multiple-auth-providers-to-an-account). + +--- + +# Link Multiple Auth Providers to an Account + +You can allow users to sign in to your app using multiple authentication providers by linking auth provider credentials to an existing user account. Users are identifiable by the same Firebase user ID regardless of the authentication provider they used to sign in. For example, a user who signed in with a password can link a Google account and sign in with either method in the future. Or, an anonymous user can link a Facebook account and then, later, sign in with Facebook to continue using your app. + +## Link auth provider credentials to a user account + +To link auth provider credentials to an existing user account: + +1. Sign in the user using any authentication provider or method. +2. Complete the sign-in flow for the new authentication provider up to, but not including, calling one of the `SignIn` methods. +3. Get a `AuthCredential` for the new authentication provider. +4. Pass the `AuthCredential` object to the signed-in user's `Link` method: + +```csharp +var currentUser = Auth.DefaultInstance.CurrentUser; +currentUser.Link (credential, HandleAuthResultHandler); + +void HandleAuthResultHandler (User user, NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that Link method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.ProviderAlreadyLinked: + case AuthErrorCode.CredentialAlreadyInUse: + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.EmailAlreadyInUse: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.RequiresRecentLogin: + case AuthErrorCode.WeakPassword: + default: + // Print error + break; + } + + return; + } + + // Do your magic to handle link result +} + +// async/await version + +var currentUser = Auth.DefaultInstance.CurrentUser; + +try { + User user = currentUser.LinkAsync (credential); + // Do your magic to handle link result +} catch (NSErrorException ex) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)ex.Error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)ex.Error.Code); + + // Posible error codes that Link method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.ProviderAlreadyLinked: + case AuthErrorCode.CredentialAlreadyInUse: + case AuthErrorCode.OperationNotAllowed: + case AuthErrorCode.EmailAlreadyInUse: + case AuthErrorCode.InvalidEmail: + case AuthErrorCode.RequiresRecentLogin: + case AuthErrorCode.WeakPassword: + default: + // Print error + break; + } +} +``` + +The call to `Link` will fail if the credentials are already linked to another user account. In this situation, you must handle merging the accounts and associated data as appropriate for your app: + +```csharp +var previousUser = Auth.DefaultInstance.CurrentUser; +Auth.DefaultInstance.SignIn (credential, (user, error) => { + // ... +}); +var currentUser = Auth.DefaultInstance.CurrentUser; + +// Merge previousUser and currentUser accounts and data +// ... +``` + +If the call to `Link` succeeds, the user can now sign in using any linked authentication provider and access the same Firebase data. + +## Unlink an auth provider from a user account + +You can unlink an auth provider from an account, so that the user can no longer sign in with that provider. + +To unlink an auth provider from a user account, pass the provider ID to the `Unlink` method. You can get the provider IDs of the auth providers linked to a user from the `ProviderData` property. + +```csharp +var currentUser = Auth.DefaultInstance.CurrentUser; +currentUser .Unlink (providerId, HandleAuthResult); + +void HandleAuthResult (User user, NSError error) +{ + if (error != null) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)error.Code); + + // Posible error codes that Unlink method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.NoSuchProvider: + case AuthErrorCode.RequiresRecentLogin: + default: + // Print error + break; + } + } + + // Provider unlinked from account +} + +// async/await version + +try { + User user = currentUser.UnlinkAsync (providerId); + // Provider unlinked from account +} catch (NSErrorException ex) { + AuthErrorCode errorCode; + if (IntPtr.Size == 8) // 64 bits devices + errorCode = (AuthErrorCode)((long)ex.Error.Code); + else // 32 bits devices + errorCode = (AuthErrorCode)((int)ex.Error.Code); + + // Posible error codes that Unlink method could throw + // Visit https://firebase.google.com/docs/auth/ios/errors for more information + switch (errorCode) { + case AuthErrorCode.NoSuchProvider: + case AuthErrorCode.RequiresRecentLogin: + default: + // Print error + break; + } +} + +``` + +--- + +# Create custom email action handlers + +Some user management actions, such as updating a user's email address and resetting a user's password, result in emails being sent to the user. These emails contain links that recipients can open to complete or cancel the user management action. By default, user management emails link to the default action handler, which is a web page hosted at a URL in your project's Firebase Hosting domain. + +To learn more about this, please, read the following [documentation][19]. + +--- + +# Extend Firebase Authentication with Cloud Functions + +You can trigger a function in response to the creation and deletion of user accounts via Firebase Authentication. For example, you could send a welcome email to a user who has just created an account in your app. + +To learn more about this, please, read the following [documentation][20]. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/auth/ios/manage-users) to see original Firebase documentation._ + +[1]: https://firebase.google.com/console/ +[2]: http://support.google.com/firebase/answer/7015592 +[3]: https://components.xamarin.com/gettingstarted/googleiossignin +[4]: https://components.xamarin.com/gettingstarted/facebookios +[5]: https://developers.facebook.com/ +[6]: https://console.firebase.google.com/u/0/project/_/settings/serviceaccounts/adminsdk +[7]: https://www.nuget.org/packages/xamarin.auth +[8]: https://components.xamarin.com/gettingstarted/xamarin.auth +[9]: https://apps.twitter.com/ +[10]: https://github.com/settings/applications/new +[11]: https://github.com/settings/developers +[12]: https://support.google.com/firebase/answer/7000714 +[14]: https://firebase.google.com/pricing/ +[15]: https://firebase.google.com/docs/cloud-messaging/ios/certs +[16]: https://developer.apple.com/membercenter/index.action +[17]: https://firebase.google.com/docs/auth/admin/create-custom-tokens +[18]: https://components.xamarin.com/view/firebaseiosdynamiclinks +[19]: https://firebase.google.com/docs/auth/custom-email-handler +[20]: https://firebase.google.com/docs/auth/ios/account-linking +[21]: https://github.com/xamarin/GoogleApisForiOSComponents/blob/master/Firebase.DynamicLinks/component/GettingStarted.md +[22]: https://firebase.google.com/docs/auth/admin/custom-claims +[note_icon]: https://cdn3.iconfinder.com/data/icons/UltimateGnome/22x22/apps/gnome-app-install-star.png +[warning_icon]: https://cdn2.iconfinder.com/data/icons/freecns-cumulus/32/519791-101_Warning-20.png diff --git a/docs/Firebase/CloudFirestore/Details.md b/docs/Firebase/CloudFirestore/Details.md new file mode 100755 index 00000000..659b8b9c --- /dev/null +++ b/docs/Firebase/CloudFirestore/Details.md @@ -0,0 +1,27 @@ +Use Firebase flexible, scalable NoSQL cloud database to store and sync data for client- and server-side development. + +Firebase Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud Platform. Like Firebase Database, it keeps your data in sync across client apps through realtime listeners and offers offline support for mobile and web so you can build responsive apps that work regardless of network latency or Internet connectivity. Firebase Cloud Firestore also offers seamless integration with other Firebase and Google Cloud Platform products, including Cloud Functions. + +_**Note:**_ _Firebase Cloud Firestore is currently in beta release. Feature availability and support for product integrations and platforms will continue to improve as the product matures. For more information on existing limitations in Cloud Firestore, see the [limits and quotas documentation](https://firebase.google.com/docs/firestore/quotas)._ + +## Key capabilities + +| | | +|--:|---| +| Flexibility | The Firebase Cloud Firestore data model supports flexible, hierarchical data structures. Store your data in documents, organized into collections. Documents can contain complex nested objects in addition to subcollections. | +| Expressive querying | In Firebase Cloud Firestore, you can use queries to retrieve individual, specific documents or to retrieve all the documents in a collection that match your query parameters. Your queries can include multiple, chained filters and combine filtering and sorting. They're also indexed by default, so query performance is proportional to the size of your result set, not your data set. | +| Realtime updates | Like Firebase Database, Firebase Cloud Firestore uses data synchronization to update data on any connected device. However, it's also designed to make simple, one-time fetch queries efficiently. | +| Offline support | Firebase Cloud Firestore caches data that your app is actively using, so the app can write, read, listen to, and query data even if the device is offline. When the device comes back online, Firebase Cloud Firestore synchronizes any local changes back to Firebase Cloud Firestore. | +| Designed to scale | Firebase Cloud Firestore brings you the best of Google Cloud Platform's powerful infrastructure: automatic multi-region data replication, strong consistency guarantees, atomic batch operations, and real transaction support. We've designed Firebase Cloud Firestore to handle the toughest database workloads from the world's biggest apps. | + +## How does it work? + +Firebase Cloud Firestore is a cloud-hosted, NoSQL database that your iOS, Android, and web apps can access directly via native SDKs. + +Following Firebase Cloud Firestore's NoSQL data model, you store data in documents that contain fields mapping to values. These documents are stored in collections, which are containers for your documents that you can use to organize your data and build queries. Documents support many different [data types](https://firebase.google.com/docs/firestore/manage-data/data-types), from simple strings and numbers, to complex, nested objects. You can also create subcollections within documents and build hierarchical data structures that scale as your database grows. The Firebase Cloud Firestore data model supports whatever data structure works best for your app. + +Additionally, querying in Firebase Cloud Firestore is expressive, efficient, and flexible. Create shallow queries to retrieve data at the document level without needing to retrieve the entire collection, or any nested subcollections. Add sorting, filtering, and limits to your queries or cursors to paginate your results. To keep data in your apps current, without retrieving your entire database each time an update happens, add realtime listeners. Adding realtime listeners to your app notifies you with a data snapshot whenever the data your client apps are listening to changes, retrieving only the new changes. + +Protect access to your data in Firebase Cloud Firestore with Firebase Authentication and Firebase Cloud Firestore Security Rules for Android, iOS, and JavaScript, or Identity and Access Management (IAM) for server-side languages. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/firestore/) to see original Firebase documentation._ \ No newline at end of file diff --git a/docs/Firebase/CloudFirestore/GettingStarted.md b/docs/Firebase/CloudFirestore/GettingStarted.md new file mode 100755 index 00000000..0f15d2c9 --- /dev/null +++ b/docs/Firebase/CloudFirestore/GettingStarted.md @@ -0,0 +1,1560 @@ +# Firebase Cloud Firestore on iOS + +## Table of content + +- [Firebase Cloud Firestore on iOS](#firebase-cloud-firestore-on-ios) + - [Table of content](#table-of-content) + - [Create a Cloud Firestore project](#create-a-cloud-firestore-project) + - [Add Firebase to your app](#add-firebase-to-your-app) + - [Configure Remote Config in your app](#configure-remote-config-in-your-app) +- [Add and Manage Data](#add-and-manage-data) + - [Cloud Firestore Data Model](#cloud-firestore-data-model) + - [Documents](#documents) + - [Collections](#collections) + - [References](#references) + - [Hierarchical Data](#hierarchical-data) + - [Subcollections](#subcollections) + - [Choose a Data Structure](#choose-a-data-structure) + - [Supported Data Types](#supported-data-types) + - [Add Data to Cloud Firestore](#add-data-to-cloud-firestore) + - [Set a document](#set-a-document) + - [Data types](#data-types) + - [Add a document](#add-a-document) + - [Update a document](#update-a-document) + - [Update fields in nested objects](#update-fields-in-nested-objects) + - [Transactions and Batched Writes](#transactions-and-batched-writes) + - [Updating data with transactions](#updating-data-with-transactions) + - [Passing information out of transactions](#passing-information-out-of-transactions) + - [Transaction failure](#transaction-failure) + - [Batched writes](#batched-writes) + - [Delete Data from Cloud Firestore](#delete-data-from-cloud-firestore) + - [Delete fields](#delete-fields) + - [Delete collections](#delete-collections) + - [Delete data with the Firebase CLI](#delete-data-with-the-firebase-cli) + - [Manage Cloud Firestore with the Firebase Console](#manage-cloud-firestore-with-the-firebase-console) +- [Query Data](#query-data) + - [Get Data with Cloud Firestore](#get-data-with-cloud-firestore) + - [Example data](#example-data) + - [Get a document](#get-a-document) + - [Get multiple documents from a collection](#get-multiple-documents-from-a-collection) + - [Get all documents in a collection](#get-all-documents-in-a-collection) + - [Get Realtime Updates with Cloud Firestore](#get-realtime-updates-with-cloud-firestore) + - [Events for local changes](#events-for-local-changes) + - [Listen to multiple documents in a collection](#listen-to-multiple-documents-in-a-collection) + - [View changes between snapshots](#view-changes-between-snapshots) + - [Detach a listener](#detach-a-listener) + - [Handle listen errors](#handle-listen-errors) + - [Perform Simple and Compound Queries in Cloud Firestore](#perform-simple-and-compound-queries-in-cloud-firestore) + - [Example data](#example-data) + - [Simple queries](#simple-queries) + - [Compound queries](#compound-queries) + - [Order and Limit Data with Cloud Firestore](#order-and-limit-data-with-cloud-firestore) + - [Order and limit data](#order-and-limit-data) + - [Paginate Data with Query Cursors](#paginate-data-with-query-cursors) + - [Add a simple cursor to a query](#add-a-simple-cursor-to-a-query) + - [Use a document snapshot to define the query cursor](#use-a-document-snapshot-to-define-the-query-cursor) + - [Paginate a query](#paginate-a-query) + - [Set multiple cursor conditions](#set-multiple-cursor-conditions) + - [Cities](#cities) + - [Manage Indexes in Cloud Firestore](#manage-indexes-in-cloud-firestore) +- [Secure Data](#secure-data) + - [Secure Data in Cloud Firestore](#secure-data-in-cloud-firestore) +- [Enable Offline Data](#enable-offline-data) + - [Configure offline persistence](#configure-offline-persistence) + - [Listen to offline data](#listen-to-offline-data) + - [Get offline data](#get-offline-data) + - [Query offline data](#query-offline-data) + +## Create a Cloud Firestore project + +* Open the [Firebase Console][1] and create a new project. +* In the *Database* section, click on **Realtime Database** combobox and then **Try Firestore Beta**. +* Click Enable. + +> ![note_icon] _**Note:**_ _Cloud Firestore and App Engine: You can't use both Cloud Firestore and Cloud Datastore in the same project, which might affect apps using App Engine. Try using Cloud Firestore with a different project._ + +When you create a Cloud Firestore project, it also enables the API in the [Cloud API Manager][]. + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][1], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][2]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][2] again at any time. + +## Configure Remote Config in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Xamarin Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Add the following line of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` namespace): + +```csharp +App.Configure (); +``` + +--- + +# Add and Manage Data + +## Cloud Firestore Data Model + +Cloud Firestore is a NoSQL, document-oriented database. Unlike a SQL database, there are no tables or rows. Instead, you store data in documents, which are organized into collections. + +Each document contains a set of key-value pairs. Cloud Firestore is optimized for storing large collections of small documents. + +All documents must be stored in collections. Documents can contain subcollections and nested objects, both of which can include primitive fields like strings or complex objects like lists. + +Collections and documents are created implicitly in Cloud Firestore. Simply assign data to a document within a collection. If either the collection or document does not exist, Cloud Firestore creates it. + +### Documents + +In Cloud Firestore, the unit of storage is the document. A document is a lightweight record that contains fields, which map to values. Each document is identified by a name. + +A document representing a user `alovelace` might look like this: + +![document_icon] **alovelace** +* **first** : "Ada" +* **last** : Lovelace +* **born** : 1815 + +> ![note_icon] _**Note:**_ _Cloud Firestore supports a variety of data types for values: boolean, number, string, geo point, binary blob, and timestamp. You can also use arrays or nested objects, called maps, to structure data within a document._ + +Complex, nested objects in a document are called maps. For example, you could structure the user's name from the example above with a map, like this: + +![document_icon] **alovelace** +* **name**: + * **first** : "Ada" + * **last** : Lovelace +* **born** : 1815 + +You may notice that documents look a lot like JSON. In fact, they basically are. There are some differences (for example, documents support extra data types and are limited in size to 1 MB), but in general, you can treat documents as lightweight JSON records. + +### Collections + +Documents live in collections, which are simply containers for documents. For example, you could have a users collection to contain your various users, each represented by a document: + +![collections_icon] **users** +* ![document_icon] **alovelace** + * **first** : "Ada" + * **last** : "Lovelace" + * **born** : 1815 +* ![document_icon] **aturing** + * **first** : "Alan" + * **last** : "Turing" + * **born** : 1912 + +Cloud Firestore is schemaless, so you have complete freedom over what fields you put in each document and what data types you store in those fields. Documents within the same collection can all contain different fields or store different types of data in those fields. However, it's a good idea to use the same fields and data types across multiple documents, so that you can query the documents more easily. + +A collection contains documents and nothing else. It can't directly contain raw fields with values, and it can't contain other collections. (See [Hierarchical Data](#hierarchical-data) for an explanation of how to structure more complex data in Cloud Firestore.) + +The names of documents within a collection are unique. You can provide your own keys, such as user IDs, or you can let Cloud Firestore create random IDs for you automatically (for example, by calling `Add` method). + +You do not need to "create" or "delete" collections. After you create the first document in a collection, the collection exists. If you delete all of the documents in a collection, it no longer exists. + +### References + +Every document in Cloud Firestore is uniquely identified by its location within the database. The previous example showed a document `alovelace` within the collection `users`. To refer to this location in your code, you can create a _reference_ to it: + +```csharp +var alovelaceDocument = db.GetCollection ("users").GetDocument ("alovelace"); +``` + +A reference is a lightweight object that just points to a location in your database. You can create a reference whether or not data exists there, and creating a reference does not perform any network operations. + +You can also create references to collections: + +```csharp +var userCollection = db.GetCollection ("users"); +``` + +> ![note_icon] _**Note:**_ _Collection references and document references are two distinct types of references and let you perform different operations. For example, you could use a collection reference for querying the documents in the collection, and you could use a document reference to read or write an individual document._ + +For convenience, you can also create references by specifying the path to a document or collection as a string, with path components separated by a forward slash (/). For example, to create a reference to the `alovelace` document: + +```csharp +var alovelaceDocument = db.GetDocument ("users/alovelace"); +``` + +### Hierarchical Data + +To understand how hierarchical data structures work in Cloud Firestore, consider an example chat app with messages and chat rooms. + +You can create a collection called `rooms` to store different chat rooms: + +![collections_icon] **rooms** +* ![document_icon] **roomA** + * **name** : "my chat room" +* ![document_icon] **roomB** + * ... + +Now that you have chat rooms, decide how to store your messages. You might not want to store them in the chat room's document. Documents in Cloud Firestore should be lightweight, and a chat room could contain a large number of messages. However, you can create additional collections within your chat room's document, as subcollections. + +#### Subcollections + +The best way to store messages in this scenario is by using subcollections. A subcollection is a collection associated with a specific document. + +> ![note_icon] _**Note:**_ _Querying across subcollections is not currently supported in Cloud Firestore. If you need to query data across collections, use root-level collections._ + +You can create a subcollection called `messages` for every room document in your `rooms` collection: + +![collections_icon] **rooms** +* ![document_icon] **roomA** + * **name** : "my chat room" + * ![collections_icon] **messages** + * ![document_icon] **message1** + * **from** : "alex" + * **msg** : "Hello World!" + * ![document_icon] **message2** + * ... +* ![document_icon] **roomB** + * ... + +In this example, you would create a reference to a message in the subcollection with the following code: + +```csharp +var message = db.GetCollection ("rooms").GetDocument ("roomA") + .GetCollection ("messages").GetDocument ("message1"); +``` + +Notice the alternating pattern of collections and documents. Your collections and documents must always follow this pattern. You cannot reference a collection in a collection or a document in a document. + +Subcollections allow you to structure data hierarchically, making data easier to access. To get all messages in `roomA`, you can simply access the `db.GetCollection ("rooms").GetDocument ("roomA").GetCollection ("messages");` collection. + +Documents in subcollections can contain subcollections as well, allowing you to further nest data. You can nest data up to 100 levels deep. + +> ![warning_icon] _**Warning:**_ _Deleting a document does not delete its subcollections!_ +> +> _When you delete a document that has associated subcollections, the subcollections are not deleted. They are still accessible by reference. For example, there may be a document referenced by `db.GetCollection ("coll").GetDocument ("doc").GetCollection ("subcoll").GetDocument ("subdoc")` even though the document referenced by `db.GetCollection ("coll").GetDocument ("doc")` no longer exists. If you want to delete documents in subcollections when deleting a document, you must do so manually, as shown in [Delete Collections](#delete_collections)._ + +## Choose a Data Structure + +Remember, when you structure your data in Cloud Firestore, you have a few different options: documents, multiple collections, and subcollections within documents. Read this [documentation][3] to learn more about this. Consider the advantages of each option as they relate to your use case. + +## Supported Data Types + +This [page][4] describes the data types that Cloud Firestore supports. + +## Add Data to Cloud Firestore + +There are several ways to write data to Cloud Firestore: + +* Set the data of a document within a collection, explicitly specifying a document identifier. +* Add a new document to a collection. In this case, Cloud Firestore automatically generates the document identifier. +* Create an empty document with an automatically generated identifier, and assign data to it later. + +This guide explains how to use the set, add, or update individual documents in Cloud Firestore. If you want to write data in bulk, see [Transactions and Batched Writes](#transactions-and-batched-writes). + +### Set a document + +To create or overwrite a single document, use the `SetData` method: + +```csharp +var docData = new Dictionary { + { "name", "Los Angeles" }, + { "state", "CA" }, + { "country", "USA" } +}; + +// Add a new document in collection "cities" +db.GetCollection ("cities").GetDocument ("LA").SetData (docData, HandleDocumentActionCompletion); + +void HandleDocumentActionCompletion (NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error writing document: {error.LocalizedDescription}"); + return; + } + + System.Console.WriteLine ("Document successfully written!"); +} +``` + +Also, you can use `SetDataAsync` method for `await/async` lovers: + +```csharp +var docData = new Dictionary { + { "name", "Los Angeles" }, + { "state", "CA" }, + { "country", "USA" } +}; + +// Add a new document in collection "cities" +try { + await db.GetCollection ("cities").GetDocument ("LA").SetDataAsync (docData); + System.Console.WriteLine ("Document successfully written!"); +} catch (NSErrorException ex) { + System.Console.WriteLine ($"Error writing document: {ex.Error.LocalizedDescription}"); +} +``` + +If the document does not exist, it will be created. If the document does exist, its contents will be overwritten with the newly provided data, unless you specify that the data should be merged into the existing document, as follows: + +```csharp +var docData = new Dictionary { + { "name", "Los Angeles" }, + { "state", "CA" }, + { "country", "USA" } +}; + +// Add a new document in collection "cities" +db.GetCollection ("cities").GetDocument ("LA").SetData (docData, SetOptions.CreateMerge ()); +``` + +If you're not sure whether the document exists, pass the option to merge the new data with any existing document to avoid overwriting entire documents. + +#### Data types + +Cloud Firestore lets you write a variety of data types inside a document, including strings, booleans, numbers, dates, null, and nested arrays and objects. Cloud Firestore always stores numbers as doubles, regardless of what type of number you use in your code: + +```csharp +var docData = new Dictionary { + { "stringExample", "Hello world!" }, + { "booleanExample", true }, + { "numberExample", 3.14 }, + { "dateExample", new NSDate () }, + { "arrayExample", NSArray.FromObjects (true, 3, "hello") }, + { "nullExample", null }, + { "objectExample", NSDictionary.FromObjectsAndKeys ( + keys: new object [] { "a", "b" }, + objects: new object [] { 5, NSDictionary.FromObjectsAndKeys ( + keys: new object [] { "nested" }, + objects: new object [] { "foo" } + ) } + ) } +}; + +db.GetCollection ("data").GetDocument ("one").SetData (docData, HandleDocumentActionCompletion); + +void HandleDocumentActionCompletion (NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error writing document: {error.LocalizedDescription}"); + return; + } + + System.Console.WriteLine ("Document successfully written!"); +} + +// Async/await way +try { + await db.GetCollection ("data").GetDocument ("one").SetDataAsync (docData); + System.Console.WriteLine ("Document successfully written!"); +} catch (NSErrorException ex) { + System.Console.WriteLine ($"Error writing document: {ex.Error.LocalizedDescription}"); +} +``` + +> ![note_icon] _**Important:**_ _To create nested objects, you cannot use nested Dictionaries, doing so will throw an exception. To create nested object, you need to use NSDictionaries as shown above._ + +### Add a document + +When you use `SetData` method to create a document, you must specify an ID for the document to create. For example: + +```csharp +db.GetCollection ("cities").GetDocument ("new_city_id").SetData (data); +``` + +But sometimes there isn't a meaningful ID for the document, and it's more convenient to let Cloud Firestore auto-generate an ID for you. You can do this by calling `AddDocument` method: + +```csharp +var docData = new Dictionary { + { "name", "Tokyo" }, + { "country", "Japan" } +}; + +DocumentReference newDocument = null; +newDocument = db.GetCollection ("cities").AddDocument (docData, HandleAddDocumentCompletion); + +void HandleAddDocumentCompletion (NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error adding document: {error.LocalizedDescription}"); + return; + } + + System.Console.WriteLine ($"Document added with ID: {newDocument.Id}"); +} +``` + +> ![note_icon] **_Important:_** _Unlike "push IDs" in the Firebase Realtime Database, Cloud Firestore auto-generated IDs do not provide any automatic ordering. If you want to be able to order your documents by creation date, you should store a timestamp as a field in the documents._ + +In some cases, it can be useful to create a document reference with an auto-generated ID, then use the reference later. For this use case, you can call `CreateDocument` method: + +```csharp +var newCityDocument = db.GetCollection ("cities").CreateDocument (); +// later +newCityDocument.SetData ... +``` + +Behind the scenes, `AddDocument (...)` and `.CreateDocument ().SetData (...)` are completely equivalent, so you can use whichever is more convenient. + +### Update a document + +To update some fields of a document without overwriting the entire document, use the `UpdateData` method: + +```csharp +var docData = new Dictionary { { "capital", true } }; +var washingtonDocument = db.GetCollection ("cities").GetDocument ("DC"); + +// Set the "capital" field of the city 'DC' +washingtonDocument.UpdateData (docData, HandleDocumentActionCompletion); + +void HandleDocumentActionCompletion (NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error updating document: {error.LocalizedDescription}"); + return; + } + + System.Console.WriteLine ("Document successfully updated"); +} +``` + +An `async/await` version of this: + +```csharp +var docData = new Dictionary { { "capital", true } }; +var washingtonDocument = db.GetCollection ("cities").GetDocument ("DC"); + +// Set the "capital" field of the city 'DC' +try { + await washingtonDocument.UpdateDataAsync (docData); + System.Console.WriteLine ("Document successfully updated"); +} catch (NSErrorException ex) { + System.Console.WriteLine ($"Error updating document: {ex.Error.LocalizedDescription}"); +} +``` + +#### Update fields in nested objects + +If your document contains nested objects, you can use "dot notation" to reference nested fields within the document when you call `UpdateData` method: + +```csharp +var frankData = new Dictionary { + { "name", "Frank" }, + { "favorites", NSDictionary.FromObjectsAndKeys ( + keys: new object [] { "food", "color", "subject" }, + objects: new object [] { "Pizza", "Blue", "recess" } + ) }, + { "age", 12 } +}; +var frankDocument = db.GetCollection ("users").GetDocument ("frank"); +frankDocument.SetData (frankData); + +// To update age and favorite color: +var updateData = new Dictionary { + { "age", 13 }, + { "favorites.color", "Red" } +}; +db.GetCollection ("users").GetDocument ("frank").UpdateData (updateData, HandleDocumentActionCompletion); + +void HandleDocumentActionCompletion (NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error updating document: {error.LocalizedDescription}"); + return; + } + + System.Console.WriteLine ("Document successfully updated"); +} + +// Async/await way +try { + await db.GetCollection ("users").GetDocument ("frank").UpdateDataAsync (updateData); + System.Console.WriteLine ("Document successfully updated"); +} catch (NSErrorException ex) { + System.Console.WriteLine ($"Error updating document: {ex.Error.LocalizedDescription}"); +} +``` + +You can also add server timestamps to specific fields in your documents, to track when an update was received by the server: + +```csharp +var updateData = new Dictionary { { "lastUpdated", FieldValue.ServerTimestamp } }; +db.GetCollection ("objects").GetDocument ("some-id").UpdateData (updateData, HandleDocumentActionCompletion); + +void HandleDocumentActionCompletion (NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error updating document: {error.LocalizedDescription}"); + return; + } + + System.Console.WriteLine ("Document successfully updated"); +} + +// Async/await way +try { + await db.GetCollection ("objects").GetDocument ("some-id").UpdateDataAsync (updateData); + System.Console.WriteLine ("Document successfully updated"); +} catch (NSErrorException ex) { + System.Console.WriteLine ($"Error updating document: {ex.Error.LocalizedDescription}"); +} +``` + +## Transactions and Batched Writes + +Firebase Cloud Firestore supports atomic operations for reading and writing data. In a set of atomic operations, either all of the operations succeed, or none of them are applied. There are two types of atomic operations in Firebase Cloud Firestore: + +* **Transactions:** a transaction is a set of read and write operations on one or more documents. +* **Batched Writes:** a batched write is a set of up to 500 write operations on one or more documents. + +### Updating data with transactions + +Using the Firebase Cloud Firestore client libraries, you can group multiple operations into a single transaction. Transactions are useful when you want to update a field's value based on its current value, or the value of some other field. You could increment a counter by creating a transaction that reads the current value of the counter, increments it, and writes the new value to Cloud Firestore. + +A transaction consists of any number of `GetDocument` operations followed by any number of write operations such as `SetData`, `UpdateData`, or `DeleteData`. In the case of a concurrent edit, Firebase Cloud Firestore runs the entire transaction again. For example, if a transaction reads documents and another client modifies any of those documents, Cloud Firestore retries the transaction. This feature ensures that the transaction runs on up-to-date and consistent data. + +Transactions never partially apply writes. All writes execute at the end of a successful transaction. + +When using transactions, note that: + +* Read operations must come before write operations. +* A function calling a transaction (transaction function) might run more than once if a concurrent edit affects a document that the transaction reads. +* Transaction functions should not directly modify application state. +* Transactions will fail when the client is offline. + +The following example shows how to create and run a transaction: + +```csharp +var sf = db.GetCollection ("cities").GetDocument ("SF"); +db.RunTransaction (HandleTransactionUpdate, HandleTransactionCompletion); + +NSObject HandleTransactionUpdate (Transaction transaction, out NSError error) +{ + DocumentSnapshot sfDocument = transaction.GetDocument (sf, out error); + + if (error != null) + return null; + + if (!(sfDocument.Data? ["population"] is NSNumber)) { + error = new NSError (new NSString ("AppErrorDomain"), + -1, + NSDictionary.FromObjectAndKey (new NSString ("Unable to retrieve population from snapshot"), NSError.LocalizedDescriptionKey)); + return null; + } + + var oldPopulation = (sfDocument.Data ["population"] as NSNumber).Int32Value; + + var updateData = new Dictionary { { "population", oldPopulation + 1 } }; + transaction.UpdateData (updateData, sf); + + return null; +} + +void HandleTransactionCompletion (NSObject result, NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Transaction failed: {error.LocalizedDescription}"); + return; + } + + System.Console.WriteLine ("Transaction successfully committed!"); +} +``` + +An `async/await` version: + +```csharp +var sf = db.GetCollection ("cities").GetDocument ("SF"); + +try { + await db.RunTransactionAsync (HandleTransactionUpdate); + System.Console.WriteLine ("Transaction successfully committed!"); +} catch (NSErrorException ex) { + System.Console.WriteLine ($"Error updating document: {ex.Error.LocalizedDescription}"); +} + +NSObject HandleTransactionUpdate (Transaction transaction, out NSError error) +{ + DocumentSnapshot sfDocument = transaction.GetDocument (sf, out error); + + if (error != null) + return null; + + if (!(sfDocument.Data? ["population"] is NSNumber)) { + error = new NSError (new NSString ("AppErrorDomain"), + -1, + NSDictionary.FromObjectAndKey (new NSString ("Unable to retrieve population from snapshot"), NSError.LocalizedDescriptionKey)); + return null; + } + + var oldPopulation = (sfDocument.Data ["population"] as NSNumber).Int32Value; + + var updateData = new Dictionary { { "population", oldPopulation + 1 } }; + transaction.UpdateData (updateData, sf); + + return null; +} +``` + +#### Passing information out of transactions + +Do not modify application state inside of your transaction functions. Doing so will introduce concurrency issues, because transaction functions can run multiple times and are not guaranteed to run on the UI thread. Instead, pass information you need out of your transaction functions. The following example builds on the previous example to show how to pass information out of a transaction: + +```csharp +var sf = db.GetCollection ("cities").GetDocument ("SF"); +db.RunTransaction (HandleTransactionUpdate, HandleTransactionCompletion); + +NSObject HandleTransactionUpdate (Transaction transaction, out NSError error) +{ + DocumentSnapshot sfDocument = transaction.GetDocument (sf, out error); + + if (error != null) + return null; + + if (!(sfDocument.Data? ["population"] is NSNumber)) { + error = new NSError (new NSString ("AppErrorDomain"), + -1, + NSDictionary.FromObjectAndKey (new NSString ("Unable to retrieve population from snapshot"), NSError.LocalizedDescriptionKey)); + return null; + } + + var oldPopulation = (sfDocument.Data ["population"] as NSNumber).Int32Value; + var newPopulation = oldPopulation + 1; + + if (newPopulation > 1000000) { + error = new NSError (new NSString ("AppErrorDomain"), + -2, + NSDictionary.FromObjectAndKey (new NSString ("Population too big"), NSError.LocalizedDescriptionKey)); + return null; + } + + var updateData = new Dictionary { { "population", newPopulation } }; + transaction.UpdateData (updateData, sf); + + return NSNumber.FromInt32 (newPopulation); +} + +void HandleTransactionCompletion (NSObject result, NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error updating population: {error.LocalizedDescription}"); + return; + } + + var population = (result as NSNumber).Int32Value; + System.Console.WriteLine ($"Population increased to {population}"); +} +``` + +An `async/await` version: + +```csharp +var sf = db.GetCollection ("cities").GetDocument ("SF"); + +try { + var result = await db.RunTransactionAsync (HandleTransactionUpdate); + var population = (result as NSNumber).Int32Value; + System.Console.WriteLine ($"Population increased to {population}"); +} catch (NSErrorException ex) { + System.Console.WriteLine ($"Error updating population: {error.LocalizedDescription}"); +} + +NSObject HandleTransactionUpdate (Transaction transaction, out NSError error) +{ + DocumentSnapshot sfDocument = transaction.GetDocument (sf, out error); + + if (error != null) + return null; + + if (!(sfDocument.Data? ["population"] is NSNumber)) { + error = new NSError (new NSString ("AppErrorDomain"), + -1, + NSDictionary.FromObjectAndKey (new NSString ("Unable to retrieve population from snapshot"), NSError.LocalizedDescriptionKey)); + return null; + } + + var oldPopulation = (sfDocument.Data ["population"] as NSNumber).Int32Value; + var newPopulation = oldPopulation + 1; + + if (newPopulation > 1000000) { + error = new NSError (new NSString ("AppErrorDomain"), + -2, + NSDictionary.FromObjectAndKey (new NSString ("Population too big"), NSError.LocalizedDescriptionKey)); + return null; + } + + var updateData = new Dictionary { { "population", newPopulation } }; + transaction.UpdateData (updateData, sf); + + return NSNumber.FromInt32 (newPopulation); +} +``` + +#### Transaction failure + +A transaction can fail for the following reasons: + +* The transaction contains read operations after write operations. Read operations must always come before any write operations. +* The transaction read a document that was modified outside of the transaction. In this case, the transaction automatically runs again. The transaction is retried a finite number of times. + +A failed transaction returns an error and does not write anything to the database. You do not need to roll back the transaction; Cloud Firestore does this automatically. + +### Batched writes + +If you do not need to read any documents in your operation set, you can execute multiple write operations as a single batch that contains any combination of `SetData`, `UpdateData`, or `DeleteData` operations. A batch of writes completes atomically and can write to multiple documents. + +Batched writes are also useful for migrating large data sets to Cloud Firestore. A batched write can contain up to 500 operations and batching operations togethezr reduces connection overhead resulting in faster data migration. + +Batched writes have fewer failure cases than transactions and use simpler code. They are not affected by contention issues, because they don't depend on consistently reading any documents. Batched writes execute even when the user's device is offline. The following example shows how to build and commit a batch of writes: + +```csharp +// Get new write batch +var batch = db.CreateBatch (); + +// Set the value of 'NYC' +var nyc = db.GetCollection ("cities").GetDocument ("NYC"); +batch.SetData (new NSDictionary (), nyc); + +// Update the population of 'SF' +var sf = db.GetCollection ("cities").GetDocument ("SF"); +batch.UpdateData (new Dictionary { { "population", 1000000 } }, sf); + +// Delete the city 'LA' +var la = db.GetCollection ("cities").GetDocument ("LA"); +batch.DeleteDocument (la); + +// Commit the batch +batch.Commit (HandleCommitCompletion); + +void HandleCommitCompletion (NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error writing batch: {error.LocalizedDescription}"); + return; + } + + System.Console.WriteLine ("Batch write succeeded."); +} + +// Async/await way: +try { + // Commit the batch + await batch.CommitAsync (); + System.Console.WriteLine ("Batch write succeeded."); +} catch (NSErrorException ex) { + System.Console.WriteLine ($"Error writing batch: {error.LocalizedDescription}"); +} +``` + +## Delete Data from Cloud Firestore + +To delete a document, use the `DeleteData` method: + +```csharp +db.GetCollection ("cities").GetDocument ("DC").DeleteDocument (HandleDocumentActionCompletionHandler); + +void HandleDocumentActionCompletionHandler (NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error removing document: {error.LocalizedDescription}"); + return; + } + + System.Console.WriteLine ("Document successfully removed!"); +} + +// Async/await way: +try { + await db.GetCollection ("cities").GetDocument ("DC").DeleteDocumentAsync (); + System.Console.WriteLine ("Document successfully removed!"); +} catch (NSErrorException ex) { + System.Console.WriteLine ($"Error removing document: {ex.Error.LocalizedDescription}"); +} +``` + +### Delete fields + +To delete specific fields from a document, use the `FieldValue.Delete` property when you update a document: + +```csharp +var deleteData = new Dictionary { { "capital", FieldValue.Delete } }; +db.GetCollection ("cities").GetDocument ("BJ").UpdateData (deleteData, HandleDocumentActionCompletionHandler); + +void HandleDocumentActionCompletionHandler (NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error updating document: {error.LocalizedDescription}"); + return; + } + + System.Console.WriteLine ("Document successfully updated!"); +} + +// Async/await way: +try { + await db.GetCollection ("cities").GetDocument ("BJ").UpdateDataAsync (deleteData); + System.Console.WriteLine ("Document successfully updated!"); +} catch (NSErrorException ex) { + System.Console.WriteLine ($"Error updating document: {error.LocalizedDescription}"); +} +``` + +### Delete collections + +To delete an entire collection or subcollection in Cloud Firestore, retrieve all the documents within the collection or subcollection and delete them. If you have larger collections, you may want to delete the documents in smaller batches to avoid out-of-memory errors. Repeat the process until you've deleted the entire collection or subcollection: + +```csharp +void DeleteCollection (CollectionReference collection, int batchSize, Action completion) +{ + // Limit query to avoid out-of-memory errors on large collections. + // When deleting a collection guaranteed to fit in memory, batching can be avoided entirely. + collection.LimitedTo (batchSize).GetDocuments (HandleQuerySnapshot); + + void HandleQuerySnapshot (QuerySnapshot snapshot, NSError error) + { + // An error occurred. + if (error != null) { + completion (error); + return; + } + + // There's nothing to delete. + if (snapshot.Count == 0) { + completion (null); + return; + } + + var batch = collection.Firestore.CreateBatch (); + + foreach (var document in snapshot.Documents) + batch.DeleteDocument (document.Reference); + + batch.Commit (HandleCommitCompletion); + } + + void HandleCommitCompletion (NSError error) + { + // Stop the deletion process and handle the error. Some elements + // may have been deleted. + if (error != null) { + completion (error); + return; + } + + DeleteCollection (collection, batchSize, completion); + } +} +``` + +An `async/await` version: + +```csharp +async Task DeleteCollectionAsync (CollectionReference collection, int batchSize) +{ + // Limit query to avoid out-of-memory errors on large collections. + // When deleting a collection guaranteed to fit in memory, batching can be avoided entirely. + try { + var snapshot = await collection.LimitedTo (batchSize).GetDocumentsAsync (); + + // There's nothing to delete. + if (snapshot.Count == 0) + return; + + var batch = collection.Firestore.CreateBatch (); + + foreach (var document in snapshot.Documents) + batch.DeleteDocument (document.Reference); + + await batch.CommitAsync (); + } catch (NSErrorException ex) { + // Stop the deletion process and handle the error. Some elements + // may have been deleted. + } + + await DeleteCollectionAsync (collection, batchSize); +} +``` + +### Delete data with the Firebase CLI + +You can also use the [Firebase CLI][5] to delete documents and collections. Use the following command to delete data: + +``` +firebase firestore:delete [options] <> +``` + +## Manage Cloud Firestore with the Firebase Console + +You can manage Cloud Firestore through the following actions in the [Firebase console][6]: + +* Add, edit, and delete data. +* Create and update Cloud Firestore Security Rules. +* Manage indexes. + +> ![note_icon] _**Note:**_ _During the Beta period, you can only manage Cloud Firestore from the Firebase console, not the Cloud Platform Console._ + +To learn more about this, please, read the following [documentation][7]. + +--- + +# Query Data + +## Get Data with Cloud Firestore + +There are two ways to retrieve data stored in Cloud Firestore. Either of these methods can be used with documents, collections of documents, or the results of queries: + +* Call a method to get the data. +* Set a listener to receive data-change events. + +When you set a listener, Cloud Firestore sends your listener an initial snapshot of the data, and then another snapshot each time the document changes. + +### Example data + +To get started, write some data about cities so we can look at different ways to read it back: + +```csharp +var cities = db.GetCollection ("cities"); + +cities.GetDocument ("SF").SetData (new Dictionary { + { "name", "San Francisco" }, + { "state", "CA" }, + { "country", "USA" }, + { "capital", false }, + { "population", 860000 } +}); +cities.GetDocument ("LA").SetData (new Dictionary { + { "name", "Los Angeles" }, + { "state", "CA" }, + { "country", "USA" }, + { "capital", false }, + { "population", 3900000 } +}); +cities.GetDocument ("DC").SetData (new Dictionary { + { "name", "Washington D.C." }, + { "country", "USA" }, + { "capital", true }, + { "population", 680000 } +}); +cities.GetDocument ("TOK").SetData (new Dictionary { + { "name", "Tokyo" }, + { "country", "Japan" }, + { "capital", true }, + { "population", 9000000 } +}); +cities.GetDocument ("BJ").SetData (new Dictionary { + { "name", "Beijing" }, + { "country", "China" }, + { "capital", true }, + { "population", 21500000 } +}); +``` + +### Get a document + +The following example shows how to retrieve the contents of a single document using `GetDocument`: + +```csharp +var document = db.GetCollection ("cities").GetDocument ("SF"); +document.GetDocument (HandleDocumentSnapshot); + +void HandleDocumentSnapshot (DocumentSnapshot snapshot, NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error getting the document: {error.LocalizedDescription}"); + return; + } + + if (snapshot != null) { + System.Console.WriteLine ($"Document data: {snapshot.Data}"); + } else { + System.Console.WriteLine ("Document does not exist."); + } +} +``` + +An `async/await` version: + +```csharp +var document = db.GetCollection ("cities").GetDocument ("SF"); + +try { + var snapshot = await document.GetDocumentAsync (); + + if (snapshot != null) + System.Console.WriteLine ($"Document data: {snapshot.Data}"); + else + System.Console.WriteLine ("Document does not exist."); +} catch (NSErrorException ex) { + System.Console.WriteLine ($"Error getting the document: {ex.Error.LocalizedDescription}"); +} +``` + +> ![note_icon] _**Note:**_ _If there is no document at the location referenced by *document*, the resulting document will be `null`._ + +### Get multiple documents from a collection + +You can also retrieve multiple documents with one request by querying documents in a collection. For example, you can use `WhereEqualsTo` methods to query for all of the documents that meet a certain condition, then use `GetDocuments` method to retrieve the results: + +```csharp +db.GetCollection ("cities").WhereEqualsTo ("capital", true).GetDocuments (HandleQuerySnapshot); + +void HandleQuerySnapshot (QuerySnapshot snapshot, NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error getting documents: {error.LocalizedDescription}"); + return; + } + + foreach (var document in snapshot?.Documents) + System.Console.WriteLine ($"{document.Id} => {document.Data}"); +} +``` + +An `async/await` version: + +```csharp +try { + var snapshot = await db.GetCollection ("cities").WhereEqualsTo ("capital", true).GetDocumentsAsync (); + + foreach (var document in snapshot?.Documents) + System.Console.WriteLine ($"{document.Id} => {document.Data}"); +} catch (NSErrorException ex) { + System.Console.WriteLine ($"Error getting documents: {ex.Error.LocalizedDescription}"); +} +``` + +#### Get all documents in a collection + +In addition, you can retrieve all documents in a collection by omitting the `WhereEqualsTo` method filter entirely: + +```csharp +db.GetCollection ("cities").GetDocuments (HandleQuerySnapshot); + +void HandleQuerySnapshot (QuerySnapshot snapshot, NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error getting documents: {error.LocalizedDescription}"); + return; + } + + foreach (var document in snapshot?.Documents) + System.Console.WriteLine ($"{document.Id} => {document.Data}"); +} +``` + +An `async/await` version: + +```csharp +try { + var snapshot = await db.GetCollection ("cities").GetDocumentsAsync (); + + foreach (var document in snapshot?.Documents) + System.Console.WriteLine ($"{document.Id} => {document.Data}"); +} catch (NSErrorException ex) { + System.Console.WriteLine ($"Error getting documents: {ex.Error.LocalizedDescription}"); +} +``` + +## Get Realtime Updates with Cloud Firestore + +You can listen to a document with the `AddSnapshotListener` method. An initial call using the callback you provide creates a document snapshot immediately with the current contents of the single document. Then, each time the contents change, another call updates the document snapshot: + +```csharp +db.GetCollection ("cities").GetDocument ("SF").AddSnapshotListener (HandleDocumentSnapshot); + +void HandleDocumentSnapshot (DocumentSnapshot snapshot, NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error fetching document: {error.LocalizedDescription}"); + return; + } + + System.Console.WriteLine ($"Current data: {snapshot.Data}"); +} +``` + +#### Events for local changes + +In the previous example, the callback was called twice after setting San Francisco's population to 999999. This is because of an important feature called "latency compensation." When you perform a write, your listeners will be notified with the new data immediately, before the data is even sent to the backend database. + +As shown above, your listener will then be notified again once the data is actually written to the backend. This could take any amount of time from a few milliseconds to a few hours, depending on the state of your network connection. + +Retrieved documents have a `metadata.HasPendingWrites` property that indicates whether the document has local changes that haven't been written to the backend yet. You can use this property to repeat the previous example but show the difference between the two events: + +```csharp +db.GetCollection ("cities").GetDocument ("SF").AddSnapshotListener (HandleDocumentSnapshot); + +void HandleDocumentSnapshot (DocumentSnapshot snapshot, NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error fetching document: {error.LocalizedDescription}"); + return; + } + + var source = snapshot.Metadata.HasPendingWrites ? "Local" : "Server"; + System.Console.WriteLine ($"{source} data: {snapshot.Data}"); +} +``` + +> ![note_icon] _**Note:**_ _If you just want to know when your write has completed, you can listen to the completion callback of your write function rather than using `HasPendingWrites` property. + +### Listen to multiple documents in a collection + +As with documents, you can use `AddSnapshotListener` method instead of `GetDocument` method to listen to the results of a query. This creates a query snapshot. For example, to listen to the documents with state CA: + +```csharp +db.GetCollection ("cities").WhereEqualsTo ("state", "CA").AddSnapshotListener (HandleQuerySnapshot); + +void HandleQuerySnapshot (QuerySnapshot snapshot, NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error getting documents: {error.LocalizedDescription}"); + return; + } + + var cities = new string [snapshot?.Documents.Length ?? 0]; + + for (int i = 0; i < cities.Length; i++) + cities [i] = snapshot.Documents [i].Data ["name"].ToString (); + + var citiesString = string.Join (", ", cities); + + System.Console.WriteLine ($"Current cities in CA: {citiesString}"); +} +``` + +The snapshot handler will receive a new query snapshot every time the query results change (that is, when a document is added, removed, or modified). + +> ![note_icon] _**Important:**_ _As explained above under [Events for local changes](#events-for-local-changes), you will receive events immediately for your local writes. Your listener can use the `metadata.HasPendingWrites` field on each document to determine whether the document has local changes that have not yet been written to the backend._ + +#### View changes between snapshots + +It is often useful to see the actual changes to query results between query snapshots, instead of simply using the entire query snapshot. For example, you may want to maintain a cache as individual documents are added, removed, and modified: + +```csharp +db.GetCollection ("cities").WhereEqualsTo ("state", "CA").AddSnapshotListener (HandleQuerySnapshot); + +void HandleQuerySnapshot (QuerySnapshot snapshot, NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error getting documents: {error.LocalizedDescription}"); + return; + } + + foreach (var documentChange in snapshot?.DocumentChanges) { + switch (documentChange.Type) { + case DocumentChangeType.Added: + System.Console.WriteLine ($"New city: {documentChange.Document.Data}"); + break; + case DocumentChangeType.Modified: + System.Console.WriteLine ($"Modified city: {documentChange.Document.Data}"); + break; + case DocumentChangeType.Removed: + System.Console.WriteLine ($"Removed city: {documentChange.Document.Data}"); + break; + } + } +} +``` + +> ![note_icon] _**Important:**_ _The first query snapshot contains **added** events for all existing documents that match the query. This is because you're getting a set of changes that bring your query snapshot current with the initial state of the query. This allows you, for instance, to directly populate your UI from the changes you receive in the first query snapshot, without needing to add special logic for handling the initial state._ + +The initial state can come from the server directly, or from a local cache. If there is state available in a local cache, the query snapshot will be initially populated with the cached data, then updated with the server's data when the client has caught up with the server's state. + +#### Detach a listener + +When you are no longer interested in listening to your data, you must detach your listener so that your event callbacks stop getting called. This allows the client to stop using bandwidth to receive updates. You can use the unsubscribe function on `AddSnapshotListener` method to stop listening to updates: + +```csharp +var listener = db.GetCollection ("cities").AddSnapshotListener (HandleQuerySnapshot); + +// ... + +// Stop listening to changes +listener.Remove (); + +void HandleQuerySnapshot (QuerySnapshot snapshot, NSError error) +{ + // ... +} +``` + +#### Handle listen errors + +A listen may occasionally fail — for example, due to security permissions, or if you tried to listen on an invalid query. (Learn more about [valid and invalid queries](#compound_queries).) To handle these failures, you can provide an error callback when you attach your snapshot listener. After an error, the listener will not receive any more events, and there is no need to detach your listener. + +```csharp +db.GetCollection ("cities").AddSnapshotListener (HandleQuerySnapshot); + +void HandleQuerySnapshot (QuerySnapshot snapshot, NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error retreiving collection: {error.LocalizedDescription}"); + return; + } +} +``` + +## Perform Simple and Compound Queries in Cloud Firestore + +Cloud Firestore provides powerful query functionality for specifying which documents you want to retrieve from a collection. These queries can also be used with either `GetDocument` or `AddSnapshotListener` methods, as described in [Get Data](#get-data-with-cloud-firestore) and [Get Realtime Updates](#get-realtime-updates-with-cloud-firestore). + +### Example data + +To get started, write some data about cities so we can look at different ways to read it back: + +```csharp +var cities = db.GetCollection ("cities"); + +cities.GetDocument ("SF").SetData (new Dictionary { + { "name", "San Francisco" }, + { "state", "CA" }, + { "country", "USA" }, + { "capital", false }, + { "population", 860000 } +}); +cities.GetDocument ("LA").SetData (new Dictionary { + { "name", "Los Angeles" }, + { "state", "CA" }, + { "country", "USA" }, + { "capital", false }, + { "population", 3900000 } +}); +cities.GetDocument ("DC").SetData (new Dictionary { + { "name", "Washington D.C." }, + { "country", "USA" }, + { "capital", true }, + { "population", 680000 } +}); +cities.GetDocument ("TOK").SetData (new Dictionary { + { "name", "Tokyo" }, + { "country", "Japan" }, + { "capital", true }, + { "population", 9000000 } +}); +cities.GetDocument ("BJ").SetData (new Dictionary { + { "name", "Beijing" }, + { "country", "China" }, + { "capital", true }, + { "population", 21500000 } +}); +``` + +### Simple queries + +The following query returns all cities with state **CA**: + +```csharp +// Create a reference to the cities collection +var cities = db.GetCollection ("cities"); + +// Create a query against the collection. +var query = cities.WhereEqualsTo ("state", "CA"). +``` + +The following query returns all the capital cities: + +```csharp +var capitalCities = db.GetCollection ("cities").WhereEqualsTo ("state", true); +``` + +There are some `WhereFoo` methods to help in different comparison scenarios: + +```csharp +cities.WhereEqualsTo ("state", "CA"); +cities.WhereLessThan ("population", 100000); +cities.WhereGreaterThanOrEqualsTo ("name", "San Francisco"); +``` + +### Compound queries + +You can also chain multiple `WhereFoo` methods to create more specific queries (logical `AND`). However, to combine the equality operator (`==`) with a range comparison (`<`, `<=`, `>`, or `>=`), make sure to create a [custom index][8]. + +```csharp +cities.WhereEqualsTo ("state", "CO").WhereEqualsTo ("name", "Denver"); +cities.WhereEqualsTo ("state", "CA").WhereLessThan ("population", 1000000); +``` + +Additionally, you can only perform range comparisons (`<`, `<=`, `>`, `>=`) on a single field: + +![correct_icon] **Valid:** Range filters on only one field: + +```csharp +cities.WhereGreaterThanOrEqualsTo ("state", "CA") + .WhereLessThanOrEqualsTo ("state", "IN"); + +cities.WhereEqualsTo ("state", "CA") + .WhereGreaterThan ("population", 1000000); +``` + +![wrong_icon] **Invalid:** Range filters on different fields: + +```csharp +cities.WhereGreaterThanOrEqualsTo ("state", "CA") + .WhereGreaterThan ("population", 1000000); +``` + +## Order and Limit Data with Cloud Firestore + +Cloud Firestore provides powerful query functionality for specifying which documents you want to retrieve from a collection. These queries can also be used with either `GetDocuments` or `AddSnapshotListener` methods, as described in [Get Data](#get-data-with-cloud-firestore). + +### Order and limit data + +Cloud Firestore also lets you specify the sort order for your data and specify a limit to how many documents you want to retrieve using `OrderedBy` and `LimitedTo` methods. For example, you could query for the first 3 cities alphabetically with: + +```csharp +cities.OrderedBy ("name").LimitedTo (3); +``` + +You could also sort in descending order to get the last 3 cities: + +```csharp +cities.OrderedBy ("name", true).LimitedTo (3); +``` + +You can also order by multiple fields. For example, if you wanted to order by state, and within each state order by population in descending order: + +```csharp +cities.OrderedBy ("state") + .OrderedBy ("population", true); +``` + +You can combine `WhereFoo` filters with `OrderedBy` and `LimitedTo` methods. In the following example, the queries define a population threshold, sort by population in ascending order, and return only the first few results that exceed the threshold: + +```csharp +cities.WhereGreaterThan ("population", 100000) + .OrderedBy ("population") + .LimitedTo (2); +``` + +However, if you have a filter with a range comparison (<, <=, >, >=), your first ordering must be on the same field: + +![correct_icon] **Valid:** Range filter and `OrderedBy` on the same field: + +```csharp +cities.WhereGreaterThan ("population", 100000) + .OrderedBy ("population"); +``` + +![wrong_icon] **Invalid:** Range filter and first `OrderedBy` on different fields: + +```csharp +cities.WhereGreaterThan ("population", 100000) + .OrderedBy ("population"); +``` + +## Paginate Data with Query Cursors + +With query cursors in Cloud Firestore, you can split data returned by a query into batches according to the parameters you define in your query. + +Query cursors define the start and end points for a query, allowing you to: + +* Return a subset of the data. +* Paginate query results. + +However, to define a specific range for a query, you should use the `WhereFoo` methods described in [Simple Queries](#simple_queries). + +### Add a simple cursor to a query + +Use the `StartingAt` or `StartingAfter` methods to define the start point for a query. The `StartingAt` method includes the start point, while the `StartingAfter` method excludes it. + +For example, if you use `StartingAt (A)` in a query, it returns the entire alphabet. If you use `StartingAfter (A)` instead, it returns B-Z: + +```csharp +// Get all cities with population over one million, ordered by population. +cities.OrderedBy ("population") + .StartingAt (new object [] { 1000000 }); +``` + +Similarly, use the `EndingAt` or `EndingBefore` methods to define an end point for your query results: + +```csharp +// Get all cities with population less than one million, ordered by population. +cities.OrderedBy ("population") + .EndingAt (new object [] { 1000000 }); +``` + +### Use a document snapshot to define the query cursor + +You can also pass a document snapshot to the cursor clause as the start or end point of the query cursor. The values in the document snapshot serve as the values in the query cursor. + +For example, take a snapshot of a "San Francisco" document in your data set of cities and populations. Then, use that document snapshot as the start point for your population query cursor. Your query will return all the cities with a population larger than or equal to San Francisco's, as defined in the document snapshot: + +```csharp +db.GetCollection ("cities").GetDocument ("SF").AddSnapshotListener (HandleDocumentSnapshot); + +void HandleDocumentSnapshot (DocumentSnapshot snapshot, NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error fetching document: {error.LocalizedDescription}"); + return; + } + + // Get all cities with a population greater than or equal to San Francisco. + var sfSizeOrBigger = db.GetCollection ("cities") + .OrderedBy ("population") + .StartingAt (snapshot); +} +``` + +### Paginate a query + +Paginate queries by combining query cursors with the `LimitedTo` method. For example, use the last document in a batch as the start of a cursor for the next batch: + +```csharp +// Construct query for first 25 cities, ordered by population +var first = db.GetCollection ("cities") + .OrderedBy ("population") + .LimitedTo (25); + +first.AddSnapshotListener (HandleQuerySnapshot); + +void HandleQuerySnapshot (QuerySnapshot snapshot, NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error retreving cities: {error.LocalizedDescription}"); + return; + } + + if (snapshot.Documents.Length == 0) { + // The collection is empty. + return; + } + + // Construct a new query starting after this document, + // retrieving the next 25 cities. + var lastSnapshot = snapshot.Documents.Last (); + var next = db.GetCollection ("cities") + .OrderedBy ("population") + .StartingAfter (lastSnapshot); + + // Use the query for pagination. + // ... +} +``` + +### Set multiple cursor conditions + +To add more granularity to your cursor's start or end point, you can specify multiple conditions in the cursor clause. This is particularly useful if your data set includes fields where the first condition in a cursor clause would return multiple results. Use multiple conditions to further specify the start or end point and reduce ambiguity. + +For example, in a data set containing all the cities named "Springfield" in the United States, there would be multiple start points for a query set to start at "Springfield": + +##### Cities + +| Name | State | +|-------------|---------------| +| Springfield | Massachusetts | +| Springfield | Missouri | +| Springfield | Wisconsin | + +To start at a specific Springfield, you could add the state as a secondary condition in your cursor clause: + +```csharp +// Will return all Springfields +db.GetCollection ("cities") + .OrderedBy ("name") + .OrderedBy ("state") + .StartingAt (new object [] { "Springfield" }); + +// Will return "Springfield, Missouri" and "Springfield, Wisconsin" +db.GetCollection ("cities") + .OrderedBy ("name") + .OrderedBy ("state") + .StartingAt (new object [] { "Springfield", "Missouri" }); +``` + +## Manage Indexes in Cloud Firestore + +Cloud Firestore requires an index for every query, to ensure the best performance. All document fields are automatically indexed, so queries that only use equality clauses don't need additional indexes. If you attempt a compound query with a range clause that doesn't map to an existing index, you receive an error. The error message includes a direct link to create the missing index in the Firebase console. + +Follow the generated link to the Firebase console, review the automatically populated info, and click Create. + +To learn more about this, please, read the following [documentation][8]. + +--- + +# Secure Data + +## Secure Data in Cloud Firestore + +Cloud Firestore offers robust access management and authentication through two different methods, depending on the client libraries you use. + +* For mobile and web client libraries, use Firebase Authentication and Cloud Firestore Security Rules to handle serverless authentication, authorization, and data validation. Learn how to secure your data for the Android, iOS, and Web client libraries with [Cloud Firestore Security Rules][9]. +* For server client libraries, use Cloud Identity and Access Management (IAM) to manage access to your database. Learn how to secure your data for the Java, Python, Node.js, and Go client libraries with [IAM][10]. + +--- + +# Enable Offline Data + +Cloud Firestore supports offline data persistence. This feature caches a copy of the Cloud Firestore data that your app is actively using, so your app can access the data when the device is offline. You can write, read, listen to, and query the cached data. When the device comes back online, Cloud Firestore synchronizes any local changes made by your app to the data stored remotely in Cloud Firestore. + +To use offline persistence, you don't need to make any changes to the code that you use to access Cloud Firestore data. With offline persistence enabled, the Cloud Firestore client library automatically manages online and offline data access and synchronizes local data when the device is back online. + +### Configure offline persistence + +When you initialize Cloud Firestore, you can enable or disable offline persistence. For Android and iOS, offline persistence is enabled by default. To disable persistence, set the `PersistenceEnabled` option to `false`: + +```csharp +var settings = new FirestoreSettings { PersistenceEnabled = false }; + +// Any additional options +// ... + +// Disable offline data persistence +var db = Firestore.SharedInstance; +db.Settings = settings; +``` + +### Listen to offline data + +While the device is offline, if you have enabled offline persistence, your listeners will receive listen events when the locally cached data changes. You can listen to documents, collections, and queries. + +To check whether you're receiving data from the server or the cache, use the fromCache property on the SnapshotMetadata in your snapshot event. If fromCache is true, the data came from the cache and might be stale or incomplete. If fromCache is false, the data is complete and current with the latest updates on the server. + +By default, no event is raised if only the SnapshotMetadata changed. If you rely on the fromCache values, specify the includeMetadataChanges listen option when you attach your listen handler: + +```csharp +// Listen to metadata updates to receive a server snapshot even if +// the data is the same as the cached data. +var options = new QueryListenOptions (); +options.SetIncludeQueryMetadataChanges (true); + +db.GetCollection ("cities").WhereEqualsTo ("state", "CA").AddSnapshotListener (options, HandleQuerySnapshot); + +void HandleQuerySnapshot (QuerySnapshot snapshot, NSError error) +{ + if (error != null) { + System.Console.WriteLine ($"Error retreving snapshot: {error.LocalizedDescription}"); + return; + } + + foreach (var documentChange in snapshot?.DocumentChanges) + if (documentChange.Type == DocumentChangeType.Added) + System.Console.WriteLine ($"New city: {documentChange.Document.Data}"); + + var source = snapshot.Metadata.IsFromCache ? "local cache" : "server"; + System.Console.WriteLine ($"Metadata: Data fetched from {source}"); +} +``` + +### Get offline data + +If you get a document while the device is offline, Cloud Firestore returns data from the cache. If the cache does not contain data for that document, or the document does not exist, the get call returns an error. + +### Query offline data + +Querying works with offline persistence. You can retrieve the results of queries with either a direct get or by listening, as described in the preceding sections. You can also create new queries on locally persisted data while the device is offline, but the queries will initially run only against the cached documents. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/firestore/) to see original Firebase documentation._ + +[1]: https://firebase.google.com/console/ +[2]: https://console.cloud.google.com/projectselector/apis/api/firestore.googleapis.com/overview?pli=1 +[3]: https://firebase.google.com/docs/firestore/manage-data/structure-data +[4]: https://firebase.google.com/docs/firestore/manage-data/data-types +[5]: https://firebase.google.com/docs/cli +[6]: https://console.firebase.google.com/project/_/database/firestore/data +[7]: https://firebase.google.com/docs/firestore/using-console +[8]: https://firebase.google.com/docs/firestore/query-data/indexing +[9]: https://firebase.google.com/docs/firestore/security/get-started +[10]: https://firebase.google.com/docs/firestore/security/iam +[11]: https://bugzilla.xamarin.com/show_bug.cgi?id=43689 +[document_icon]: https://cdn1.iconfinder.com/data/icons/hawcons/32/698661-icon-54-document-20.png +[collections_icon]: https://cdn1.iconfinder.com/data/icons/hawcons/32/698680-icon-72-documents-20.png +[note_icon]: https://cdn3.iconfinder.com/data/icons/UltimateGnome/22x22/apps/gnome-app-install-star.png +[warning_icon]: https://cdn2.iconfinder.com/data/icons/freecns-cumulus/32/519791-101_Warning-20.png +[correct_icon]: https://cdn4.iconfinder.com/data/icons/icocentre-free-icons/137/f-check_256-20.png +[wrong_icon]: https://cdn4.iconfinder.com/data/icons/icocentre-free-icons/114/f-cross_256-20.png diff --git a/docs/Firebase/CloudMessaging/Details.md b/docs/Firebase/CloudMessaging/Details.md new file mode 100755 index 00000000..b30042a9 --- /dev/null +++ b/docs/Firebase/CloudMessaging/Details.md @@ -0,0 +1,17 @@ +Using Firebase Cloud Messaging, you can notify a client app that new email or other data is available to sync. You can send notification messages to drive user reengagement and retention. For use cases such as instant messaging, a message can transfer a payload of up to 4KB to a client app. + +## Key capabilities + +| | | +|-:|--| +| **Send notification messages or data messages** | Send notification messages that are displayed to your user. Or send data messages and determine completely what happens in your application code. See Message types. | +| **Versatile message targeting** | Distribute messages to your client app in any of three ways — to single devices, to groups of devices, or to devices subscribed to topics. | +| **Send messages from client apps** | Send acknowledgments, chats, and other messages from devices back to your server over FCM’s reliable and battery-efficient connection channel. | + +## How does it work? + +![FirebaseCloudMessaging_HowItWorks](https://firebase.google.com/docs/cloud-messaging/images/messaging-overview.png) + +A Firebase Cloud Messaging implementation includes an app server that interacts with FCM via HTTP or XMPP protocol, and a client app. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/cloud-messaging/) to see original Firebase documentation._ \ No newline at end of file diff --git a/docs/Firebase/CloudMessaging/GettingStarted.md b/docs/Firebase/CloudMessaging/GettingStarted.md new file mode 100755 index 00000000..6e228626 --- /dev/null +++ b/docs/Firebase/CloudMessaging/GettingStarted.md @@ -0,0 +1,597 @@ +# Firebase Cloud Messaging on iOS + +## Table of content + +- [Firebase Cloud Messaging on iOS](#firebase-cloud-messaging-on-ios) + - [Table of content](#table-of-content) + - [About Firebase Cloud Messaging](#about-firebase-cloud-messaging) + - [Method swizzling in Firebase Cloud Messaging](#method-swizzling-in-firebase-cloud-messaging) +- [Setting Up a Firebase Cloud Messaging Client App on iOS](#setting-up-a-firebase-cloud-messaging-client-app-on-ios) + - [Prerequisites](#prerequisites) + - [Add Firebase to your app](#add-firebase-to-your-app) + - [Configure Cloud Messaging in your app](#configure-cloud-messaging-in-your-app) + - [Register for remote notifications](#register-for-remote-notifications) + - [Access the registration token](#access-the-registration-token) + - [Set the Messaging Delegate property](#set-the-messaging-delegate-property) + - [Receive the current registration token](#receive-the-current-registration-token) + - [Monitor token generation](#monitor-token-generation) + - [Swizzling disabled: mapping your APNs token and registration token](#swizzling-disabled--mapping-your-apns-token-and-registration-token) + - [Import existing user APNs tokens](#import-existing-user-apns-tokens) + - [Prevent auto initialization](#prevent-auto-initialization) +- [Send your First Message to a Backgrounded App](#send-your-first-message-to-a-backgrounded-app) + - [Send a notification message](#send-a-notification-message) +- [Send Messages to Multiple Devices](#send-messages-to-multiple-devices) + - [Subscribe a client app to a topic](#subscribe-a-client-app-to-a-topic) + - [Receive and handle topic messages](#receive-and-handle-topic-messages) + - [Build send requests](#build-send-requests) +- [Receive Messages](#receive-messages) + - [Handle messages received through the FCM APNs interface](#handle-messages-received-through-the-fcm-apns-interface) + - [Interpreting notification message payload](#interpreting-notification-message-payload) + - [Handle data messages in foregrounded apps](#handle-data-messages-in-foregrounded-apps) + - [Handle messages with method swizzling disabled](#handle-messages-with-method-swizzling-disabled) + - [Interpreting data message payload](#interpreting-data-message-payload) + - [Handle queued and deleted messages](#handle-queued-and-deleted-messages) +- [Topic Messaging](#topic-messaging) + - [Subscribe the client app to a topic](#subscribe-the-client-app-to-a-topic) + - [Manage topic subscriptions on the server](#manage-topic-subscriptions-on-the-server) + - [Receive and handle topic messages](#receive-and-handle-topic-messages) + - [Build send requests](#build-send-requests) +- [Device Group Messaging](#device-group-messaging) + - [Managing device groups](#managing-device-groups) + - [Sending downstream messages to device groups](#sending-downstream-messages-to-device-groups) + - [Sending upstream messages to device groups](#sending-upstream-messages-to-device-groups) +- [Sending Upstream Messages](#sending-upstream-messages) + - [Send an upstream message](#send-an-upstream-message) + - [Handle upstream message callbacks](#handle-upstream-message-callbacks) + - [Receive XMPP messages on the app server](#receive-xmpp-messages-on-the-app-server) +- [Send Messages with the Firebase Console](#send-messages-with-the-firebase-console) + +## About Firebase Cloud Messaging + +Firebase Cloud Messaging offers a broad range of messaging options and capabilities. I invite you to read the following [documentation][1] to have a better understanding about notification messages and data messages and what you can do with them using FCM's options. + +## Method swizzling in Firebase Cloud Messaging + +One important thing you should know before start using FCM is that FCM API performs method swizzling in two key areas: mapping your APNs token to the FCM registration token and capturing analytics data during downstream message callback handling. Developers who prefer not to use swizzling can disable it by adding the flag `FirebaseAppDelegateProxyEnabled` in the app’s Info.plist file and setting it to `No` (boolean value). If you decided to disable swizzling, the docs provide example for both scenarios, with and without method swizzling enabled. + +--- + +# Setting Up a Firebase Cloud Messaging Client App on iOS + +You can implement Firebase Cloud Messaging in two complementary ways: + +* Receive basic push messages up to 2KB over the Firebase Cloud Messaging APNs interface. +* Send messages upstream and/or receive downstream payloads up to 4KB. + +## Prerequisites + +* A physical iOS device +* A project targeting iOS 8 or above +* If you want to enable Notifications specifically, you'll need to create an [Apple Push Notification Authentication Key][2], an **App Id** and a **Provisioning Profile**, then [upload the key to Firebase][3] and finally enable the app for remote notifications. + * Open Entitlements.plist and check **Enable Push Notifications** + +> ![note_icon] _**Support for iOS 7 deprecated:**_ _As of v4.5.0 of the Firebase SDK for iOS, support for iOS 7 is deprecated. Upgrade your apps to target iOS 8 or above. To see the breakdown of worldwide iOS versions, go to [Apple’s App Store support page][4]._ + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][5], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][6]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][6] again at any time. + +> ![note_icon] **_Note:_** _If you have multiple build variants with different bundle IDs defined, each app must be added to your project in Firebase console._ + +## Configure Cloud Messaging in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Xamarin Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Open `GoogleService-Info.plist` file and change `IS_GCM_ENABLED` value to `Yes`. +4. Add the following line of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` namespace): + +```csharp +Firebase.Core.App.Configure(); +``` + +### Register for remote notifications + +Either at startup, or at the desired point in your application flow, register your app for remote notifications. Call `RegisterForRemoteNotifications` method as shown: + +```csharp +// Register your app for remote notifications. +if (UIDevice.CurrentDevice.CheckSystemVersion (10, 0)) { + + // For iOS 10 display notification (sent via APNS) + UNUserNotificationCenter.Current.Delegate = this; + + var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound; + UNUserNotificationCenter.Current.RequestAuthorization (authOptions, (granted, error) => { + Console.WriteLine (granted); + }); +} else { + // iOS 9 or before + var allNotificationTypes = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound; + var settings = UIUserNotificationSettings.GetSettingsForTypes (allNotificationTypes, null); + UIApplication.SharedApplication.RegisterUserNotificationSettings (settings); +} + +UIApplication.SharedApplication.RegisterForRemoteNotifications (); +``` + +> ![advice_icon] For devices running iOS 10 and above, you must assign your delegate object to the `UNUserNotificationCenter` object to receive display notifications, and the `Messaging` object to receive data messages, before your app finishes launching. For example, in an iOS app, you must assign it in the `WillFinishLaunching` or `FinishedLaunching` method. + +## Access the registration token + +By default, the FCM SDK generates a registration token for the client app instance on initial startup of your app. Similar to the APNs device token, this token allows you to target notification messages to this particular instance of the app. + +In the same way that iOS typically delivers an APNs device token on app start, FCM provides a registration token via the `DidReceiveRegistrationToken` method of the `Messaging` delegate on each app startup. During the first app start, and in all situations where the registration token is changed, the FCM SDK retrieves the token. In both cases, the FCM SDK calls `DidReceiveRegistrationToken` found in the `IMessageDelegate` interface. + +The registration token may change when: + +* The app is restored on a new device +* The user uninstalls/reinstall the app +* The user clears app data. + +### Set the Messaging Delegate property + +To receive registration tokens on app start, implement the `IMessagingDelegate` interface in a class and provide it to `Messaging`’s `Delegate` property after calling `App.Configure ()` method. For example, if your application delegate conforms to the `IMessagingDelegate` interface, you can set the delegate on `FinishedLaunching` to itself: + +```csharp +Messaging.SharedInstance.Delegate = this; +``` + +### Receive the current registration token + +Registration tokens are delivered via the `IMessagingDelegate` method `DidReceiveRegistrationToken`. This method is called generally once per app start with an FCM token. When this method is called, it is the ideal time to: + +* If the registration token is new, send it to your application server (it's recommended to implement server logic to determine whether the token is new). +* Subscribe the registration token to topics. This is required only for new subscriptions or for situations where the user has re-installed the app. + +```csharp +var token = Messaging.SharedInstance.FcmToken ?? ""; +Console.WriteLine ($"FCM token: {token}"); +``` + +After this delegate method is called, the registration token is available via the `FcmToken` property of `Messaging` class. Prior to this delegate method call, the property may be `null`. + +### Monitor token generation + +To be notified whenever the token is updated, supply a class conforming to the `IMessagingDelegate` interface and set it to `Messaging` `Delegate` property. The following example registers the delegate and adds the proper interface method: + +```csharp +[Export ("messaging:didReceiveRegistrationToken:")] +public void DidReceiveRegistrationToken (Messaging messaging, string fcmToken) +{ + Console.WriteLine ($"Firebase registration token: {fcmToken}"); + + // TODO: If necessary send token to application server. + // Note: This callback is fired at each app startup and whenever a new token is generated. +} +``` + +Alternatively, you can listen for an `NSNotification` named `RegistrationTokenRefreshedNotification` rather than supplying a interface method. The `Messaging.SharedInstance.FcmToken` property always has the current token value. + +#### Swizzling disabled: mapping your APNs token and registration token + +If you have disabled method swizzling, you'll need to explicitly map your APNs token to the FCM registration token. Override the `AppDelegate`'s method `RegisteredForRemoteNotifications` to retrieve the APNs token, and then use the `ApnsToken` property. + +Provide your APNs token using the `ApnsToken` property: + +```csharp +public override void RegisteredForRemoteNotifications (UIApplication application, NSData deviceToken) +{ + Messaging.SharedInstance.ApnsToken = deviceToken; +} +``` + +After the FCM registration token is generated, you can access it and listen for refresh events using the same methods as with swizzling enabled. + +### Import existing user APNs tokens + +If you have an existing user base that you want to onboard to an FCM client app, use the [batchImport][7] API provided by Instance ID. With this API, you can bulk import existing iOS APNs tokens into FCM, mapping them to new, valid registration tokens. + +## Prevent auto initialization + +FCM generates an Instance ID, which is used as a registration token within FCM. When an Instance ID is generated the library will upload the identifier and configuration data to Firebase. If you want to get an explicit opt-in before using Instance ID, you can prevent generation at configure time by disabling FCM. To do this, add a metadata value to your `Info.plist` (not your `GoogleService-Info.plist`): + +`FirebaseMessagingAutoInitEnabled = NO` + +To re-enable FCM, you can make a runtime call: + +```csharp +Messaging.SharedInstance.AutoInitEnabled = true; +``` + +This value persists across app restarts once set. + +--- + +# Send your First Message to a Backgrounded App + +To get started with FCM, build out the simplest use case: sending a notification message from the [Notifications composer][8] to a specific user's device when the app is in the background on the device. + +## Send a notification message + +1. Install and run the app on the target device. You'll need to accept the request for permission to receive remote notifications. +2. Make sure the app is in the background on the device. +3. Open the [Notifications composer][8] and select **New Message**. +4. Enter the message text. +5. Select **Single Device** for the message target. +6. In the field labeled FCM Registration Token, enter the registration token you obtained in a previous section of this guide. + +After you click **Send Message**, targeted client devices that have the app in the background receive the notification in the notification center. + +--- + +# Send Messages to Multiple Devices + +Firebase Cloud Messaging provides two ways to target a message to multiple devices: + +* [Topic messaging](#topic-messaging), which allows you to send a message to multiple devices that have opted in to a particular topic. +* [Device group messaging](#device-group-messaging), which allows you to send a single message to multiple instances of an app running on devices belonging to a group. + +This part focuses on sending topic messages from your app server using the HTTP or XMPP protocols for FCM, and receiving and handling them in an iOS app. + +## Subscribe a client app to a topic + +Client apps can subscribe to any existing topic, or they can create a new topic. To learn more about this, go to [Topic Messaging](#subscribe-the-client–app-to-a-topic) section below. + +## Receive and handle topic messages + +FCM delivers topic messages in the same way as other downstream messages. To learn how to receive and handle messages, go to [Receive Messages](#receive-messages) section below. + +## Build send requests + +Sending messages to a Firebase Cloud Messaging topic is very similar to sending messages to an individual device or to a user group. To learn more about this, please, read the following [documentation][9]. + +--- + +# Receive Messages + +Once your client app is installed on a device, it can receive messages through the FCM APNs interface. You can immediately start sending notifications to user segments with the Notifications composer, or your application server can send messages with a notification payload through the APNs interface. + +Handling messages received through the FCM APNs interface is likely to cover most typical use cases. You can also [send upstream messages](#sending-upstream-messages), or [receive data messages in foregrounded apps](#handle-data-messages-in-foregrounded-apps). + +## Handle messages received through the FCM APNs interface + +When your app is in the background, iOS directs messages with the `notification` key to the system tray. A tap on a notification opens the app, and the content of the notification is passed to the `DidReceiveRemoteNotification` method in the `AppDelegate`. + +Implement AppDelegate `DidReceiveRemoteNotification` as shown: + +```csharp +public override void ReceivedRemoteNotification (UIApplication application, NSDictionary userInfo) +{ + // If you are receiving a notification message while your app is in the background, + // this callback will not be fired till the user taps on the notification launching the application. + // TODO: Handle data of notification + + // With swizzling disabled you must let Messaging know about the message, for Analytics + //Messaging.SharedInstance.AppDidReceiveMessage (userInfo); + + // Print full message. + Console.WriteLine (userInfo); +} + +public override void DidReceiveRemoteNotification (UIApplication application, NSDictionary userInfo, Action completionHandler) +{ + // If you are receiving a notification message while your app is in the background, + // this callback will not be fired till the user taps on the notification launching the application. + // TODO: Handle data of notification + + // With swizzling disabled you must let Messaging know about the message, for Analytics + //Messaging.SharedInstance.AppDidReceiveMessage (userInfo); + + // Print full message. + Console.WriteLine (userInfo); + + completionHandler (UIBackgroundFetchResult.NewData); +} +``` + +If you want to open your app and perform a specific action, set `click_action` in the [notification payload][10]. Use the value that you would use for the `category` key in the APNs payload. + +When overriding `DidReceiveRemoteNotification` method, you need to add "**remote-notification**" to the list of your **Required background modes** in your Info.plist + +### Interpreting notification message payload + +The payload of notification messages is a dictionary of keys and values. Notification messages sent through APNs follow the APNs payload format as below: + +```json +{ + "aps" : { + "alert" : { + "body" : "great match!", + "title" : "Portugal vs. Denmark", + }, + "badge" : 1, + }, + "customKey" : "customValue" +} +``` + +## Handle data messages in foregrounded apps + +To receive data-only messages directly from FCM (as opposed to via APNs) when the app is in the foreground, you'll need to connect to the FCM service and handle messages with `IMessagingDelegate` `DidReceiveMessage` interface method. + +To connect, set the `ShouldEstablishDirectChannel` property to `true` in the `AppDelegate`. FCM manages the connection, closing it when your app goes into the background and reopening it whenever the app is foregrounded. + +To receive data messages when your app is in the foreground, implement `DidReceiveMessage`. Your app can still receive data messages when it is in the background without this callback, but for foreground cases you'll need to handle it. + +### Handle messages with method swizzling disabled + +If you disable method swizzling, you'll need to call method `Messaging` `AppDidReceiveMessage` method. This lets FCM track message delivery and analytics, which is performed automatically with method swizzling enabled: + +```csharp +// With "FirebaseAppDelegateProxyEnabled": NO +public override void DidReceiveRemoteNotification (UIApplication application, NSDictionary userInfo, Action completionHandler) +{ + // Let FCM know about the message for analytics etc. + Messaging.SharedInstance.AppDidReceiveMessage (userInfo); + + // Do your magic to handle the notification data +} +``` + +Since iOS 10, you can set `UNUserNotificationCenter` `Delegate` to receive display notifications from Apple and `Messaging`s `Delegate` to receive data messages from FCM. If you do not set these two delegates with `AppDelegate`, method swizzling for message handling is disabled. You'll need to call method `AppDidReceiveMessage` to track message delivery and analytics. + +```csharp +// Receive displayed notifications for iOS 10 devices. +// Handle incoming notification messages while app is in the foreground. +[Export ("userNotificationCenter:willPresentNotification:withCompletionHandler:")] +public void WillPresentNotification (UNUserNotificationCenter center, UNNotification notification, Action completionHandler) +{ + var userInfo = notification.Request.Content.UserInfo; + + // With swizzling disabled you must let Messaging know about the message, for Analytics + //Messaging.SharedInstance.AppDidReceiveMessage (userInfo); + + // Print full message. + Console.WriteLine (userInfo); + + // Change this to your preferred presentation option + completionHandler (UNNotificationPresentationOptions.None); +} + +// Handle notification messages after display notification is tapped by the user. +[Export ("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")] +public void DidReceiveNotificationResponse (UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler) +{ + var userInfo = response.Notification.Request.Content.UserInfo; + + // Print full message. + Console.WriteLine (userInfo); + + completionHandler (); +} +``` + +### Interpreting data message payload + +The payload of data messages is a dictionary of keys and values. Data messages sent to the devices directly by FCM server are expressed in the format of a dictionary as below: + +```json +{ + "body" : "great match!", + "title" : "Portugal vs. Denmark", + "icon" : "myicon" +} +``` + +### Handle queued and deleted messages + +Apps that connect to FCM to receive data messages should handle `Messaging.MessagesDeletedNotification` with `NSNotificationCenter` or with `Messaging.Notifications.ObserveMessagesDeleted`. You may receive this callback when there are too many messages (>100) pending for your app on a particular device at the time it connects, or if the device hasn't connected to FCM for more than one month. When the app instance receives this callback, it should perform a full sync with your app server. + +--- + +# Topic Messaging + +Based on the publish/subscribe model, FCM topic messaging allows you to send a message to multiple devices that have opted in to a particular topic. You compose topic messages as needed, and FCM handles routing and delivering the message reliably to the right devices. + +For example, users of a local weather forecasting app could opt in to a "severe weather alerts" topic and receive notifications of storms threatening specified areas. Users of a sports app could subscribe to automatic updates in live game scores for their favorite teams. + +Some things to keep in mind about topics: + +* Topic messaging supports unlimited topics and subscriptions for each app. +* Topic messaging is best suited for content such as news, weather, or other publicly available information. +* Topic messages are optimized for throughput rather than latency. For fast, secure delivery to single devices or small groups of devices, [target messages to registration tokens][11], not topics. +* If you need to send messages to multiple devices per user, consider [device group messaging][12] for those use cases. + +## Subscribe the client app to a topic + +Client apps can subscribe to any existing topic, or they can create a new topic. When a client app subscribes to a new topic name (one that does not already exist for your Firebase project), a new topic of that name is created in FCM and any client can subsequently subscribe to it. + +The `Messaging` class handles topic messaging functionality. To subscribe to a topic, call `Subscribe` method from your application's main thread (FCM is not thread-safe): + +```csharp +Messaging.SharedInstance.Subscribe ("topic", (error) => { + // ... +}); +Console.WriteLine ("Subscribed to topic"); + +// async/away Version + +try { + await Messaging.SharedInstance.SubscribeAsync ("topic"); + Console.WriteLine ("Subscribed to topic"); +} catch (NSErrorException ex) { + // ... +} +``` + +This makes an asynchronous request to the FCM backend and subscribes the client to the given topic. Before calling `Subscribe` method, make sure that the client app instance has already received a registration token via the callback `IMessagingDelegate` `DidReceiveRegistrationToken` method. + +If the subscription request fails initially, FCM retries until it can subscribe to the topic successfully. Each time the app starts, FCM makes sure that all requested topics have been subscribed. + +To unsubscribe, call `Unsubscribe` method, and FCM unsubscribes from the topic in the background. + +```csharp +Messaging.SharedInstance.Unsubscribe ("topic", (error) => { + // ... +}); +Console.WriteLine ("Unsubscribed to topic"); + +// async/away Version + +try { + await Messaging.SharedInstance.UnsubscribeAsync ("topic"); + Console.WriteLine ("Unsubscribed to topic"); +} catch (NSErrorException ex) { + // ... +} +``` + +## Manage topic subscriptions on the server + +You can take advantage of [Instance ID APIs][13] to perform basic topic management tasks from the server side. Given the registration token(s) of client app instances, you can do the following: + +* Find out details about a client app instance's subscriptions, including each topic name and subscribe date. See [Get information about app instances][14]. +* Subscribe or unsubscribe an app instance to a topic. See [Create a relationship mapping for an app instance][15]. +* Subscribe or unsubscribe multiple app instances to a topic. See [Manage relationship maps for multiple app instances][16]. + +## Receive and handle topic messages + +FCM delivers topic messages in the same way as other downstream messages. To learn how to receive and handle messages, go to [Receive Messages](#receive-messages) section above. + +## Build send requests + +Sending messages to a Firebase Cloud Messaging topic is very similar to sending messages to an individual device or to a user group. To learn more about this, please, read the following [documentation][9]. + +--- + +# Device Group Messaging + +With device group messaging, you can send a single message to multiple instances of an app running on devices belonging to a group. Typically, "group" refers a set of different devices that belong to a single user. All devices in a group share a common notification key, which is the token that FCM uses to fan out messages to all devices in the group. + +You can use device group messaging with the [Admin SDKs][17], or by implementing the [XMPP][18] or [HTTP][19] protocols on your app server. The maximum number of members allowed for a notification key is 20. + +## Managing device groups + +To learn about this, please, read the following [documentation][20]. + +## Sending downstream messages to device groups + +To learn about this, please, read the following [documentation][21]. + +## Sending upstream messages to device groups + +To send upstream messages to device groups on iOS, the iOS client app needs to call `Messaging.SendMessage` method. + +--- + +# Sending Upstream Messages + +If your app server implements the [XMPP Connection Server][18] protocol, it can receive upstream messages from a user's device to the cloud. To initiate an upstream message, the client app sends a request containing the following: + +* The address of the receiving app server in the format **SENDER_ID@gcm.googleapis.com**. +* A message ID that should be unique for each [sender ID][22]. +* The message data comprising the key-value pairs of the message's payload. + +When it receives this data, FCM builds an XMPP stanza to send to the app server, adding some additional information about the sending device and app. + +## Send an upstream message + +To send messages upstream to the server, an iOS client app composes a message, connects to FCM, and calls `SendMessage` method. + +To connect, set the `ShouldEstablishDirectChannel` property to `true` in the `AppDelegate`. FCM manages the connection, closing it when your app goes into the background and reopening it whenever the app is foregrounded. + +Configure the call to SendMessage method as shown: + +```csharp +Messaging.SharedInstance.SendMessage (message, to, messageId, ttl); +``` + +where: + +* `message` is a `Dictionary` or `NSDictionary` of keys and values as strings. Any key-value pair that is not a string is ignored. +* `to` is the address of the receiving app server in the format **SENDER_ID@gcm.googleapis.com**. +* `messageId` is a unique message identifier. All the message receiver callbacks are identified on the basis of this message ID. +* `ttl` is the time to live. If FCM can't deliver the message before this expiration is reached, it sends a notification back to the client. + +The FCM client library caches the message on the client app and sends it when the client has an active server connection. On receiving the message, the FCM connection server sends it to the app server. + +### Handle upstream message callbacks + +Handle upstream callbacks by adding observers that listen for `SendErrorNotification` and `SendSuccessNotification`, and then retrieve error information from the methods. In this example, `HandleSendMessageError` is the method used to handle the callback: + +```csharp +var sendSuccessToken = Messaging.Notifications.ObserveSendSuccess (HandleSendMessageSuccess); +var sendErrorToken = Messaging.Notifications.ObserveSendError (HandleSendMessageError); + +void HandleSendMessageSuccess(object sender, NSNotificationEventArgs e) +{ + var messageId = e.Notification.Object.ToString (); +} + +void HandleSendMessageError (object sender, NSNotificationEventArgs e) +{ + var messageId = e.Notification.Object.ToString (); + var userInfo = e.Notification.UserInfo; // contains error info etc +} + +// Call this lines to stop receiving notifications +sendSuccessToken.Dispose (); +sendErrorToken.Dispose (); + +// Or + +var sendSuccessToken = NSNotificationCenter.DefaultCenter.AddObserver (Messaging.SendSuccessNotification, HandleSendMessageSuccess); +var sendErrorToken = NSNotificationCenter.DefaultCenter.AddObserver (Messaging.SendSuccessNotification, HandleSendMessageError); + +void HandleSendMessageSuccess (NSNotification notification) +{ + var messageId = notification.Object.ToString (); +} + +void HandleSendMessageError (NSNotification notification) +{ + var messageId = notification.Object.ToString (); + var userInfo = notification.UserInfo; // contains error info etc +} + +// Call this lines to stop receiving notifications +NSNotificationCenter.DefaultCenter.RemoveObserver (sendSuccessToken); +NSNotificationCenter.DefaultCenter.RemoveObserver (sendErrorToken); +``` + +To optimize network usage, FCM batches responses to `HandleSendMessageError` and `HandleSendMessageSuccess`, so the acknowledgement may not be immediate for each message. + +## Receive XMPP messages on the app server + +To learn about this, please, read the following [documentation][23]. + +--- + +# Send Messages with the Firebase Console + +You can send notification messages to iOS and Android devices using the Notifications composer in the Firebase console. To learn more about this, please, read the following [documentation][24]. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/cloud-messaging/ios/client) to see original Firebase documentation._ + +[1]: https://firebase.google.com/docs/cloud-messaging/concept-options +[2]: https://firebase.google.com/docs/cloud-messaging/ios/certs +[3]: https://firebase.google.com/docs/cloud-messaging/ios/client#upload_your_apns_authentication_key +[4]: https://developer.apple.com/support/app-store/ +[5]: https://firebase.google.com/console/ +[6]: http://support.google.com/firebase/answer/7015592 +[7]: https://developers.google.com/instance-id/reference/server#create_registration_tokens_for_apns_tokens +[8]: https://console.firebase.google.com/project/_/notification +[9]: https://firebase.google.com/docs/cloud-messaging/send-message#send_messages_to_topics_1 +[10]: https://firebase.google.com/docs/cloud-messaging/http-server-ref#notification-payload-support +[11]: https://firebase.google.com/docs/cloud-messaging/send-message#send_messages_to_specific_devices +[12]: https://firebase.google.com/docs/cloud-messaging/send-message#send_messages_to_device_groups +[13]: https://developers.google.com/instance-id/ +[14]: https://developers.google.com/instance-id/reference/server#get_information_about_app_instances +[15]: https://developers.google.com/instance-id/reference/server#create_a_relation_mapping_for_an_app_instance +[16]: https://developers.google.com/instance-id/reference/server#manage_relationship_maps_for_multiple_app_instances +[17]: https://firebase.google.com/docs/cloud-messaging/admin/ +[18]: https://firebase.google.com/docs/cloud-messaging/server#implementing-the-xmpp-server-protocol +[19]: https://firebase.google.com/docs/cloud-messaging/server#implementing-the-http-server-protocol +[20]: https://firebase.google.com/docs/cloud-messaging/ios/device-group#managing_device_groups +[21]: https://firebase.google.com/docs/cloud-messaging/ios/device-group#sending_downstream_messages_to_device_groups +[22]: https://firebase.google.com/docs/cloud-messaging/concept-options#senderid +[23]: https://firebase.google.com/docs/cloud-messaging/ios/upstream#receive_xmpp_messages_on_the_app_server +[24]: https://firebase.google.com/docs/cloud-messaging/send-with-console +[note_icon]: https://cdn3.iconfinder.com/data/icons/UltimateGnome/22x22/apps/gnome-app-install-star.png +[advice_icon]: https://cdn1.iconfinder.com/data/icons/nuove/22x22/actions/messagebox_warning.png +[warning_icon]: https://cdn2.iconfinder.com/data/icons/freecns-cumulus/32/519791-101_Warning-20.png diff --git a/docs/Firebase/CrashReporting/Details.md b/docs/Firebase/CrashReporting/Details.md new file mode 100755 index 00000000..f39b39c7 --- /dev/null +++ b/docs/Firebase/CrashReporting/Details.md @@ -0,0 +1,39 @@ +## **Important note:** *This component is only compatible with Xamarin Studio and Visual Studio for Mac.* + +Firebase Crash Reporting uses shell scripts to upload symbols to Firebase Console, but scripts use commands that are only available in Mac. Therefore, this component is only compatible with Xamarin Studio and Visual Studio for Mac. + +# Firebase Crash Reporting on iOS + +Comprehensive and actionable information to help diagnose and fix problems in your app. + +Crash Reporting creates detailed reports of the errors in your app. Errors are grouped into clusters of similar stack traces and triaged by the severity of impact on your users. In addition to automatic reports, you can log custom events to help capture the steps leading up to a crash. + +## Key capabilities + +| | | +|-:|--| +| **Monitor fatal errors** | Monitor fatal errors in iOS. Reports are triaged by the severity of impact on users. | +| **Collect the data you need to diagnose problems** | Each report contains a full stack trace as well as device characteristics, performance data, and user circumstances when the error took place. Similar reports are automatically clustered to make it easier to identify related bugs. | +| **Email alerts** | Enable email alerts to receive frequent updates when new crashes are uncovered or regressions are detected. | +| **Integrate with Analytics** | Errors captured are set as **app_exception** events in Firebase Analytics, allowing you to filter audiences based on who sees errors. In addition to stack traces, Crash Reporting also integrates with Analytics to provide you with the list of events that preceded a crash. This information helps to simplify your debugging process. | +| **Free and easy** | Crash Reporting is free to use. Once you've added Firebase to your app, it's just a few lines of code to enable comprehensive error reporting. | + +## User privacy + +Firebase Crash Reporting does not itself collect any personally identifiable information (such as names, email addresses, or phone numbers). Developers can collect additional data using Crash Reporting with log and exception messages. Such data collected through Crash Reporting should not contain information that personally identifies an individual to Google. + +Here is an example of a log message that does not contain personally identifiable information: + +```csharp +Firebase.CrashReporting.CrashReporting.Log ("SQL database failed to initialize"); +``` + +And here is another one that does contain personally identifiable information: + +```csharp +Firebase.CrashReporting.CrashReporting.Log ($"{user.Email} purchased product {product.Id}"); +``` + +If identifying a user is necessary to diagnose an issue, then you must use adequate obfuscation measures to render the data you send to Google anonymous. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/crash/) to see original Firebase documentation._ diff --git a/docs/Firebase/CrashReporting/GettingStarted.md b/docs/Firebase/CrashReporting/GettingStarted.md new file mode 100755 index 00000000..8731ed72 --- /dev/null +++ b/docs/Firebase/CrashReporting/GettingStarted.md @@ -0,0 +1,157 @@ +## **Important note:** *This component is only compatible with Xamarin Studio and Visual Studio for Mac.* + +Firebase Crash Reporting uses shell scripts to upload symbols to Firebase Console, but scripts use commands that are only available in Mac. Therefore, this component is only compatible with Xamarin Studio and Visual Studio for Mac. + +# Use Firebase Crash Reporting on iOS + +Firebase Crash Reporting creates detailed reports of the errors in your app. Errors are grouped into clusters of similar stack traces, and triaged by the severity of impact on your users. In addition to automatic reports, you can log custom events to help capture the steps leading up to a crash. + +## Table of Content + +- [User privacy](#user-privacy) +- [Add Firebase to your app](#add-firebase-to-your-app) +- [Configure Crash Reporting in your app](#configure-crash-reporting-in-your-app) +- [Create your first error](#create-your-first-error) +- [Upload symbol files](#upload-symbol-files) + - [Upload symbol files with Visual Studio](#upload-symbol-files-with-visual-studio) + - [Upload symbol files with Terminal](#upload-symbol-files-with-terminal) +- [Upload your first error to Firebase](#upload-your-first-error-to-firebase) +- [Create custom logs](#create-custom-logs) + - [Known issues](#known-issues) + +## User privacy + +Firebase Crash Reporting does not itself collect any personally identifiable information (such as names, email addresses, or phone numbers). Developers can collect additional data using Crash Reporting with log and exception messages. Such data collected through Crash Reporting should not contain information that personally identifies an individual to Google. + +Here is an example of a log message that does not contain personally identifiable information: + +```csharp +Firebase.CrashReporting.CrashReporting.Log ("SQL database failed to initialize"); +``` + +And here is another one that does contain personally identifiable information: + +```csharp +Firebase.CrashReporting.CrashReporting.Log ($"{user.Email} purchased product {product.Id}"); +``` + +If identifying a user is necessary to diagnose an issue, then you must use adequate obfuscation measures to render the data you send to Google anonymous. + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][1], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][2]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][2] again at any time. + +## Configure Crash Reporting in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Xamarin Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Add the following line of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` and `Firebase.CrashReporting` namespaces): + +```csharp +CrashReporting.Configure (); +App.Configure (); +``` + +## Create your first error + +Add a crash code right after Firebase initialization call in your AppDelegate's `FinishedLaunching` method to cause a crash when the app launches: + +```csharp +App.Configure (); + +// Cause crash code +var crash = new NSObject (); +crash.PerformSelector (new Selector ("doesNotRecognizeSelector"), crash, 0); +``` + +Don't run the app yet, please read **Upload symbol files** before running. + +## Upload symbol files + +In order to view human-readable crash reports, you will need to upload symbol files to Firebase Console after each build. To achieve this, you will need to upload the executable of your app that is generated after each build (the executable file lives inside of **Your.app** that is generated commonly inside of **bin** folder of your project). But first, you will need to download the **service account key** to authenticate your uploads: + +1. Go to [Firebase Console][1] and select your project. +2. Open **project settings** and go to **Service Accounts** tab. +3. Select **Crash Reporting** and click on **Generate New Private Key** button. +4. Name the file as **service-account.json** and save it in the root of your project folder. + +### Upload symbol files with Visual Studio + +Follow these steps to upload your app symbols with Xamarin Studio: + +* In Visual Studio, open **Project Options** of your app and go to **Build** > **Custom Commands**. +* Double check that **Debug** configuration and **iPhone** platform is selected. +* In Combobox select **After Build** option. +* Paste the following command in **Command** text field: + +``` +sh ${ProjectDir}/scripts/FirebaseCrashReporting/xamarin_upload_symbols.sh -n ${ProjectName} -b ${TargetDir} -i ${ProjectDir}/Info.plist -p ${ProjectDir}/GoogleService-Info.plist -s ${ProjectDir}/service-account.json +``` + +* Save options changed. +* Now, build your app with **Debug** configuration. + +Depending of your internet connection, the build can take some minutes because the script is uploading your symbols to Firebase. + +> ***Note:*** *If you don't want to keep uploading your symbols after each build, just remove the **After Build** command.* + +### Upload symbol files with Terminal + +Follow these steps to upload your app symbols with Terminal: + +* In Xamarin Studio, select **Debug** configuration, **iPhone** Platform and build the app (cmd + k) (don't run it). +* In Terminal, go to your project folder and run the following command: + +``` +# Don't forget to replace [YourAppName] value +sh scripts/FirebaseCrashReporting/xamarin_upload_symbols.sh -n [YourAppName] -b bin/iPhone/Debug -i Info.plist -p GoogleService-Info.plist -s service-account.json +``` + +* Depending of your internet connection, the script can take some minutes because it's uploading your symbols to Firebase. + +## Upload your first error to Firebase + +After you uploaded your symbol files to Firebase, do the following steps to view your crash in Firebase Console: + +1. Launch the app from Xamarin Studio. +2. Wait until your app crashes, then, stop the debugging. +3. Launch the app directly from the home screen on the device. +4. Wait until your app crashes. +5. Remove the crashing line so your app can start successfully. +6. Run your app again. +7. Within 20 minutes your crash should show up in **Crash Reporting** section of Firebase Console. + +## Create custom logs + +You can use `Log` method to create custom log messages that are included in your crash reports. The following example demonstrates creating a log message: + +```csharp +using Firebase.CrashReporting; + +// ... + +CrashReporting.Log ("Cause Crash button clicked"); + +// Cause crash code +var crash = new NSObject (); +crash.PerformSelector (new Selector ("doesNotRecognizeSelector"), crash, 0); +``` + +> _**Note:**_ _The string given to this method must be an escaped string due it will be passed to a C function and it expects an escaped string. For example, if you want to print a %, you must type %%. Passing an unescaped string may cause the termination of your app._ + +### Known issues + +* App doesn't compile when `Incremental builds` is enabled. (Bug [#43689][3]) +* The Firebase SDK does not currently support using the `NSException` class in the Xcode simulator. If you use this class, it will result in malformed stack traces in the Firebase console. As a workaround, either use a physical device or test with a different type of exception. +* Crash Reporting uses a unique ID to identify each user. Due to a known bug in Xcode 8.1, creating this ID fails on iOS 10 simulators, preventing the upload of error reports. To work around this in Xcode 8.1, you can run tests on a device, or turn on **Keychain Sharing** in your **Entitlements.plist** file. This bug has been addressed in the beta release of Xcode 8.2. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/crash/ios) to see original Firebase documentation._ + +[1]: https://firebase.google.com/console/ +[2]: http://support.google.com/firebase/answer/7015592 +[3]: https://bugzilla.xamarin.com/show_bug.cgi?id=43689 diff --git a/docs/Firebase/Crashlytics/Details.md b/docs/Firebase/Crashlytics/Details.md new file mode 100755 index 00000000..219d23ff --- /dev/null +++ b/docs/Firebase/Crashlytics/Details.md @@ -0,0 +1,16 @@ +Get clear, actionable insight into app issues with this powerful crash reporting solution. + +Firebase Crashlytics is a lightweight, realtime crash reporter that helps you track, prioritize, and fix stability issues that erode your app quality. Crashlytics saves you troubleshooting time by intelligently grouping crashes and highlighting the circumstances that lead up to them. + +Find out if a particular crash is impacting a lot of users. Get alerts when an issue suddenly increases in severity. Figure out which lines of code are causing crashes. + +## Key capabilities + +| | | +|-:|-| +| **Curated crash reports** | Crashlytics synthesizes an avalanche of crashes into a manageable list of issues, provides contextual information, and highlights the severity and prevalence of crashes so you can pinpoint the root cause faster. | +| **Cures for the common crash** | Crashlytics offers Crash Insights, helpful tips that highlight common stability problems and provide resources that make them easier to troubleshoot, triage, and resolve. | +| **Integrated with Analytics** | Crashlytics can capture your app's errors as **app_exception** events in Analytics. The events simplify debugging by giving you access a list of other events leading up to each crash, and provide audience insights by letting you pull Analytics reports for users with crashes. | +| **Realtime alerts** | Get realtime alerts for new issues, regressed issues, and growing issues that might require immediate attention. | + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/crashlytics/) to see original Firebase documentation._ diff --git a/docs/Firebase/Crashlytics/GettingStarted.md b/docs/Firebase/Crashlytics/GettingStarted.md new file mode 100755 index 00000000..f9e7676a --- /dev/null +++ b/docs/Firebase/Crashlytics/GettingStarted.md @@ -0,0 +1,356 @@ +# Get Started with Firebase Crashlytics for iOS + +## Table of Content + +- [Get Started with Firebase Crashlytics for iOS](#get-started-with-firebase-crashlytics-for-ios) + - [Table of Content](#table-of-content) + - [Before you begin](#before-you-begin) + - [Add Firebase to your app](#add-firebase-to-your-app) + - [Configure Firebase in your app](#configure-firebase-in-your-app) +- [Upgrade to Firebase Crashlytics from Firebase Crash Reporting](#upgrade-to-firebase-crashlytics-from-firebase-crash-reporting) + - [Update project dependencies](#update-project-dependencies) + - [Migrate logs](#migrate-logs) + - [Set up manual initialization](#set-up-manual-initialization) +- [Test your Firebase Crashlytics implementation](#test-your-firebase-crashlytics-implementation) + - [Force a crash to test your implementation](#force-a-crash-to-test-your-implementation) + - [Adjust your project's debug settings](#adjust-your-projects-debug-settings) + - [Test it out](#test-it-out) + - [Enable Crashlytics debug mode](#enable-crashlytics-debug-mode) +- [Customize your Firebase Crashlytics crash reports](#customize-your-firebase-crashlytics-crash-reports) + - [Enable opt-in reporting](#enable-opt-in-reporting) + - [Add custom logs](#add-custom-logs) + - [Add custom keys](#add-custom-keys) + - [Set user IDs](#set-user-ids) + - [Log non-fatal exceptions](#log-non-fatal-exceptions) + - [Logs and custom keys](#logs-and-custom-keys) + - [Performance considerations](#performance-considerations) + - [What about NSExceptions?](#what-about-nsexceptions) + - [Manage Crash Insights data](#manage-crash-insights-data) +- [Extend Firebase Crashlytics with Cloud Functions](#extend-firebase-crashlytics-with-cloud-functions) + +## Before you begin + +* Enable Crashlytics: Click **Set up Crashlytics** in the [Firebase console][1]. + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][1], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][2]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][2] again at any time. + +## Configure Firebase in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Visual Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behavior to `Bundle Resource` by Right clicking/Build Action. +3. Add the `Xamarin.Firebase.iOS.Core` NuGet to your project. +4. Add the following lines of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` and `Firebase.Crashlytics` namespace): + +```csharp +App.Configure (); +Crashlytics.Configure (); +``` + +--- + +# Upgrade to Firebase Crashlytics from Firebase Crash Reporting + +Crashlytics is the new, primary crash reporter for Firebase. If your app uses Firebase Crash Reporting, there's good news: Crashlytics offers enhanced crash reporting with nearly the same setup process you're used to, so upgrading is straightforward: + +1. Update your project's dependencies. +2. Migrate any log calls, if you have them. +3. Set up manual initialization, if you used it. + +## Update project dependencies + +To update your app's depencencies for Firebase Crashlytics, swap the Xamarin.Firebase.iOS.CrashReporting NuGet with the Xamarin.Firebase.iOS.Crashlytics NuGet and remove the Firebase Crash Reporting custom command: + +1. In Visual Studio, do a double click on **Packages** folder and add the Xamarin.Firebase.iOS.Crashlytics NuGet. +2. Remove Xamarin.Firebase.iOS.CrashReporting NuGet by right clicking/Remove. +3. Open your app **Project Options**, go to **Build** > **Custom Command** and delete the Crash Reporting command: + + ``` + sh ${ProjectDir}/scripts/FirebaseCrashReporting/xamarin_upload_symbols.sh -n ${ProjectName} -b ${TargetDir} -i ${ProjectDir}/Info.plist -p ${ProjectDir}/GoogleService-Info.plist -s ${ProjectDir}/service-account.json + ``` + +## Migrate logs + +If you used Firebase Crash Reporting custom logs, you have to update those for Firebase Crashlytics too: + +| Firebase Crash Reporting | Firebase Crashlytics | +|--------------------------|--------------------------------------------------------------------------------------------------| +| CrashReporting.Log | Logging.Log / Logging.LogCallerInformation
Logging.NSLog / Logging.NSLogCallerInformation | + +> ![warning_icon] _**Note:**_ _The string given to these methods must be an escaped string due it will be passed to a C function and it expects an escaped string. For example, if you want to print a %, you must type %%. Passing an unescaped string may cause the termination of your app._ + +## Set up manual initialization + +Like Firebase Crash Reporting, the Firebase Crashlytics SDK automatically initializes Crashlytics as soon as you add it to your app. If instead you initialize reporting manually, Crashlytics has a way to do that as well: + +1. Turn off automatic collection with a new key to your Info.plist file: + * Key: `firebase_crashlytics_collection_enabled` + * Value: `false` +2. Replace the Crash Reporting initialization call with one for Crashlytics: + ```csharp + // Delete Crash Reporting + // CrashReporting.SharedInstance.CrashCollectionEnabled = true; + + // Add Crashlytics + Fabric.Fabric.With (typeof (Crashlytics)); + ``` + +--- + +# Test your Firebase Crashlytics implementation + +## Force a crash to test your implementation + +You don't have to wait for a crash to know that Crashlytics is working. You can use the SDK to force a crash by adding the following code to your app: + +```csharp +using Firebase.Crashlytics; + +... + +public override void ViewDidLoad () +{ + base.ViewDidLoad (); + + var button = new UIButton (UIButtonType.RoundedRect) { + Frame = new CGRect (0, 0, 100, 30), + TranslatesAutoresizingMaskIntoConstraints = false + }; + button.SetTitle ("Crash", UIControlState.Normal); + button.TouchUpInside += Button_TouchUpInside; + View.AddSubview (button); + + button.AddConstraint (NSLayoutConstraint.Create (button, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal, + View, NSLayoutAttribute.CenterX, + 1, 0)); + button.AddConstraint (NSLayoutConstraint.Create (button, NSLayoutAttribute.CenterY, NSLayoutRelation.Equal, + View, NSLayoutAttribute.CenterY, + 1, 0)); +} + +void Button_TouchUpInside (object sender, EventArgs e) +{ + Crashlytics.SharedInstance.Crash (); +} +``` + +## Adjust your project's debug settings + +Crashlytics can’t capture crashes if your build attaches a debugger at launch. Adjust your build settings to change the project's debug information format: + +1. In Visual Studio, open your app project settings. + * _Mac:_ Go to **Build** > **iOS Build** + * _Windows:_ Go to **iOS Build** +2. Select an **iPhone** platform. +2. Make sure that **Strip native debugging symbols** is unchecked. + +## Test it out + +The code snippet above adds a button that crashes your app when pressed. For it to work, run the app without a debugger, the debugger interferes with Crashlytics: + +1. Select a physical device and Debug configuration. +2. Run the app without debugging: + * _Mac:_ Open **Run** menu > **Start Without Debugging** + * _Windows:_ Open **Build** menu > **Start Without Debugging** + + The build process will upload the `dSYM` file of the app to Firebase in a background process. +3. Touch Crash button to crash the app. +4. Open your app once more to let the Crashlytics API report the crash. Your crash should show up in the Firebase console within 5 minutes. + +## Enable Crashlytics debug mode + +If your forced crash didn't crash, crashed before you wanted it to, or you're experiencing some other issue with Crashlytics, you can enable Crashlytics debug mode to track down the problem: + +```csharp +public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) +{ + ... + Fabric.Fabric.SharedSdk.Debug = true; + ... + + return true; +} +``` + +--- + +# Customize your Firebase Crashlytics crash reports + +Firebase Crashlytics can work with very little setup on your part—as soon as you add the SDK, Crashlytics gets to work sending crash reports to the Firebase console. + +For more fine-grained control of your crash reports, customize your Crashlytics SDK configuration. For example, you can enable opt-in reporting for privacy-minded users, add logs to track down pesky bugs, and more. + +## Enable opt-in reporting + +By default, Firebase Crashlytics automatically collects crash reports for all your app's users. To give users more control over the data they send, you can enable opt-in reporting instead. + +To do that, you have to disable automatic collection and initialize Crashlytics only for opt-in users. + +1. Turn off automatic collection with a new key to your **Info.plist** file: + * Key: `firebase_crashlytics_collection_enabled` + * Value: `false` +2. Enable collection for selected users by initializing Crashlytics at runtime: + ```csharp + Fabric.Fabric.With (typeof (Crashlytics)); + ``` + +## Add custom logs + +To give yourself more context for the events leading up to a crash, you can add custom Crashlytics logs to your app. Crashlytics associates the logs with your crash data and makes them visible in the Firebase console. + +Use `LogCallerInformation` and `NSLogCallerInformation` methods to help pinpoint issues. It automatically includes information about the C# filename, method, and line number associated with the log. You can optionally pass the class name to have a better log: + +* **Debug builds:** `NSLogCallerInformation` method passes through to `NSLog` method so you can see the output in Visual Studio and on the device. +* **Release builds:** To improve performance, `LogCallerInformation` method silences all other output and will be only shown on Firebase Crashlytics dashboard when a crash occurs. + +```csharp +void Button_TouchUpInside (object sender, EventArgs e) +{ + var data = new Dictionary { + { "A keyblade", "for a heartless" }, + { "A heart", "for a nobody" } + }; + var nsData = NSDictionary.FromObjectsAndKeys (data.Values.ToArray (), data.Keys.ToArray (), data.Keys.Count); + + Logging.LogCallerInformation ($"Hi! Maybe, I'm about to crash! Here's some data: {nsData}", nameof (ViewController)); + + // or + + Logging.NSLogCallerInformation ($"Hi! Maybe, I'm about to crash! Here's some data: {nsData}", nameof (ViewController)); + + Crashlytics.SharedInstance.Crash (); +} +``` + +Assuming that you are coding in a file named `MyViewController.cs`, the output will be: + +``` +MyViewController.cs: ViewController.Button_TouchUpInside line 47 $ Hi! Maybe I'm about to crash! Here's some data: { + "A keyblade" = "for a heartless"; + "A heart" = "for a nobody"; +} +``` + +If you omit the second parameter of `NSLogCallerInformation` or `LogCallerInformation` method, the output will be: + +``` +MyViewController.cs: Button_TouchUpInside line 47 $ Hi! Maybe I'm about to crash! Here's some data: { + "A keyblade" = "for a heartless"; + "A heart" = "for a nobody"; +} +``` + +> ![note_icon] _To avoid slowing down your app, Crashlytics limits logs to 64kB. Crashlytics deletes older log entries if a session's logs go over that limit._ + +> ![warning_icon] _**Note:**_ _The string given to these methods must be an escaped string due it will be passed to a C function and it expects an escaped string. For example, if you want to print a %, you must type %%. Passing an unescaped string may cause the termination of your app._ + +## Add custom keys + +Custom keys help you get the specific state of your app leading up to a crash. You can associate arbitrary key/value pairs with your crash reports, and see them in the Firebase console: + +```csharp +void SetObjectValue (NSObject value, string key); +void SetStringValue (string value, string key); +void SetIntValue (int value, string key); +void SetBoolValue (bool value, string key); +void SetFloatValue (float value, string key); +``` + +Sometimes you need to change the existing key value. Call the same key, but replace the value, for example: + +```csharp +Crashlytics.SharedInstance.SetIntValue (3, "current_level"); +Crashlytics.SharedInstance.SetStringValue ("logged_in", "last_UI_action"); +``` + +> ![note_icon] _Crashlytics supports a maximum of 64 key/value pairs. Once you reach this threshold, additional values are not saved. Each key/value pair can be up to 1 kB in size_ + +## Set user IDs + +To diagnose an issue, it’s often helpful to know which of your users experienced a given crash. Crashlytics includes a way to anonymously identify users in your crash reports. + +To add user IDs to your reports, assign each user a unique identifier in the form of an ID number, token, or hashed value: + +```csharp +Crashlytics.SharedInstance.SetUserIdentifier ("123456789"); +``` + +If you ever need to clear a user identifier after you set it, reset the value to a blank string. + +## Log non-fatal exceptions + +In addition to automatically reporting your app’s crashes, Crashlytics lets you log non-fatal exceptions. + +On iOS, you do that by recording `NSError` objects, which Crashlytics reports and groups much like crashes: + +```csharp +Crashlytics.SharedInstance.RecordError (error); +``` + +When using the `RecordError` method, it's important to understand the `NSError` structure and how Crashlytics uses the data to group crashes. Incorrect usage of the `RecordError` method can cause unpredicatable behavior and may require Crashlytics to limit reporting of logged errors for your app. + +An `NSError` object has three arguments: `NSString Domain`, `nint Code`, and `NSDictionary UserInfo`. + +Unlike fatal crashes, which are grouped via stack trace analysis, logged errors are grouped by the NSError `Domain` and `Code`. This is an important distinction between fatal crashes and logged errors. For example, logging an error such as: + +```csharp +var userInfo = new Dictionary { + { NSError.LocalizedDescriptionKey, NSBundle.MainBundle.LocalizedString ("The request failed.", null) }, + { NSError.LocalizedFailureReasonErrorKey, NSBundle.MainBundle.LocalizedString ("The response returned a 404.", null) }, + { NSError.LocalizedRecoverySuggestionErrorKey, NSBundle.MainBundle.LocalizedString ("Does this page exist?", null) }, + { "ProductId", "123456" }, + { "UserId", "Jane Smith" } +}; + +var error = new NSError (new NSString ("SomeErrorDomain"), + -1001, + NSDictionary.FromObjectsAndKeys (userInfo.Values.ToArray (), userInfo.Keys.ToArray (), userInfo.Keys.Count)); +``` + +Creates a new issue that is grouped by `SomeErrorDomain` and `-1001`. Additional logged errors that use the same domain and code values will be grouped under this issue. + +> ![warning_icon] _Avoid using unique values, such as user ID, product ID, and timestamps in the domain and code fields. Using unique values in these fields causes a high cardinality of issues and may result in Crashlytics needing to limit the reporting of logged errors in your app. Unique values should instead be added to the `UserInfo` Dictionary object._ + +Data contained within the `UserInfo` object are converted to key-value pairs and displayed in the keys/logs section within an individual issue. + +![note_icon] _Crashlytics only stores the most recent 8 exceptions in a given app session. If your app throws more than 8 exceptions in a session, older exceptions are lost._ + +### Logs and custom keys + +Just like crash reports, you can embed logs and custom keys to add context to the NSError. However, there is a difference in what logs are attached to crashes versus logged errors. When a crash occurs and the app is relaunched, the logs Crashlytics retrieves from disk are those that were written right up to the time of the crash. When you log an NSError, the app does not immediately terminate. Because Crashlytics only sends the logged error report on the next app launch, and because Crashlytics must limit the amount of space allocated for logs on disk, it is possible to log enough after an NSError is recorded so that all relevant logs are rotated out by the time Crashlytics sends the report from the device. Keep this balance in mind when logging NSErrors and using `Log` and custom keys in your app. + +### Performance considerations + +Keep in mind that logging an NSError can be fairly expensive. At the time you make the call, Crashlytics captures the current thread’s call stack using a process called stack unwinding. This process can be CPU and I/O intensive, particularly on architectures that support DWARF unwinding (arm64 and x86). After the unwind is complete, the information is written to disk synchronously. This prevents data loss if the next line were to crash. + +While it is safe to call this API on a background thread, remember that dispatching this call to another queue will lose the context of the current stack trace. + +### What about NSExceptions? + +Crashlytics doesn’t offer a facility for logging/recording NSException instances directly. Generally speaking, the Cocoa and Cocoa Touch APIs are not exception-safe. That means the use of `@catch` in your Objective-C library code or in a third-party Objective-C code can have very serious unintended side-effects in your process, even when used with extreme care. You should never use `@catch` statements in your Objective-C code. Please refer to Apple’s [documentation][2] on the topic. + +## Manage Crash Insights data + +Crash Insights helps you resolve issues by comparing your anonymized stack traces to traces from other Firebase apps and letting you know if your issue is part of a larger trend. For many issues, Crash Insights even provides resources to help you debug the crash. + +Crash Insights uses aggregated crash data to identify common stability trends. If you’d prefer not to share your app's data, you can opt-out of Crash Insights from the Crash Insights menu at the top of your Crashlytics issue list in the [Firebase console][1]. + +--- + +# Extend Firebase Crashlytics with Cloud Functions + +You can trigger a function in response to Crashlytics issue events including new issues, regressed issues, and velocity alerts. To learn more about this, please, read the following [documentation][3]. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/crashlytics/get-started) to see original Firebase documentation._ + +[1]: https://console.firebase.google.com +[2]: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Exceptions/Articles/ExceptionsAndCocoaFrameworks.html +[3]: https://firebase.google.com/docs/crashlytics/extend-with-cloud-functions +[note_icon]: https://cdn3.iconfinder.com/data/icons/UltimateGnome/22x22/apps/gnome-app-install-star.png +[warning_icon]: https://cdn2.iconfinder.com/data/icons/freecns-cumulus/32/519791-101_Warning-20.png diff --git a/docs/Firebase/Database/Details.md b/docs/Firebase/Database/Details.md new file mode 100755 index 00000000..619419a7 --- /dev/null +++ b/docs/Firebase/Database/Details.md @@ -0,0 +1,23 @@ +Store and sync data with Firebase NoSQL cloud database. Data is synced across all clients in realtime, and remains available when your app goes offline. + +The Firebase Realtime Database is a cloud-hosted database. Data is stored as JSON and synchronized in realtime to every connected client. When you build cross-platform apps with our iOS, Android, and JavaScript SDKs, all of your clients share one Realtime Database instance and automatically receive updates with the newest data. + +## Key capabilities + +| | | +|-:|--| +| **Realtime** | Instead of typical HTTP requests, the Firebase Realtime Database uses data synchronization—every time data changes, any connected device receives that update within milliseconds. Provide collaborative and immersive experiences without thinking about networking code. | +| **Offline** | Firebase apps remain responsive even when offline because the Firebase Realtime Database SDK persists your data to disk. Once connectivity is reestablished, the client device receives any changes it missed, synchronizing it with the current server state. | +| **Accessible from Client Devices** | The Firebase Realtime Database can be accessed directly from a mobile device or web browser; there’s no need for an application server. Security and data validation are available through the Firebase Realtime Database Security Rules, expression-based rules that are executed when data is read or written. | + +## How does it work? + +The Firebase Realtime Database lets you build rich, collaborative applications by allowing secure access to the database directly from client-side code. Data is persisted locally, and even while offline, realtime events continue to fire, giving the end user a responsive experience. When the device regains connection, the Realtime Database synchronizes the local data changes with the remote updates that occurred while the client was offline, merging any conflicts automatically. + +The Realtime Database provides a flexible, expression-based rules language, called Firebase Realtime Database Security Rules, to define how your data should be structured and when data can be read from or written to. When integrated with Firebase Authentication, developers can define who has access to what data, and how they can access it. + +The Realtime Database is a NoSQL database and as such has different optimizations and functionality compared to a relational database. The Realtime Database API is designed to only allow operations that can be executed quickly. This enables you to build a great realtime experience that can serve millions of users without compromising on responsiveness. Because of this, it is important to think about how users need to access your data and then [structure it accordingly][1]. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/database/) to see original Firebase documentation._ + +[1]: https://firebase.google.com/docs/database/web/structure-data \ No newline at end of file diff --git a/docs/Firebase/Database/GettingStarted.md b/docs/Firebase/Database/GettingStarted.md new file mode 100755 index 00000000..a8a4031d --- /dev/null +++ b/docs/Firebase/Database/GettingStarted.md @@ -0,0 +1,734 @@ +# Firebase Database on iOS + +The Firebase Database is a cloud-hosted database. Data is stored as JSON and synchronized in realtime to every connected client. When you build cross-platform apps with our Android, iOS, and JavaScript SDKs, all of your clients share one Realtime Database instance and automatically receive updates with the newest data. + +## Table of content + +- [Add Firebase to your app](#add-firebase-to-your-app) +- [Configure Database in your app](#configure-database-in-your-app) +- [Structure Your Database](#structure-your-database) +- [Recommended documentation to get a better understanding of the Security & Rules of Firebase Database](#recommended-documentation-to-get-a-better-understanding-of-the-security-rules-of-firebase-database) +- [Configure Firebase Database Rules](#configure-firebase-database-rules) +- [Read and Write Data on iOS](#read-and-write-data-on-ios) + - [Basic write operations](#basic-write-operations) + - [Listen for value events](#listen-for-value-events) + - [Read data once](#read-data-once) + - [Update specific fields](#update-specific-fields) + - [Delete data](#delete-data) +- [Detach listeners](#detach-listeners) +- [Save data as transactions](#save-data-as-transactions) +- [Write data offline](#write-data-offline) +- [Work with Lists of Data on iOS](#work-with-lists-of-data-on-ios) + - [Append to a list of data](#append-to-a-list-of-data) + - [Listen for child events](#listen-for-child-events) + - [Listen for value events](#listen-for-value-events) +- [Sorting and filtering data](#sorting-and-filtering-data) + - [Sort data](#sort-data) + - [Filtering data](#filtering-data) + - [Limit the number of results](#limit-the-number-of-results) + - [Filter by key or value](#filter-by-key-or-value) +- [How query data is ordered](#how-query-data-is-ordered) + - [GetQueryOrderedByKey method](#getqueryorderedbykey-method) + - [GetQueryOrderedByValue method](#getqueryorderedbyvalue-method) + - [GetQueryOrderedByChild method](#getqueryorderedbychild-method) +- [Offline Capabilities on iOS](#offline-capabilities-on-ios) + - [Disk Persistence](#disk-persistence) + - [Persistence Behavior](#persistence-behavior) + - [Keeping Data Fresh](#keeping-data-fresh) + - [Querying Data Offline](#querying-data-offline) + - [Handling Transactions Offline](#handling-transactions-offline) +- [Managing Presence](#managing-presence) + - [How OnDisconnect methods works](#how-ondisconnect-methods-works) +- [Detecting Connection State](#detecting-connection-state) +- [Handling Latency](#handling-latency) + - [Server Timestamps](#server-timestamps) + - [Clock Skew](#clock-skew) +- [Automated Backups](#automated-backups) +- [Best practices](#best-practices) + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][1], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][2]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][2] again at any time. + +## Configure Database in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Xamarin Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Add the following line of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` namespace): + +```csharp +App.Configure (); +``` + +## Structure Your Database + +Before you continue, please, read this [documentation][10] to know how Database is structured and best practices for data structure. + +## Recommended documentation to get a better understanding of the Security & Rules of Firebase Database + +Before you continue, I invite you to read these following docs to make your coding easier: + +* [Understand Rules][13] +* [Get Started][14] +* [Secure Data][15] +* [Secure User Data][16] +* [Index Data][17] +* [Manage Rules via REST][18] + +## Configure Firebase Database Rules + +The Realtime Database provides a declarative rules language that allows you to define how your data should be structured, how it should be indexed, and when your data can be read from and written to. By default, read and write access to your database is restricted so only authenticated users can read or write data. To get started without setting up [Authentication][3], you can [configure your rules for public access][4]. This does make your database open to anyone, even people not using your app, so be sure to restrict your database again when you set up authentication. + +## Read and Write Data on iOS + +This document covers the basics of reading and writing Firebase data. + +Firebase data is written to a `Database` reference and retrieved by attaching an asynchronous listener to the reference. The listener is triggered once for the initial state of the data and again anytime the data changes. + +To read or write data from the database, you need an instance of `DatabaseReference`: + +```csharp +DatabaseReference rootNode = Database.DefaultInstance.GetRootReference (); +``` + +Doing this, you will have a variable called `rootNode` that points to **https://yourFirebaseDatabase/** + +### Basic write operations + +For basic write operations, you can use `SetValue (T value)`, `SetValues (T [] values)` or `SetValues (NSObject [] values)` to save data to a specified reference, replacing any existing data at that path. You can use this method to pass types that correspond to the available JSON types as follows: + +* NSString +* NSNumber +* NSDictionary +* NSArray + +> ![warning_icon] ***Note:*** *If you pass an object different from these 4 types, an exception will be thrown. Types inherited from these 4 types will be accepted.* + +For instance, you can add a user with `SetValue (T value)`. First create a reference that points to the user reference: + +```csharp +DatabaseReference userNode = rootNode.GetChild ("users").GetChild (user.Uid); +``` + +Doing this, now you have a variable that points to **https://yourFirebaseDatabase/users/$userUid** where **$userUid** is the key of the reference. The reference is created even if it doesn't exist but only will be saved into Firebase Database until you assign some value to the reference: + +```csharp +object [] keys = { "username" }; +object [] values = { username }; +var data = NSDictionary.FromObjectsAndKeys (values, keys, keys.Length); + +userNode.SetValue (data); +``` + +You can avoid creating variables for each reference and assign the value directly: + +```csharp +rootNode.GetChild ("users").GetChild (user.Uid).SetValue (data); +``` + +Using `SetValue` in these ways overwrites data at the specified location, including any child nodes. However, you can still update a child without rewriting the entire object. If you want to allow users to update their profiles you could update the username as follows: + +```csharp +rootNode.GetChild ("users").GetChild (user.Uid).GetChild ("username").SetValue (username); +``` + +### Listen for value events + +To read data at a path and listen for changes, use the `ObserveEvent` or `ObserveSingleEvent` methods of `DatabaseReference` to observe `DataEventType.Value` events. + +| Event type | Typical usage | +|:-----------------------:|---------------------------------------------------------------| +| **DataEventType.Value** | Read and listen for changes to the entire contents of a path. | + +You can use the `DataEventType.Value` event to read the data at a given path, as it exists at the time of the event. This method is triggered once when the listener is attached and again every time the data, including any children, changes. The event callback is passed a `snapshot` containing all data at that location, including child data. If there is no data, the `value` of the `snapshot` returned is `null`. + +> ![note_icon] ***Important:*** *The `DataEventType.Value` event is fired every time data is changed at the specified database reference, including changes to children. To limit the size of your snapshots, attach only at the highest level needed for watching changes. For example, attaching a listener to the root of your database is not recommended.* + +The following example demonstrates a notes application retrieving the user's folders from the database: + +```csharp +DatabaseReference foldersNode = rootNode.GetChild ("folders").GetChild (user.Uid); +nuint handleReference = foldersNode.ObserveEvent (DataEventType.Value, (snapshot) => { + var folderData = snapshot.GetValue (); + // Do magic with the folder data +}); +``` + +The listener receives a `DataSnapshot` that contains the data at the specified location in the database at the time of the event in its `GetValue`, `GetValue`, `GetValues` or `GetValues` methods. You can assign the values to the appropriate native type, such as `NSDictionary`. If no data exists at the location, the value is `null`. + +### Read data once + +In some cases you may want a callback to be called once and then immediately removed, such as when initializing a UI element that you don't expect to change. You can use the `ObserveSingleEvent` method to simplify this scenario: the event callback added triggers once and then does not trigger again. + +This is useful for data that only needs to be loaded once and isn't expected to change frequently or require active listening. For instance, we can use this method to know the total of notes that a folder has: + +```csharp +nuint notesCount; +DatabaseReference notesCountNode = rootNode.GetChild ("folders").GetChild (user.Uid).GetChild (folderUid).GetChild ("notesCount"); +notesCountNode.ObserveSingleEvent (DataEventType.Value, (snapshot) => { + notesCount = snapshot.GetValue ().NUIntValue; +}, (error) => { + Console.WriteLine (error.LocalizedDescription); +}); +``` + +### Update specific fields + +To simultaneously write to specific children of a node without overwriting other child nodes, use the `UpdateChildValues` method. + +When calling updateChildValues, you can update lower-level child values by specifying a path for the key. If data is stored in multiple locations to scale better, you can update all instances of that data using [data fan-out][5]. For example, in notes app, the user wants to save a new note and simultaneously update the total of notes that the folder has. To do this, the code could be like this: + +```csharp +var noteUid = rootNode.GetChild ("notes").GetChild (user.Uid).GetChild (folderUid).GetChildByAutoId ().Key; + +object [] noteKeys = { "content", "created", "lastModified", "title" }; +object [] noteValues = { content, created, lastModified, title }; +var noteData = NSDictionary.FromObjectsAndKeys (noteValues, noteKeys, noteKeys.Length); + +object [] nodes = { $"notes/{user.Uid}/{folderUid}/{noteUid}", $"folders/{user.Uid}/{folderUid}/notesCount" }; +object [] nodesValues = { noteData, NSNumber.FromNUInt (++notesCount) }; +var childUpdates = NSDictionary.FromObjectsAndKeys (nodesValues, nodes, nodes.Length); + +rootNode.UpdateChildValues (childUpdates); +``` + +Using these paths, you can perform simultaneous updates to multiple locations in the JSON tree with a single call to `UpdateChildValues` method. Simultaneous updates made this way are atomic: either all updates succeed or all updates fail. + +### Delete data + +The simplest way to delete data is to call `RemoveValue` method on a reference to the location of that data. + +You can also delete by specifying `null` as the value for another write operation such as `SetValue` or `UpdateChildValues`. You can use this technique with `UpdateChildValues` to delete multiple children in a single API call: + +```csharp +DatabaseReference noteNode = rootNode.GetChild ("notes").GetChild (user.Uid).GetChild (folderUid).GetChild (noteUid); + +// Same functionality +noteNode.RemoveValue (); +noteNode.SetValue (null); + +object [] nodes = { $"notes/{user.Uid}/{folderUid}/{noteUid}" }; +object [] nodesValues = { null }; +var childUpdates = NSDictionary.FromObjectsAndKeys (nodesValues, nodes, nodes.Length); +rootNode.UpdateChildValues (childUpdates); +``` + +## Detach listeners + +Observers don't automatically stop syncing data when you leave a ViewController. If an observer isn't properly removed, it continues to sync data to local memory. When an observer is no longer needed, remove it by passing the associated **DatabaseHandle** to the `RemoveObserver` method: + +```csharp +DatabaseReference foldersNode = rootNode.GetChild ("folders").GetChild (user.Uid); +nuint handleReference = foldersNode.ObserveEvent (DataEventType.Value, (snapshot) => { + var folderData = snapshot.GetValue (); + // Do magic with the folder data +}); + +// Some other code + +// When you don't want to keep listening for changes in that node, do the following at some point of your app +foldersNode.RemoveObserver (handleReference); +``` + +When you add a callback block to a reference, a `nuint` is returned. These handles can be used to remove the callback block. + +If multiple listeners have been added to a database reference, each listener is called when an event is raised. In order to stop syncing data at that location, you must remove all observers at a location by calling the `RemoveAllObservers` method. + +Calling `RemoveObserver` or `RemoveAllObservers` on a listener does not automatically remove listeners registered on its child nodes; you must also be keep track of those references or handles to remove them. + +## Save data as transactions + +When working with data that could be corrupted by concurrent modifications, such as incremental counters, you can use a [transaction operation][6]. You give this operation two arguments: an update function and an optional completion callback. The update function takes the current state of the data as an argument and returns the new desired state you would like to write. + +For instance, imagine that you have a collaborative notes app, and some users are working in the same note, you could allow users to update the note as follows: + +```csharp +noteNode.RunTransaction ((currentData) => { + var noteData = currentData.GetValue (); + + if (noteData == null) + return TransactionResult.Success (currentData); + + var mutableNoteData = noteData.MutableCopy () as NSMutableDictionary; + mutableNoteData ["content"] = content; + mutableNoteData ["title"] = title; + + currentData.SetValue (mutableNoteData); + + return TransactionResult.Success (currentData); +}, (error, commited, snapshot) => { + if (error != null) { + Console.WriteLine (error.LocalizedDescription); + } +}); +``` + +Using a transaction prevents note content from being incorrect if multiple users edit the same note at the same time or the client had stale data. The value contained in the `MutableData` class is initially the client's last known value for the path, or `null` if there is none. The server compares the initial value against it's current value and accepts the transaction if the values match, or rejects it. If the transaction is rejected, the server returns the current value to the client, which runs the transaction again with the updated value. This repeats until the transaction is accepted or too many attempts have been made. + +> ![note_icon] ***Note:*** *Because `RunTransaction` method is called multiple times, it must be able to handle `null` data. Even if there is existing data in your remote database, it may not be locally cached when the transaction function is run, resulting in `null` for the initial value.* + +## Write data offline + +If a client loses its network connection, your app will continue functioning correctly. + +Every client connected to a Firebase database maintains its own internal version of any active data. When data is written, it's written to this local version first. The Firebase client then synchronizes that data with the remote database servers and with other clients on a "best-effort" basis. + +As a result, all writes to the database trigger local events immediately, before any data is written to the server. This means your app remains responsive regardless of network latency or connectivity. + +Once connectivity is reestablished, your app receives the appropriate set of events so that the client syncs with the current server state, without having to write any custom code. + +## Work with Lists of Data on iOS + +### Append to a list of data + +Use the `GetChildByAutoId` method to append data to a list in multiuser applications. The `GetChildByAutoId` method generates a unique key every time a new child is added to the specified Firebase reference. By using these auto-generated keys for each new element in the list, several clients can add children to the same location at the same time without write conflicts. The unique key generated by `GetChildByAutoId` is based on a timestamp, so list items are automatically ordered chronologically. + +You can use the reference to the new data returned by the `GetChildByAutoId` method to get the value of the child's auto-generated key or set data for the child. Calling `Key` property on a `GetChildByAutoId` reference returns the auto-generated key. + +You can use these auto-generated keys to simplify flattening your data structure. For more information, see the data fan-out [example][5]. + +### Listen for child events + +Child events are triggered in response to specific operations that happen to the children of a node from an operation such as a new child added through the `GetChildByAutoId` method or a child being updated through the `UpdateChildValues` method. + +| Event type | Typical usage | +|:------------------------------:|---------------| +| **DataEventType.ChildAdded** | Retrieve lists of items or listen for additions to a list of items. This event is triggered once for each existing child and then again every time a new child is added to the specified path. The listener is passed a snapshot containing the new child's data. Also, It is used with data that is ordered by `GetQueryOrderedByChild` or `GetQueryOrderedByValue` methods. | +| **DataEventType.ChildChanged** | Listen for changes to the items in a list. This event is triggered any time a child node is modified. This includes any modifications to descendants of the child node. The snapshot passed to the event listener contains the updated data for the child. | +| **DataEventType.ChildRemoved** | Listen for items being removed from a list. This event is triggered when an immediate child is removed.The snapshot passed to the callback block contains the data for the removed child. | +| **DataEventType.ChildMoved** | Listen for changes to the order of items in an ordered list. This event is triggered whenever an update causes reordering of the child. | + +Each of these together can be useful for listening to changes to a specific node in a database. For example, the notes app could use these methods together to monitor activity when a folder is created or deleted, as shown below: + +```csharp +// Listen for new folders in the Firebase database +foldersNode.ObserveEvent (DataEventType.ChildAdded, (snapshot, prevKey) => { + var data = snapshot.GetValue (); + var created = data ["created"].ToString (); + var lastModified = data ["lastModified"].ToString (); + var name = data ["name"].ToString (); + var notesCount = (data ["notesCount"] as NSNumber).UInt32Value; + + var folder = new Folder { + Name = name, + Created = created, + LastModified = AppDelegate.ConvertUnformattedUtcDateToCurrentDate (lastModified), + NotesCount = notesCount + }; + + folders.Add (folder); + + TableView.ReloadData (); +}); + +// Listen for deleted comments in the Firebase database +foldersNode.ObserveEvent (DataEventType.ChildRemoved, (snapshot, prevKey) => { + var data = snapshot.GetValue (); + var created = data ["created"].ToString (); + var lastModified = data ["lastModified"].ToString (); + var name = data ["name"].ToString (); + var notesCount = (data ["notesCount"] as NSNumber).UInt32Value; + + var folder = new Folder { + Name = name, + Created = created, + LastModified = AppDelegate.ConvertUnformattedUtcDateToCurrentDate (lastModified), + NotesCount = notesCount + }; + + folders.Remove (folder); + + TableView.ReloadData (); +}); +``` + +### Listen for value events + +While listening for child events is the recommended way to read lists of data, there are situations listening for value events on a list reference is useful. + +Attaching a `DataEventType.Value` observer to a list of data will return the entire list of data as a single DataSnapshot, which you can then loop over to access individual children. + +Even when there is only a single match for the query, the snapshot is still a list; it just contains a single item. To access the item, you need to loop over the result: + +```csharp +foldersNode.ObserveEvent (DataEventType.Value, (snapshot) => { + // Loop over the children + NSEnumerator children = snapshot.Children; + var child = children.NextObject () as DataSnapshot; + + while (child != null) { + // Work with data... + + child = children.NextObject () as DataSnapshot; + } +}); +``` + +This pattern can be useful when you want to fetch all children of a list in a single operation, rather than listening for additional child added events. + +## Sorting and filtering data + +You can use the Realtime Database `DatabaseQuery` class to retrieve data sorted by key, by value, or by the value of a child. You can also filter the sorted result to a specific number of results or a range of keys or values. + +> ![note_icon] ***Note:*** *Filtering and sorting can be expensive, especially when done on the client. If your app uses queries, define the .indexOn rule to index those keys on the server and improve query performance as described in [Indexing Your Data][7].* + +### Sort data + +To retrieve sorted data, start by specifying one of the order-by methods to determine how results are ordered: + +| Method | Usage | +|:--------------------------:|------------------------------------------------------| +| **GetQueryOrderedByKey** | Order results by child keys. | +| **GetQueryOrderedByValue** | Order results by child values. | +| **GetQueryOrderedByChild** | Order results by the value of a specified child key. | + +You can only use **one** order-by method at a time. Calling an order-by method multiple times in the same query **throws an error**. + +The following example demonstrates how you could retrieve a list of a notes sorted by their last time modified: + +```csharp +DatabaseReference notesNode = rootNode.GetChild ("notes").GetChild (user.Uid).GetChild (folderUid); + +// Get all notes sorted by their last time modified in ascending order +DatabaseQuery notesByDate = notesNode.GetQueryOrderedByChild ("lastModified"); +notesByDate.ObserveEvent (DataEventType.ChildAdded, (snapshot) => { + var data = snapshot.GetValue (); + var content = data ["content"].ToString (); + var created = data ["created"].ToString (); + var lastModified = data ["lastModified"].ToString (); + var title = data ["title"].ToString (); + + var note = new Note { + Content = content, + Created = created, + LastModified = lastModified, + Title = title + } + + notes.Add (note); + + TableView.ReloadData (); +}); +``` + +This query retrieves the user's notes from the path in the database based on their user Id and the folder Id, ordered by the last time modified. This technique of using IDs as index keys is called data fan out, you can read more about it in [Structure Your Database][5]. + +The call to the `GetQueryOrderedByChild` method specifies the child key to order the results by. In this case, notes are sorted by the value of the **lastModified** child in each post. For more information on how other data types are ordered, see [How query data is ordered][8]. + +### Filtering data + +To filter data, you can combine any of the limit or range methods with an order-by method when constructing a query. + +| Method | Usage | +|:---------------------------:|------------------------------------------------------------------------------------------------------------| +| **GetQueryLimitedToFirst** | Sets the maximum number of items to return from the beginning of the ordered list of results. | +| **GetQueryLimitedToLast** | Sets the maximum number of items to return from the end of the ordered list of results. | +| **GetQueryStartingAtValue** | Return items greater than or equal to the specified key or value, depending on the order-by method chosen. | +| **GetQueryEndingAtValue** | Return items less than or equal to the specified key or value, depending on the order-by method chosen. | +| **GetQueryEqualToValue** | Return items equal to the specified key or value, depending on the order-by method chosen. | + +Unlike the order-by methods, you can combine multiple limit or range functions. For example, you can combine the `GetQueryStartingAtValue` and `GetQueryEndingAtValue` methods to limit the results to a specified range of values. + +### Limit the number of results + +You can use the `GetQueryLimitedToFirst` and `GetQueryLimitedToLast` methods to set a maximum number of children to be synced for a given callback. For example, if you use `GetQueryLimitedToFirst` to set a limit of 100, you initially only receive up to 100 `DataEventType.ChildAdded` callbacks. If you have fewer than 100 items stored in your Firebase database, an `DataEventType.ChildAdded` callback fires for each item. + +As items change, you receive `DataEventType.ChildAdded` callbacks for items that enter the query and `DataEventType.ChildRemoved` callbacks for items that drop out of it so that the total number stays at 100. + +The following example demonstrates how example notes app retrieves a list of the 100 most recent modified notes by user: + +```csharp +DatabaseReference notesNode = rootNode.GetChild ("notes").GetChild (user.Uid).GetChild (folderUid); + +// First 100 notes sorted by their last time modified in ascending order +DatabaseQuery notesByDate = notesNode.GetQueryOrderedByChild ("lastModified").GetQueryLimitedToFirst (100); +notesByDate.ObserveEvent (DataEventType.ChildAdded, (snapshot) => { + var data = snapshot.GetValue (); + var content = data ["content"].ToString (); + var created = data ["created"].ToString (); + var lastModified = data ["lastModified"].ToString (); + var title = data ["title"].ToString (); + + var note = new Note { + Content = content, + Created = created, + LastModified = lastModified, + Title = title + } + + notes.Add (note); + + TableView.ReloadData (); +}); +``` + +### Filter by key or value + +You can use `GetQueryStartingAtValue`, `GetQueryEndingAtValue`, and `GetQueryEqualToValue` to choose arbitrary starting, ending, and equivalence points for queries. This can be useful for paginating data or finding items with children that have a specific value. + +## How query data is ordered + +This section explains how data is sorted by each of the order-by methods in the `DatabaseQuery` class. + +### GetQueryOrderedByKey method + +When using `GetQueryOrderedByKey` to sort your data, data is returned in ascending order by key. + +1. Children with a key that can be parsed as a 32-bit integer come first, sorted in ascending order. +2. Children with a string value as their key come next, sorted [lexicographically][9] in ascending order. + +### GetQueryOrderedByValue method + +When using `GetQueryOrderedByValue`, children are ordered by their value. The ordering criteria are the same as in `GetQueryOrderedByChild`, except the value of the node is used instead of the value of a specified child key. + +### GetQueryOrderedByChild method + +When using `GetQueryOrderedByChild`, data that contains the specified child key is ordered as follows: + +1. Children with a `null` value for the specified child key come first. +2. Children with a value of `false` for the specified child key come next. If multiple children have a value of false, they are sorted [lexicographically][9] by key. +3. Children with a value of `true` for the specified child key come next. If multiple children have a value of true, they are sorted lexicographically by key. +4. Children with a numeric value come next, sorted in ascending order. If multiple children have the same numerical value for the specified child node, they are sorted by key. +5. Strings come after numbers and are sorted lexicographically in ascending order. If multiple children have the same value for the specified child node, they are ordered lexicographically by key. +6. Objects come last and are sorted lexicographically by key in ascending order. + +## Offline Capabilities on iOS + +Firebase apps work great offline and we have several features to make the experience even better. Enabling disk persistence allows your app to keep all of its state even after an app restart. We provide several tools for monitoring presence and connectivity state. + +### Disk Persistence + +Firebase apps automatically handle temporary network interruptions for you. Cached data will still be available while offline and your writes will be resent when network connectivity is recovered. Enabling disk persistence allows our app to also keep all of its state even after an app restart. We can enable disk persistence with just one line of code: + +```csharp +Database.DefaultInstance.PersistenceEnabled = true; +``` + +With disk persistence enabled, our synced data and writes will be persisted to disk across app restarts and our app should work seamlessly in offline situations. + +### Persistence Behavior + +By enabling persistence, any data that we sync while online will be persisted to disk and available offline, even when we restart the app. This means our app will work as it would online using the local data stored in the cache. Listener callbacks will continue to fire for local updates. + +The Firebase Database client automatically keeps a queue of all write operations that are performed while our application is offline. When persistence is enabled, this queue will also be persisted to disk so all of our writes will be available when we restart the app. When the app regains connectivity, all of the operations will be sent to the server. + +If our app uses [Firebase Authentication][3], the client will persist the user's authentication token across restarts. If the auth token expires while our app is offline, the client will pause our write operations until we re-authenticate, else our writes might fail due to security rules. + +### Keeping Data Fresh + +The Firebase Database synchronizes and stores a local copy of the data for active listeners. In addition, you can keep specific locations in sync: + +```csharp +DatabaseReference foldersNode = rootNode.GetChild ("folders").GetChild (AppDelegate.UserUid); +foldersNode.KeepSynced (true); +``` + +The client will automatically download the data at these locations and keep it in sync even if the reference has no active listeners. You can turn synchronization back off with the following line of code: + +```csharp +foldersNode.KeepSynced (false); +``` + +By default, 10MB of previously synced data will be cached. This should be enough for most applications. If the cache outgrows its configured size, the Firebase Database will purge data that has been used least recently. Data that is kept in sync, will not be purged from the cache. + +### Querying Data Offline + +The Firebase Database stores data returned from a query for use when offline. For queries constructed while offline, the Firebase Database continues to work for previously loaded data. If the requested data hasn't loaded, the Firebase Database loads data from the local cache. When we come back online our data will load and reflect the query. + +For example, here we have a piece of code that queries for the last four items in our Firebase Database of notes: + +```csharp +DatabaseReference notesNode = rootNode.GetChild ("notes").GetChild (user.Uid).GetChild (folderUid); + +// Last 4 notes sorted by their last time modified +DatabaseQuery notesByDate = notesNode.GetQueryOrderedByChild ("lastModified").GetQueryLimitedToLast (4); +notesByDate.ObserveEvent (DataEventType.ChildAdded, (snapshot) => { + var data = snapshot.GetValue (); + var content = data ["content"].ToString (); + var created = data ["created"].ToString (); + var lastModified = data ["lastModified"].ToString (); + var title = data ["title"].ToString (); + + var note = new Note { + Content = content, + Created = created, + LastModified = lastModified, + Title = title + } + + notes.Add (note); + + TableView.ReloadData (); +}); +``` + +Now let's assume that the user loses connection, goes offline, and restarts the app. While still offline, we query for the last two items from the same location. This query will successfully return the last two items because we had loaded all four in the query above: + +```csharp +DatabaseReference notesNode = rootNode.GetChild ("notes").GetChild (user.Uid).GetChild (folderUid); + +// Last 2 notes sorted by their last time modified +DatabaseQuery notesByDate = notesNode.GetQueryOrderedByChild ("lastModified").GetQueryLimitedToLast (2); +notesByDate.ObserveEvent (DataEventType.ChildAdded, (snapshot) => { + var data = snapshot.GetValue (); + var content = data ["content"].ToString (); + var created = data ["created"].ToString (); + var lastModified = data ["lastModified"].ToString (); + var title = data ["title"].ToString (); + + var note = new Note { + Content = content, + Created = created, + LastModified = lastModified, + Title = title + } + + notes.Add (note); + + TableView.ReloadData (); +}); +``` + +In the above example, the Firebase Database client raises `DataEventType.ChildAdded` events for the latest modified notes, via our persisted cache. But it will not raise a `DataEventType.Value` event, since we've never done that query while online. + +If we were to request the last six items while offline, we'd get `DataEventType.ChildAdded` events for the four cached items straight away. When we come back online, the Firebase Realtime Database client will synchronize with the server and we'll get the final two `DataEventType.ChildAdded` and the `DataEventType.Value` events. + +### Handling Transactions Offline + +Any transactions that are performed while our app is offline, will be queued. Once the app regains network connectivity, the transactions will be sent to the server. + +It's important to know that **Transactions are not persisted across app restarts**. Even with persistence enabled, transactions are not persisted across app restarts. So you cannot rely on transactions done offline being committed to your Firebase Database. To provide the best user experience, your app should show that a transaction has not been saved into your Firebase Database yet, or make sure your app remembers them manually and executes them again after an app restart. + +## Managing Presence + +In realtime applications it is often useful to detect when clients connect and disconnect. For example, we may want to mark a user as 'offline' when their client disconnects. + +Firebase Database clients provide simple primitives that allow data to be written to the database when a client disconnects from the Firebase Database servers. These updates will occur whether the client disconnects cleanly or not, so we can rely on them to clean up data even if a connection is dropped or a client crashes. All write operations, including setting, updating, and removing, can be performed upon a disconnection. + +Here is a simple example of writing data upon disconnection by using the **OnDisconnect** primitive: + +```csharp +DatabaseReference disconnectedNode = rootNode.GetChild ("disconnected"); +// Write a string when this client loses connection +disconnectedNode.SetValueOnDisconnect (new NSString ("I disconnected!")); +``` + +### How OnDisconnect methods works + +When an **OnDisconnect** operation is established, it lives on the Firebase Database server. The server checks security to make sure the user can perform the write event requested, and informs the client if it is invalid. The server then monitors the connection. If at any point it times out, or is actively closed by the client, the server checks security a second time (to make sure the operation is still valid) and then invokes the event. + +The client can use the callback on the write operation to ensure the **OnDisconnect** was correctly attached: + +```csharp +rootNode.RemoveValueOnDisconnect ((error, reference) => { + if (error != null) { + Console.WriteLine ($"Could not establish onDisconnect event: {error.LocalizedDescription}"); + } +}); +``` + +An **OnDisconnect** event can also be canceled by calling `CancelDisconnectOperations` method: + +```csharp +rootNode.CancelDisconnectOperations (); +``` + +## Detecting Connection State + +For many presence-related features, it is useful for a client to know when it is online or offline. Firebase Database clients provide a special location at **/.info/connected** which is updated every time the client's connection state changes. Here is an example: + +```csharp +DatabaseReference connectedNode = Database.DefaultInstance.GetReferenceFromPath (".info/connected"); +connectedNode.ObserveEvent (DataEventType.Value, (snapshot) => { + var connected = snapshot.GetValue ().BoolValue; + + if (connected) + Console.WriteLine ("Connected"); + else + Console.WriteLine ("Not connected"); +}); +``` + +**/.info/connected** is a boolean value which is not synchronized between clients because the value is dependent on the state of the client. In other words, if one client reads **/.info/connected** as `false`, this is no guarantee that a separate client will also read `false`. + +## Handling Latency + +### Server Timestamps + +The Firebase Database servers provide a mechanism to insert timestamps generated on the server as data. This feature, combined with **OnDisconnect**, provides an easy way to reliably make note of the time at which a client disconnected: + +```csharp +DatabaseReference userLastOnlineNode = Database.DefaultInstance.GetReferenceFromPath ($"users/{user.Uid}/lastOnline"); +userLastOnlineNode.SetValueOnDisconnect (ServerValue.Timestamp); +``` + +### Clock Skew + +While `ServerValue.Timestamp` is much more accurate, and preferable for most read/write ops, it can occasionally be useful to estimate the clients clock skew with respect to the Firebase Database's servers. We can attach a callback to the location **/.info/serverTimeOffset** to obtain the value, in milliseconds, that Firebase Database clients will add to the local reported time (epoch time in milliseconds) to estimate the server time. Note that this offset's accuracy can be affected by networking latency, and so is useful primarily for discovering large (> 1 second) discrepancies in clock time: + +```csharp +DatabaseReference offsetNode = Database.DefaultInstance.GetReferenceFromPath (".info/serverTimeOffset"); +offsetNode.ObserveSingleEvent (DataEventType.Value, (snapshot) => { + var offset = snapshot.GetValue ().DoubleValue; + + var ticksFrom1970 = new System.DateTime (1970, 1, 1, 0, 0, 0, 0).Ticks; + var ticksUtc = System.DateTime.UtcNow.Ticks; + var epoch = (ticksUtc - ticksFrom1970) / System.TimeSpan.TicksPerMillisecond; + + var estimatedServerTimeMs = epoch + offset; +}); +``` + +## Automated Backups + +Please, read this [Firebase documentation][19] to learn more about Automated Backups. + +## Best practices + +To learn about Firebase Database best practices, please visit this [Firebase post][11]. + +## Automated Backups + +[Blaze][20] plan users can set up their Firebase Realtime Database for automatic backups, a self-service feature that enables daily backups of your Database application data and [rules][13] in JSON format to a [Google Cloud Storage][21] bucket. + +To learn more about this, please, read the following [documentation][22]. + +## Extend Realtime Database with Cloud Functions + +With Cloud Functions, you can handle events in the Firebase Realtime Database with no need to update client code. Cloud Functions lets you run database operations with full administrative privileges, and ensures that each change to the database is processed individually. You can make Firebase Database changes via the [DeltaSnapshot][23] or via the [Admin SDK][24]. + +To learn more about this, please, read the following [documentation][25]. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/database/ios/start) to see original Firebase documentation._ + +[1]: https://firebase.google.com/console/ +[2]: http://support.google.com/firebase/answer/7015592 +[3]: https://components.xamarin.com/view/firebaseiosanalytics +[4]: https://firebase.google.com/docs/database/security/quickstart#sample-rules +[5]: https://firebase.google.com/docs/database/ios/structure-data#fanout +[6]: https://firebase.google.com/docs/reference/ios/firebasedatabase/interface_f_i_r_database_reference.html#a796bff455159479a44b225eeaa2ba9d6 +[7]: https://firebase.google.com/docs/database/security/indexing-data +[8]: https://firebase.google.com/docs/database/ios/lists-of-data#data_order +[9]: http://en.wikipedia.org/wiki/Lexicographical_order +[10]: https://firebase.google.com/docs/database/ios/structure-data +[11]: https://firebase.googleblog.com/2015/10/best-practices-for-ios-uiviewcontroller_6.html +[13]: https://firebase.google.com/docs/database/security/ +[14]: https://firebase.google.com/docs/database/security/quickstart +[15]: https://firebase.google.com/docs/database/security/securing-data +[16]: https://firebase.google.com/docs/database/security/user-security +[17]: https://firebase.google.com/docs/database/security/indexing-data +[18]: https://firebase.google.com/docs/database/rest/app-management +[19]: https://firebase.google.com/docs/database/ios/backups +[20]: https://firebase.google.com/pricing/ +[21]: https://cloud.google.com/storage/docs/ +[22]: https://firebase.google.com/docs/database/backups +[23]: https://firebase.google.com/docs/reference/functions/functions.database.DeltaSnapshot +[24]: https://firebase.google.com/docs/database/admin/start +[25]: https://firebase.google.com/docs/database/extend-with-functions +[note_icon]: https://cdn3.iconfinder.com/data/icons/UltimateGnome/22x22/apps/gnome-app-install-star.png +[warning_icon]: https://cdn2.iconfinder.com/data/icons/freecns-cumulus/32/519791-101_Warning-20.png diff --git a/docs/Firebase/DynamicLinks/Details.md b/docs/Firebase/DynamicLinks/Details.md new file mode 100755 index 00000000..b2a04c0b --- /dev/null +++ b/docs/Firebase/DynamicLinks/Details.md @@ -0,0 +1,20 @@ +Firebase Dynamic Links are links that work the way you want, on multiple platforms, and whether or not your app is already installed. + +With Dynamic Links, your users get the best available experience for the platform they open your link on. If a user opens a Dynamic Link on iOS or Android, they can be taken directly to the linked content in your native app. If a user opens the same Dynamic Link in a desktop browser, they can be taken to the equivalent content on your website. + +In addition, Dynamic Links work across app installs: if a user opens a Dynamic Link on iOS or Android and doesn't have your app installed, the user can be prompted to install it; then, after installation, your app starts and can access the link. + +## How does it work? + +You create a Dynamic Link either by using the Firebase console, using a REST API, iOS or Android Builder API, or by forming a URL by adding Dynamic Link parameters to a domain specific to your app. These parameters specify the links you want to open, depending on the user's platform and whether your app is installed. + +When a user opens one of your Dynamic Links, if your app isn't yet installed, the user is sent to the Play Store or App Store to install your app (unless you specify otherwise), and your app opens. You can then retrieve the link that was passed to your app and handle the link as appropriate for your app. + +| | | +|--|--| +| Set up Firebase and the Dynamic Links SDK | Enable Firebase Dynamic Links for your Firebase project in the Firebase console. Then, include the Dynamic Links SDK in your app. | +| Create Dynamic Links | You can create Dynamic Links programmatically or by using the Firebase console. | +| Handle Dynamic Links in your app | When your app opens, use the Dynamic Links SDK to check if a Dynamic Link was passed to it. If so, get the deep link from the Dynamic Link data and handle the deep link as necessary. | +| View analytics data | Track the performance of your Dynamic Links in the Firebase console. | + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/dynamic-links/) to see original Firebase documentation._ \ No newline at end of file diff --git a/docs/Firebase/DynamicLinks/GettingStarted.md b/docs/Firebase/DynamicLinks/GettingStarted.md new file mode 100755 index 00000000..1318e4a8 --- /dev/null +++ b/docs/Firebase/DynamicLinks/GettingStarted.md @@ -0,0 +1,281 @@ +# Firebase Dynamic Links on iOS + +With Firebase Dynamic Links, you can give new users of your app a personalized onboarding experience, and thus increase user sign-ups, user retention, and long-term user engagement. + +Dynamic Links are links into an app that work whether or not users have installed the app yet. When users open a Dynamic Link into an app that is not installed, the app's App Store page opens, where users can install the app. After users install and open the app, the app handles the link. + +An app might handle the link by opening the app's content using custom URL schemes on iOS 8, or handling Universal Links on iOS 9 and newer. Or, an app could handle the link by initiating some app-specific logic such as crediting the user with a coupon, or displaying a specific welcome screen. + +## Table of content + +- [Prerequisites](#prerequisites) +- [Add Firebase to your app](#add-firebase-to-your-app) +- [Configure Dynamic Links in your app](#configure-dynamic-links-in-your-app) +- [Use the iOS Builder API](#use-the-ios-builder-api) + - [Create a long link from parameters](#create-a-long-link-from-parameters) + - [Set the length of a short Dynamic Link](#set-the-length-of-a-short-dynamic-link) + - [Create a short link from a long link](#create-a-short-link-from-a-long-link) +- [Manually constructing a Dynamic Link URL](#manually-constructing-a-dynamic-link-url) +- [Open Dynamic Links in your app](#open-dynamic-links-in-your-app) +- [Known issues](#known-issues) + +## Prerequisites + +Firebase Dynamic Links requires iOS 8 or newer. You can target iOS 7 in your app, but Firebase Dynamic Links SDK calls only function on apps running iOS 8 or newer. + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][1], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][2]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][2] again at any time. +5. Go to **Project settings**, in **General** tab select your recently created iOS bundle ID. +6. Set your App Store ID, you can find your App Store ID in your app’s URL. (e.g. https://itunes.apple.com/us/app/yourapp/id123456789, **123456789** is your App Store ID). +7. Set your Team ID, you can find your Team ID in the Apple Member Center under the [membership tab][3]. +8. In [Firebase console][1], in your project menu go to Dynamic Links tab, accept the terms of service if you are prompted to do so, copy Url link, paste it into your Navigation bar and add **/apple-app-site-association** (e.g https://app_code.app.goo.gl//apple-app-site-association) +9. Your app is connected if the apple-app-site-association file contains a reference to your app's App Store ID and bundle ID, for example: + +``` +{"applinks":{"apps":[],"details":[{"appID":"1234567890.com.example.ios","paths":["/*"]}]}} +``` + +If the details field is empty, double-check that you specified your Team ID. + +## Configure Dynamic Links in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Xamarin Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. In your `Entitlements.plist` file, enable **Associated Domains**. +4. Add `applinks:app_code.app.goo.gl` to **Domains** where **app_code** is your Dynamic Links identifier. +5. In your `Info.plist` file, go to **Advance** and add an Url Type. +6. Set the **Identifier** field to a unique value and the **URL scheme** field to either your Bundle ID or a unique value. If you set the **URL scheme** to a value other than your Bundle ID, you must specify the Bundle ID when you create your Dynamic Links. +7. Add the following lines of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` namespace): + +```csharp +App.Configure (); +``` + +## Create Dynamic Links + +There are four ways you can create a Dynamic Link: + +* Using the [Firebase console][4]. This is useful if you're creating one-off links to share on social media. +* Using the [Dynamic Link Builder API](#use-the-ios-builder-api). This is the preferred way to dynamically create links in your app for user-to-user sharing or in any situation that requires many links. You can track the performance of Dynamic Links created with the Builder API using the Dynamic Links [Analytics API][5]. +* Using the [REST API][6]. This is the preferred way to dynamically create links on platforms that don't have a Builder API. You can track the performance of Dynamic Links created with the REST API using the Dynamic Links [Analytics API][5]. +* [Manually](#manually-constructing-a-dynamic-link-url). If you don't need to track click data and you don't care if the links are long, you can manually construct Dynamic Links using URL parameters, and by doing so, avoid an extra network round trip. + +## Use the iOS Builder API + +You can create short or long Dynamic Links with the Firebase Dynamic Links iOS Builder API. This API accepts either a long Dynamic Link or an object containing Dynamic Link parameters, and returns a URL like the following example: + +``` +https://abc123.app.goo.gl/WXYZ +``` + +### Create a long link from parameters + +You can create a Dynamic Link programmatically by setting the following parameters and getting the `DynamicLinkComponents.Url` parameter: + +```csharp +var link = NSUrl.FromString (dictionary ["Link"].ToString ()); +var components = DynamicLinkComponents.FromLink (link, Dynamic_Link_Domain); + +var source = dictionary ["Source"].ToString (); +var medium = dictionary ["Medium"].ToString (); +var campaign = dictionary ["Campaign"].ToString (); + +var analyticsParams = DynamicLinkGoogleAnalyticsParameters.FromSource (source, medium, campaign); +analyticsParams.Term = dictionary ["Term"].ToString (); +analyticsParams.Content = dictionary ["Content"].ToString (); +components.AnalyticsParameters = analyticsParams; + +var bundleId = dictionary ["BundleID"].ToString (); + +if (!string.IsNullOrWhiteSpace (bundleId)) { + var iOSParams = DynamicLinkiOSParameters.FromBundleId (bundleId); + iOSParams.FallbackUrl = NSUrl.FromString (dictionary ["FallbackURL"].ToString ()); + iOSParams.MinimumAppVersion = dictionary ["MinimumAppVersion"].ToString (); + iOSParams.CustomScheme = dictionary ["CustomScheme"].ToString (); + iOSParams.IPadBundleId = dictionary ["IPadBundleId"].ToString (); + iOSParams.IPadFallbackUrl = NSUrl.FromString (dictionary ["IPadFallbackUrl"].ToString ()); + iOSParams.AppStoreId = dictionary ["AppStoreId"].ToString (); + components.IOSParameters = iOSParams; + + var appStoreParams = DynamicLinkiTunesConnectAnalyticsParameters.Create (); + appStoreParams.AffiliateToken = dictionary ["AffiliateToken"].ToString (); + appStoreParams.CampaignToken = dictionary ["CampaignToken"].ToString (); + appStoreParams.ProviderToken = dictionary ["ProviderToken"].ToString (); + components.ITunesConnectParameters = appStoreParams; +} + +var packageName = dictionary ["PackageName"].ToString (); + +if (!string.IsNullOrWhiteSpace (packageName)) { + var androidParams = DynamicLinkAndroidParameters.FromPackageName (packageName); + androidParams.FallbackUrl = NSUrl.FromString (dictionary ["FallbackURL"].ToString ()); + androidParams.MinimumVersion = nint.Parse (dictionary ["MinimumAppVersion"].ToString ()); + components.AndroidParameters = androidParams; +} + +var socialParams = DynamicLinkSocialMetaTagParameters.Create (); +socialParams.Title = dictionary ["Title"].ToString (); +socialParams.DescriptionText = dictionary ["DescriptionText"].ToString (); +socialParams.ImageUrl = NSUrl.FromString (dictionary ["ImageUrl"].ToString ()); +components.SocialMetaTagParameters = socialParams; + +var longLink = components.Url; +Console.WriteLine (longLink.AbsoluteString); +``` + +### Set the length of a short Dynamic Link + +You can also set the `PathLength` parameter to specify how the path component of the short Dynamic Link is generated: + +```csharp +var options = DynamicLinkComponentsOptions.Create (); +options.PathLength = ShortDynamicLinkPathLength.Unguessable; +components.Options = options; +``` + +By default, or if you set the parameter to `Unguessable`, the path component will be a 17-character string, like in the following example: + +``` +https://abc123.app.goo.gl/UVWXYZuvwxyz12345 +``` + +Such strings are created by base62-encoding randomly generated 96-bit numbers. Use this setting to prevent your Dynamic Links URLs from being guessed and crawled, which can potentially expose sensitive information to unintended recipients. + +If you set the parameter to `Short`, the path component will be a string that is only as long as needed to be unique, with a minimum length of 4 characters: + +``` +https://abc123.app.goo.gl/WXYZ +``` + +Use this method if sensitive information would not be exposed if a short Dynamic Link URL were guessed. + +### Create a short link from a long link + +You can use the Firebase Dynamic Links API to shorten a long Dynamic Link. To do so, call: + +```csharp +components.GetShortenUrl ((shortUrl, warnings, error) => { + // Handle shortURL or error. + if (error != null) { + Console.WriteLine ($"Error generating short link: {error.LocalizedDescription}"); + return; + } + + Console.WriteLine (shortUrl.AbsoluteString); +}); + +// async/await way: + +try { + var result = await components.GetShortenUrlAsync (); + Console.WriteLine (result.ShortUrl.AbsoluteString); +} catch (NSErrorException ex) { + Console.WriteLine ($"Error generating short link: {ex.Error.LocalizedDescription}"); +} +``` + +### Specifying a custom URL scheme for Dynamic Links + +By default, Dynamic Links uses your app's bundle identifier as the URL scheme needed to open up your application. We recommend staying with this default value to keep your implementation simple. + +However, developers who are already using a custom URL scheme for other purposes may wish to use this same custom URL scheme for their Dynamic Links as well. If you are in this situation, you can specify a different URL scheme for your Firebase Dynamic Links by following these steps: + +1. When setting up your app, make sure you specify the default URL scheme to be used by your application before configuring your FirebaseApp shared instance: + + ```csharp + public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) + { + // Set DeepLinkUrlScheme to the custom URL scheme you defined in your + // Info.plist. + Options.DefaultInstance.DeepLinkUrlScheme = "com.xamarin.firebase.ios.dynamiclinkssample"; + App.Configure (); + + return true; + } + ``` + +2. Whenever you create any Dynamic Link, you will need to specify the custom URL scheme that your app uses. You can do this through the Firebase console, setting the `customScheme` in the Builder API, specifying the `ius` parameter in your URL, or sending the `iosCustomScheme` parameter to the REST API + +## Manually constructing a Dynamic Link URL + +Please, read this [Firebase documentation][7] to learn how to construct a Dynamic Link URL manually. + +## Open Dynamic Links in your app + +You will need to override `OpenUrl (UIApplication, NSUrl, string, NSObject)` method to support iOS 8 or older and `OpenUrl (UIApplication, NSUrl, NSDictionary)` method to support iOS 9 or newer. These methods handle links received through your app's custom URL scheme. These methods are called when your app receives a link on iOS 8 and older, and when your app is opened for the first time after installation on any version of iOS: + +```csharp +// Handle Custom Url Schemes for iOS 9 or newer +public override bool OpenUrl (UIApplication app, NSUrl url, NSDictionary options) +{ + return OpenUrl (app, url, null, null); +} + +// Handle Custom Url Schemes for iOS 8 or older +public override bool OpenUrl (UIApplication application, NSUrl url, string sourceApplication, NSObject annotation) +{ + Console.WriteLine ("I'm handling a link through the OpenUrl method."); + + var dynamicLink = DynamicLinks.SharedInstance?.FromCustomSchemeUrl (url); + + if (dynamicLink == null) + return false; + + // Handle the deep link. For example, show the deep-linked content or + // apply a promotional offer to the user's account. + return true; +} +``` + +Override `ContinueUserActivity (UIApplication, NSUserActivity, UIApplicationRestorationHandler)` method to handle links received as [Universal Links][8] on iOS 9 and newer: + +```csharp +// Handle links received as Universal Links on iOS 9 or later +public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler) +{ + return DynamicLinks.SharedInstance.HandleUniversalLink (userActivity.WebPageUrl, (dynamicLink, error) => { + if (error != null) { + System.Console.WriteLine (error.LocalizedDescription); + return; + } + + // Handle Universal Link + }); +} +``` + +## View Dynamic Links Analytics Data + +To help you gauge the effectiveness of your promotions and campaigns, Firebase Dynamic Links provides several ways to view analytics data and integrate with analytics tools. + +To learn more about this, please, read the following [documentation][10]. + +## Debugging Dynamic Links + +To help you debug your Dynamic Links, you can preview your Dynamic Links' behavior on different platforms and configurations with an automatically-generated flowchart. Generate the flowchart by adding the d=1 parameter to any short or long Dynamic Link. For example, app_code.app.goo.gl/path?d=1 for a short Dynamic Link. + +To learn more about this, please, read the following [documentation][11]. + +## Generate link previews with social metadata + +To learn about this, please, read the following [documentation][12]. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/dynamic-links/ios) to see original Firebase documentation._ + +[1]: https://firebase.google.com/console/ +[2]: http://support.google.com/firebase/answer/7015592 +[3]: https://developer.apple.com/account/#/membership +[4]: https://console.firebase.google.com/project/_/durablelinks/links/ +[5]: https://firebase.google.com/docs/reference/dynamic-links/analytics +[6]: https://firebase.google.com/docs/dynamic-links/rest +[7]: https://firebase.google.com/docs/dynamic-links/create-manually +[8]: https://developer.apple.com/library/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html +[10]: https://firebase.google.com/docs/dynamic-links/analytics +[11]: https://firebase.google.com/docs/dynamic-links/debug +[12]: https://firebase.google.com/docs/dynamic-links/link-previews diff --git a/docs/Firebase/Invites/Details.md b/docs/Firebase/Invites/Details.md new file mode 100755 index 00000000..7c1d509b --- /dev/null +++ b/docs/Firebase/Invites/Details.md @@ -0,0 +1,39 @@ +Firebase Invites are an out-of-the-box solution for app referrals and sharing via email or SMS. + +Word of mouth is one of the most effective ways of getting users to install your app. In a [recent study](https://think.storage.googleapis.com/docs/mobile-app-marketing-insights.pdf) of thousands of smartphone users, researchers found that the #1 reason people discovered an app is because they heard about it from a friend or colleague. Firebase Invites makes it easy to turn your app's users into your app's strongest advocates. + +Firebase Invites builds on Firebase Dynamic Links. which ensures that recipients of links have the best possible experience for their platform and the apps they have installed. + +## Key capabilities + +| | | +|-:|--| +| **Rich sharing that's easy for users** | Firebase Invites makes it simple for users to send content to their friends, over both SMS and email, by ensuring that referral codes, recipe entries, or other shared content gets passed along with the invitation—no cutting-and-pasting required. | +| **Rich sharing that's easy to implement** | Firebase Invites handles the invitation flow for you, allowing you to deliver a straightforward user experience without taking engineering time away from the rest of your app. | +| **Invitations that survive the installation process** | Because Firebase Invites is built on Dynamic Links, invitations work across the App Store and Play Store installation processes and ensure that recipients get the referral code or shared content, whether or not they have your app installed. | + +### Complete list of features + +| Sending invitations | | +|--------------------:|--| +| **Combines the most common sharing channels** | Firebase Invites can be sent over SMS or email. | +| **Merged contacts selector** | The share screen's contact list is populated from the user's Google Contacts and the contacts stored locally on the device. | +| **Recipient recommendations** | The share screen recommends recipients based on the contacts the user communicates with frequently. | +| **Customizable invitation message** | You can set the default message to be sent with invitations. This message can be edited by the user when sending invitations. | +| **Customizable rich-text email invitations** | You can customize email invitations in either of two ways:
  • Provide custom images that will be used along with additional text and graphics from the app's entry in the App Store or Play Store.

  • Provide HTML for a fully customized email invitation.
| + +| Receiving invitations | | +|----------------------:|--| +| **Installation flow initiation** | Firebase Invites smartly directs the recipient to the appropriate store when they open the link and need to install the app. iOS users are sent to the App Store, Android users are sent to the Play Store, and web users are sent to the store for the sender's platform. | +| **Installation flow survival** | Invitations use Dynamic Links, which ensure that the link information contained in the invitation doesn't get lost, even if the user has to install the app first. | +| **Low friction for users** | iOS and Android users can receive invitations without signing in to their Google Accounts. | + +## How does it work? + +![FirebaseInvites_HowItWorks](https://firebase.google.com/docs/invites/images/send-invitations.png) + +When a user taps one of your app's Share buttons and chooses the Firebase Invites channel—usually named "Email and SMS"—the Firebase Invites sharing screen opens. From the sharing screen, the user selects recipients from their Google contacts and contacts stored locally on the device, optionally customizes the invitation message and sends the invitations. Invitations are sent by email or SMS, depending on the available contact information, and contain a Dynamic Link to your app. + +When the invitation's recipients open the Dynamic Link in the invitation, they are sent to the Play Store or App Store if they need to install your app; then, your app opens and can retrieve and handle the link. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/invites/) to see original Firebase documentation._ \ No newline at end of file diff --git a/docs/Firebase/Invites/GettingStarted.md b/docs/Firebase/Invites/GettingStarted.md new file mode 100755 index 00000000..2ec8d7a0 --- /dev/null +++ b/docs/Firebase/Invites/GettingStarted.md @@ -0,0 +1,167 @@ +# Send and Receive Firebase Invites from Your iOS App + +## Table of content + +- [Send and Receive Firebase Invites from Your iOS App](#send-and-receive-firebase-invites-from-your-ios-app) + - [Table of content](#table-of-content) + - [Prerequisites](#prerequisites) + - [Add Firebase to your app](#add-firebase-to-your-app) + - [Configure Invites in your app](#configure-invites-in-your-app) + - [Give your app access to your Contacts](#give-your-app-access-to-your-contacts) + - [Handle incoming app invites](#handle-incoming-app-invites) + - [Enable your users to send app invites](#enable-your-users-to-send-app-invites) + +## Prerequisites + +Firebase Invites requires iOS 8 or newer. You can target iOS 7 in your app, but all Firebase Invites SDK calls will be no-ops if the app isn't running on iOS 8 or newer. + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][1], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][2]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][2] again at any time. +5. Go to **Project settings**, in **General** tab select your recently created iOS bundle ID. +6. Set your App Store ID, you can find your App Store ID in your app’s URL. (e.g. https://itunes.apple.com/us/app/yourapp/id123456789, **123456789** is your App Store ID). +7. Set your Team ID, you can find your Team ID in the Apple Member Center under the [membership tab][3]. +8. You must enable Firebase Dynamic Links to use Firebase Invites. In [Firebase console][1], in your project menu go to Dynamic Links tab, accept the terms of service if you are prompted to do so, copy Url link, paste it into your Navigation bar and add **/apple-app-site-association** (e.g https://app_code.app.goo.gl//apple-app-site-association) +9. Your app is connected if the apple-app-site-association file contains a reference to your app's App Store ID and bundle ID, for example: + +``` +{"applinks":{"apps":[],"details":[{"appID":"1234567890.com.example.ios","paths":["/*"]}]}} +``` + +If the details field is empty, double-check that you specified your Team ID. + +If you haven't created a Dynamic Link follow this [documentation][3]. + +## Configure Invites in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Xamarin Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Open `GoogleService-Info.plist` file and change `IS_SIGNIN_ENABLED` and `IS_APPINVITE_ENABLED` values to `Yes`. +4. Add the following line of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` namespace): + +```csharp +App.Configure (); +``` + +## Give your app access to your Contacts + +Invites, before sending your invitation, access to your Contacts to show you a list with them so you can invite everyone to try your awesome app! To allow Invites to achieve this, you need to do the following steps in Visual Studio: + +1. Open your `Info.plist` and go to **Source** tab. +2. Add a new entry and search for **Privacy - Contacts Usage Description**. +3. Add your message that will be displayed when Invites tries to access to your contacts as value. For example: "MyRecipeApp uses your contacts to make it easy to share recipes with your friends." + +## Handle incoming app invites + +After you have configured your app, you must next enable your app to handle incoming app invites. + +When a user selects an incoming app invite on their iOS device, if the user has not yet installed your app, they can choose to install your app from its iTunes App Store page. When the user opens your app for the first time, it's important for your app to provide a personalized onboarding experience to increase the likelihood they will become an engaged, long-term user of your app. To help you do this, the Invites SDK provides the deeplink and invitation ID associated with the app invite received by the user. + +> ![note_icon] **_Note:_** _If the Invites SDK indicates a weak match for a deeplink, it means that the match between the deeplink and the receiving device may not be perfect. In this case your app should reveal no personal information from the deeplink._ + +```csharp +// Support for iOS 9 or later +public override bool OpenUrl (UIApplication app, NSUrl url, NSDictionary options) +{ + var openUrlOptions = new UIApplicationOpenUrlOptions (options); + return OpenUrl (app, url, openUrlOptions.SourceApplication, openUrlOptions.Annotation); +} + +// Support for iOS 8 or before +public override bool OpenUrl (UIApplication application, NSUrl url, string sourceApplication, NSObject annotation) +{ + // Handle Sign In + if (SignIn.SharedInstance.HandleUrl (url, sourceApplication ?? "", annotation)) + return true; + + // Handle App Invite requests + return Invites.HandleUniversalLink (url, HandleInvitesUniversalLink); + + void HandleInvitesUniversalLink (ReceivedInvite receivedInvite, NSError error) + { + // ... + } +} +``` + +## Enable your users to send app invites + +Now that your app is ready to handle incoming invites correctly, it is time to enable your app to send invitations to the user's contacts. + +Before a user can send Invites, the user must be signed in with their Google Account. Follow [Google Sign-In getting started][4] to integrate Sign-In into your +app. + +To send invitations, first declare a class that implements the `IInviteDelegate` interface: + +```csharp +public class ViewController : UIViewController, IInviteDelegate +``` + +Then, add a **Send Invitation** button to your app. You can add this button as an option in your main menu, or add this button alongside your deep-linkable content, so that users can send specific content along with the invitation. See the [Firebase Invites best practices][5]. + +When users tap your **Send Invitation** button, open the invitation dialog: + +```csharp +// NOTE: You must have the App Store ID set in your developer console project +// in order for invitations to successfully be sent. +public void SendInvite () +{ + // When you create the invitation dialog, you must specify the title + // of the invitation dialog and the invitation message to send. + // You can also customize the image and deep link URL that + // get sent in the invitation + var inviteDialog = Invites.GetInviteDialog (); + inviteDialog.SetInviteDelegate (this); + inviteDialog.SetTitle ("Invites Sample"); + + // A message hint for the dialog. Note this manifests differently depending on the + // received invitation type. For example, in an email invite this appears as the subject. + inviteDialog.SetMessage ($"Try this out! {anUrl}"); + + // These following values are optionals and are only sent via email + inviteDialog.SetDeepLink ("app_url"); + inviteDialog.SetDescription ("A description of the app"); + inviteDialog.SetCustomImage ("The url of the image"); + inviteDialog.SetCallToActionText ("Button title of the invitations"); + + // If you have an Android version of your app and you want to send + // an invitation that can be opened on Android in addition to iOS + var targetApp = new InvitesTargetApplication { AndroidClientId = "Android ID" }; + inviteDialog.SetOtherPlatformsTargetApplication (targetApp); + + inviteDialog.Open (); +} +``` + +If you want to learn more about Invitation parts, see the following [documentation][6]. + +The `SendInvite` method above then opens the contact chooser dialog where the user selects the contacts to invite. Invitations are sent by email or SMS. After the user sends the invitation, your app receives a callback to the `InviteFinished` method of `IInviteDelegate` interface: + +```csharp +[Export ("inviteFinishedWithInvitations:error:")] +public void InviteFinished (string [] invitationIds, NSError error) +{ + if (error == null) { + foreach (var id in invitationIds) + System.Console.WriteLine (id); + } else { + System.Console.WriteLine (error.LocalizedDescription); + } +} +``` + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/invites/ios) to see original Firebase documentation._ + +[1]: https://firebase.google.com/console/ +[2]: http://support.google.com/firebase/answer/7015592 +[3]: https://firebase.google.com/docs/dynamic-links/ios#create-a-dynamic-link +[4]: https://components.xamarin.com/gettingstarted/googleiossignin +[5]: https://firebase.google.com/docs/invites/best-practices +[6]: https://firebase.google.com/docs/invites/ios#customize-the-invitation +[note_icon]: https://cdn3.iconfinder.com/data/icons/UltimateGnome/22x22/apps/gnome-app-install-star.png +[warning_icon]: https://cdn2.iconfinder.com/data/icons/freecns-cumulus/32/519791-101_Warning-20.png \ No newline at end of file diff --git a/docs/Firebase/MLKit.ModelInterpreter/Details.md b/docs/Firebase/MLKit.ModelInterpreter/Details.md new file mode 100755 index 00000000..e9cc7b71 --- /dev/null +++ b/docs/Firebase/MLKit.ModelInterpreter/Details.md @@ -0,0 +1,19 @@ +# Custom Models + +If you're an experienced ML developer and ML Kit's pre-built models don't meet your needs, you can use a custom [TensorFlow Lite][1] model with ML Kit. + +Host your TensorFlow Lite models using Firebase or package them with your app. Then, use the ML Kit SDK to perform inference using the best-available version of your custom model. If you host your model with Firebase, ML Kit automatically updates your users with the latest version. + +## Key capabilities + +| | | +|--|--| +| **TensorFlow Lite model hosting** | Host your models using Firebase to reduce your app's binary size and to make sure your app is always using the most recent version available of your model. | +| **On-device ML inference** | Perform inference in an iOS or Android app by using the ML Kit SDK to run your custom TensorFlow Lite model. The model can be bundled with the app, hosted in the Cloud, or both. | +| **Automatic model fallback** | Specify multiple model sources; use a locally-stored model when the Cloud-hosted model is unavailable. | +| **Automatic model updates** | Configure the conditions under which your app automatically downloads new versions of your model: when the user's device is idle, is charging, or has a Wi-Fi connection. | + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/ml-kit/use-custom-models) to see original Firebase documentation._ + +[1]: https://www.tensorflow.org/mobile/tflite/ + diff --git a/docs/Firebase/MLKit.ModelInterpreter/GettingStarted.md b/docs/Firebase/MLKit.ModelInterpreter/GettingStarted.md new file mode 100755 index 00000000..68666eff --- /dev/null +++ b/docs/Firebase/MLKit.ModelInterpreter/GettingStarted.md @@ -0,0 +1,268 @@ +# Use a TensorFlow Lite model for inference with ML Kit on iOS + +You can use ML Kit to perform on-device inference with a TensorFlow Lite model. + +ML Kit can use TensorFlow Lite models only on devices running iOS 9 and newer. + +## Table of Content + +- [Use a TensorFlow Lite model for inference with ML Kit on iOS](#use-a-tensorflow-lite-model-for-inference-with-ml-kit-on-ios) + - [Table of Content](#table-of-content) + - [Add Firebase to your app](#add-firebase-to-your-app) + - [Configure MLKit in your app](#configure-mlkit-in-your-app) + - [Host or bundle your model](#host-or-bundle-your-model) + - [Host models on Firebase](#host-models-on-firebase) + - [Bundle models with an app](#bundle-models-with-an-app) + - [Load the model](#load-the-model) + - [Configure a Firebase-hosted model source](#configure-a-firebase-hosted-model-source) + - [Configure a local model source](#configure-a-local-model-source) + - [Create an interpreter from your model sources](#create-an-interpreter-from-your-model-sources) + - [Specify the model's input and output](#specify-the-models-input-and-output) + - [Perform inference on input data](#perform-inference-on-input-data) + - [Appendix: Model security](#appendix-model-security) +- [Use a custom TensorFlow Lite build](#use-a-custom-tensorflow-lite-build) + - [Prerequisites](#prerequisites) + - [Building the Tensorflow Lite library](#building-the-tensorflow-lite-library) + - [Add your custom TensorFlow Lite framework](#add-your-custom-tensorflow-lite-framework) + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][1], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][2]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][2] again at any time. + +## Configure MLKit in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Visual Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Add the following line of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` namespace): + +```csharp +App.Configure (); +``` + +Convert the TensorFlow model you want to use to TensorFlow Lite format. See [TOCO: TensorFlow Lite Optimizing Converter][3]. + +## Host or bundle your model + +Before you can use a TensorFlow Lite model for inference in your app, you must make the model available to ML Kit. ML Kit can use TensorFlow Lite models hosted remotely using Firebase, bundled with the app binary, or both. + +By hosting a model on Firebase, you can update the model without releasing a new app version, and you can use Remote Config and A/B Testing to dynamically serve different models to different sets of users. + +If you choose to only provide the model by hosting it with Firebase, and not bundle it with your app, you can reduce the initial download size of your app. Keep in mind, though, that if the model is not bundled with your app, any model-related functionality will not be available until your app downloads the model for the first time. + +By bundling your model with your app, you can ensure your app's ML features still work when the Firebase-hosted model isn't available. + +> ![note_icon] _Before you use a custom model in a publicly-available app, be aware of the [security implications][4]._ + +### Host models on Firebase + +To host your TensorFlow Lite model on Firebase: + +1. In the **ML Kit** section of the [Firebase console][1], click the **Custom** tab. +2. Click **Add custom model** (or **Add another model**). +3. Specify a name that will be used to identify your model in your Firebase project, then upload the TensorFlow Lite model file (usually ending in `.tflite` or `.lite`). + +After you add a custom model to your Firebase project, you can reference the model in your apps using the name you specified. At any time, you can upload a new TensorFlow Lite model, and your app will download the new model and start using it when the app next restarts. You can define the device conditions required for your app to attempt to update the model (see below). + +### Bundle models with an app + +To bundle your TensorFlow Lite model with your app, add the model file (usually ending in `.tflite` or `.lite`) into the **Resources** folder in your Visual Studio project, or, add it anywhere on your project tree and change **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. The model file will be included in the app bundle and available to ML Kit. + +## Load the model + +To use your TensorFlow Lite model in your app, first configure ML Kit with the locations where your model is available: on the cloud using Firebase, in local storage, or both. If you specify both a local and cloud model source, ML Kit will use the cloud source if it is available, and fall back to the locally-stored model if the cloud source isn't available. + +### Configure a Firebase-hosted model source + +If you hosted your model with Firebase, register a `CloudModelSource` object, specifying the name you assigned the model when you uploaded it, and the conditions under which ML Kit should download the model initially and when updates are available: + +```csharp +var conditions = new ModelDownloadConditions (true, true); +var cloudModelSource = new CloudModelSource ("my_cloud_model", true, conditions, conditions); +var registrationSuccessful = ModelManager.SharedInstance.Register (cloudModelSource); +``` + +### Configure a local model source + +If you bundled the model with your app, register a `LocalModelSource` object, specifying the filename of the TensorFlow Lite model and assigning the model a name you will use in the next step: + +```csharp +var modelPath = NSBundle.MainBundle.PathForResource ("my_model", "tflite"); + +if (modelPath == null) + return; + +var localModelSource = new LocalModelSource ("my_local_model", modelPath); +var registrationSuccessful = ModelManager.SharedInstance.Register (localModelSource); +``` + +### Create an interpreter from your model sources + +After you configure your model sources, create a `ModelOptions` object with the Cloud source, the local source, or both, and use it to get an instance of `ModelInterpreter`. If you only have one source, specify `null` for the source type you don't use: + +```csharp +var options = new ModelOptions ("my_cloud_model", "my_local_model"); +var interpreter = ModelInterpreter.Create (options); +``` + +## Specify the model's input and output + +Next, configure the model interpreter's input and output formats. + +A TensorFlow Lite model takes as input and produces as output one or more multidimensional arrays. These arrays contain either `byte`, `int`, `long`, or `float` values. You must configure ML Kit with the number and dimensions ("shape") of the arrays your model uses. + +If you don't know the shape and data type of your model's input and output, you can use the TensorFlow Lite Python interpreter to inspect your model. For example: + +```python +import tensorflow as tf + +interpreter = tf.lite.Interpreter(model_path="my_model.tflite") +interpreter.allocate_tensors() + +# Print input shape and type +print(interpreter.get_input_details()[0]['shape']) # Example: [1 224 224 3] +print(interpreter.get_input_details()[0]['dtype']) # Example: + +# Print output shape and type +print(interpreter.get_output_details()[0]['shape']) # Example: [1 1000] +print(interpreter.get_output_details()[0]['dtype']) # Example: +``` + +After you determine the format of your model's input and output, configure your app's model interpreter by creating a `ModelInputOutputOptions` object. + +For example, a floating-point image classification model might take as input an _**N**x224x224x3_ array of `float` values, representing a batch of **N** _224x224_ three-channel (RGB) images, and produce as output a list of 1000 `float` values, each representing the probability the image is a member of one of the 1000 categories the model predicts. + +For such a model, you would configure the model interpreter's input and output as shown below: + +```csharp +var ioOptions = new ModelInputOutputOptions (); + +ioOptions.SetInputFormat (0, ModelElementType.Float32, new nuint[] { 1, 224, 224, 3 }, out NSError inputError); +if (inputError != null) { return; } + +ioOptions.SetOutputFormat (0, ModelElementType.Float32, new nuint [] { 1, 1000 }, out NSError outputError); +if (outputError != null) { return; } +``` + +## Perform inference on input data + +Finally, to perform inference using the model, get your input data, perform any transformations on the data that might be necessary for your model, and build a `NSData` object that contains the data: + +```csharp +var inputData = new NSMutableData (0); + +// Prepare your input data + +var modelInputs = new ModelInputs (); +modelInputs.AddInput (inputData, out NSError error); + +if (error != null) { return; } +``` + +After you prepare your model input, pass the input and input/output options to your `ModelInterpreter`'s `Run` method. + +```csharp +interpreter.Run (modelInputs, ioOptions, HandleModelInterpreterRunCallback); + +void HandleModelInterpreterRunCallback (ModelOutputs outputs, NSError error) +{ + if (error != null || outputs == null) { + return; + } + + // Process outputs +} + +// or use async / await + +try { + ModelOutputs outputs = await interpreter.RunAsync (modelInputs, ioOptions); + + if (outputs == null) { + return; + } + + // Process outputs +} catch (NSErrorException ex) { + +} +``` + +You can get the output by calling the `GetOutput` method of the object that is returned. For example: + +```csharp +// Get first and only output of inference with a batch size of 1 +var output = outputs.GetOutput (0, out NSError outputError) as NSArray; +var probabilities = output.GetItem> (0); +``` + +How you use the output depends on the model you are using. + +## Appendix: Model security + +Regardless of how you make your TensorFlow Lite models available to ML Kit, ML Kit stores them in the standard serialized protobuf format in local storage. + +In theory, this means that anybody can copy your model. However, in practice, most models are so application-specific and obfuscated by optimizations that the risk is similar to that of competitors disassembling and reusing your code. Nevertheless, you should be aware of this risk before you use a custom model in your app. + +# Use a custom TensorFlow Lite build + +If you're an experienced ML developer and the pre-built TensorFlow Lite library doesn't meet your needs, you can use a custom [TensorFlow Lite][5] build with ML Kit. For example, you may want to add custom ops. + +## Prerequisites + +* A working [TensorFlow Lite][6] build environment +* A checkout of TensorFlow Lite 1.10.1 + +You can check out the correct version using Git: + +``` +$ git checkout -b work +$ git reset --hard tflite-v1.10.1 +$ git cherry-pick 4dcfddc5d12018a5a0fdca652b9221ed95e9eb23 +``` + +## Building the Tensorflow Lite library + +1. Build Tensorflow Lite (with your modifications) following the [standard instructions][7] +2. Build the framework: + + ``` + $ tensorflow/lite/lib_package/create_ios_frameworks.sh + ``` + +The generated framework can be found at `tensorflow/lite/gen/ios_frameworks/tensorflow_lite.framework.zip` + +> ![note_icon] **_Note:_** _There have been [build issues reported][8] with Xcode 9.3_ + +## Add your custom TensorFlow Lite framework + +To be able to use your TensorFlow Lite framework, the framework must be copied to TensorFlow Lite Xamarin Build Download cache folder, to do so, follow these steps: + +1. Unzip the tensorflow_lite.framework.zip file generated above +2. Copy the unzipped tensorflow_lite.framework to the TensorFlow Lite Xamarin Build Download cache folder + + * Mac: `~/Library/Caches/XamarinBuildDownload/TnsrFlwLt-1.10.1/Frameworks` + * Windows: `%LOCALAPPDATA%\XamarinBuildDownloadCache\TnsrFlwLt-1.10.1\Frameworks` + + > ![note_icon] _**Note:**_ _If you cannot find the folder, please, open Visual Studio and build your project so Xamarin Build Download can create the TensorFlow Lite cache folder. The `1.10.1` version can vary._ + +3. Erase your `bin` and `obj` folders of your project and build again on Visual Studio + +If you want to use the default TensorFlow Lite framework again, just erase everything that start with `TnsrFlwLt-1.10.1` in the Xamarin Build Download cache folder, erase the `bin` and `obj` folders and build again your project on Visual Studio. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/ml-kit/ios/use-custom-models) to see original Firebase documentation._ + +[1]: https://firebase.google.com/console/ +[2]: http://support.google.com/firebase/answer/7015592 +[3]: https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/toco +[4]: https://firebase.google.com/docs/ml-kit/ios/use-custom-models#model_security +[5]: https://www.tensorflow.org/mobile/tflite/ +[6]: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/README.md#building-tensorflow-lite-and-the-demo-app-from-source +[7]: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/g3doc/ios.md +[8]: https://github.com/tensorflow/tensorflow/issues/18356 +[note_icon]: https://cdn3.iconfinder.com/data/icons/UltimateGnome/22x22/apps/gnome-app-install-star.png +[warning_icon]: https://cdn2.iconfinder.com/data/icons/freecns-cumulus/32/519791-101_Warning-20.png diff --git a/docs/Firebase/MLKit/Details.md b/docs/Firebase/MLKit/Details.md new file mode 100755 index 00000000..8be391b3 --- /dev/null +++ b/docs/Firebase/MLKit/Details.md @@ -0,0 +1,31 @@ +Use machine learning in your apps to solve real-world problems. + +ML Kit is a mobile SDK that brings Google's machine learning expertise to Android and iOS apps in a powerful yet easy-to-use package. Whether you're new or experienced in machine learning, you can implement the functionality you need in just a few lines of code. There's no need to have deep knowledge of neural networks or model optimization to get started. On the other hand, if you are an experienced ML developer, ML Kit provides convenient APIs that help you use your custom TensorFlow Lite models in your mobile apps. + +## Key capabilities + +| | | +|-:|-| +| **Production-ready for common use cases:** | ML Kit comes with a set of ready-to-use APIs for common mobile use cases: recognizing text, detecting faces, identifying landmarks, scanning barcodes, and labeling images. Simply pass in data to the ML Kit library and it gives you the information you need. | +| **On-device or in the cloud:** | ML Kit’s selection of APIs run on-device or in the cloud. Our on-device APIs can process your data quickly and work even when there’s no network connection. Our cloud-based APIs, on the other hand, leverage the power of Google Cloud Platform's machine learning technology to give you an even higher level of accuracy. | +| **Deploy custom models:** | If ML Kit's APIs don't cover your use cases, you can always bring your own existing TensorFlow Lite models. Just upload your model to Firebase, and we'll take care of hosting and serving it to your app. ML Kit acts as an API layer to your custom model, making it simpler to run and use. | + +## How does it work? + +ML Kit makes it easy to apply ML techniques in your apps by bringing Google's ML technologies, such as the Google Cloud Vision API, TensorFlow Lite, and the Android Neural Networks API together in a single SDK. Whether you need the power of cloud-based processing, the real-time capabilities of mobile-optimized on-device models, or the flexibility of custom TensorFlow Lite models, ML Kit makes it possible with just a few lines of code. + +### What features are available on device or in the cloud? + +| Feature | On-device | Cloud | +|------------------------|:-----------------:|:-----------------:| +| Text recognition | ![available_icon] | ![available_icon] | +| Face detection | ![available_icon] | | +| Barcode scanning | ![available_icon] | | +| Image labeling | ![available_icon] | ![available_icon] | +| Landmark recognition | | ![available_icon] | +| Custom model inference | ![available_icon] | | + + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/analytics/) to see original Firebase documentation._ + +[available_icon]: https://cdn3.iconfinder.com/data/icons/flat-actions-icons-9/512/Tick_Mark-24.png \ No newline at end of file diff --git a/docs/Firebase/MLKit/GettingStarted.md b/docs/Firebase/MLKit/GettingStarted.md new file mode 100755 index 00000000..f674c9f9 --- /dev/null +++ b/docs/Firebase/MLKit/GettingStarted.md @@ -0,0 +1,1297 @@ +# Get Started with Firebase MLKit for iOS + +Use machine learning in your apps to solve real-world problems. + +ML Kit is a mobile SDK that brings Google's machine learning expertise to Android and iOS apps in a powerful yet easy-to-use package. Whether you're new or experienced in machine learning, you can implement the functionality you need in just a few lines of code. There's no need to have deep knowledge of neural networks or model optimization to get started. On the other hand, if you are an experienced ML developer, ML Kit provides convenient APIs that help you use your custom TensorFlow Lite models in your mobile apps. + +## Table of Content + +- [Get Started with Firebase MLKit for iOS](#get-started-with-firebase-mlkit-for-ios) + - [Table of Content](#table-of-content) + - [Add Firebase to your app](#add-firebase-to-your-app) + - [Configure MLKit in your app](#configure-mlkit-in-your-app) +- [Text Recognition](#text-recognition) + - [Choose between on-device and Cloud APIs](#choose-between-on-device-and-cloud-apis) +- [Recognize Text in Images with ML Kit on iOS](#recognize-text-in-images-with-ml-kit-on-ios) + - [Input image guidelines](#input-image-guidelines) + - [Recognize text in images](#recognize-text-in-images) + - [1. Run the text recognizer](#1-run-the-text-recognizer) + - [2. Extract text from blocks of recognized text](#2-extract-text-from-blocks-of-recognized-text) + - [Tips to improve real-time performance](#tips-to-improve-real-time-performance) + - [Recognize text in images of documents](#recognize-text-in-images-of-documents) + - [1. Run the text recognizer](#1-run-the-text-recognizer-1) + - [2. Extract text from blocks of recognized text](#2-extract-text-from-blocks-of-recognized-text-1) +- [Face Detection](#face-detection) + - [Key capabilities](#key-capabilities) +- [Face Detection Concepts Overview](#face-detection-concepts-overview) +- [Detect Faces with ML Kit on iOS](#detect-faces-with-ml-kit-on-ios) + - [Input image guidelines](#input-image-guidelines-1) + - [1. Configure the face detector](#1-configure-the-face-detector) + - [2. Run the face detector](#2-run-the-face-detector) + - [3. Get information about detected faces](#3-get-information-about-detected-faces) +- [Barcode Scanning](#barcode-scanning) + - [Key capabilities](#key-capabilities-1) +- [Scan Barcodes with ML Kit on iOS](#scan-barcodes-with-ml-kit-on-ios) + - [Input image guidelines](#input-image-guidelines-2) + - [1. Configure the barcode detector](#1-configure-the-barcode-detector) + - [2. Run the barcode detector](#2-run-the-barcode-detector) + - [3. Get information from barcodes](#3-get-information-from-barcodes) + - [Tips to improve real-time performance](#tips-to-improve-real-time-performance-1) +- [Image Labeling](#image-labeling) + - [Choose between on-device and Cloud APIs](#choose-between-on-device-and-cloud-apis-1) + - [Example on-device labels](#example-on-device-labels) + - [Example cloud labels](#example-cloud-labels) + - [Google Knowledge Graph entity IDs](#google-knowledge-graph-entity-ids) +- [Label Images with ML Kit on iOS](#label-images-with-ml-kit-on-ios) + - [On-device image labeling](#on-device-image-labeling) + - [1. Configure the image labeler](#1-configure-the-image-labeler) + - [2. Run the image labeler](#2-run-the-image-labeler) + - [3. Get information about labeled objects](#3-get-information-about-labeled-objects) + - [Tips to improve real-time performance](#tips-to-improve-real-time-performance-2) + - [Cloud image labeling](#cloud-image-labeling) + - [1. Configure the image labeler](#1-configure-the-image-labeler-1) + - [2. Run the image labeler](#2-run-the-image-labeler-1) + - [3. Get information about labeled objects](#3-get-information-about-labeled-objects-1) +- [Landmark Recognition](#landmark-recognition) + - [Key capabilities](#key-capabilities-2) +- [Recognize Landmarks with ML Kit on iOS](#recognize-landmarks-with-ml-kit-on-ios) + - [Configure the landmark detector](#configure-the-landmark-detector) + - [Run the landmark detector](#run-the-landmark-detector) + - [Get information about the recognized landmarks](#get-information-about-the-recognized-landmarks) +- [Protect your ML Kit iOS app's Cloud credentials](#protect-your-ml-kit-ios-apps-cloud-credentials) + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][10], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][11]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][11] again at any time. + +If you want to use the Cloud-based model, and you have not already enabled the Cloud-based APIs for your project, do so now: + +1. Open the ML Kit APIs page of the Firebase console. +2. If you have not already upgraded your project to a Blaze plan, click **Upgrade** to do so. (You will be prompted to upgrade only if your project isn't on the Blaze plan.) + + Only Blaze-level projects can use Cloud-based APIs. + +3. If Cloud-based APIs aren't already enabled, click **Enable Cloud-based APIs.** + +> ![note_icon] *Before you deploy to production an app that uses a Cloud API, you should take some additional steps to [prevent and mitigate the effect of unauthorized API access.][4]* + +If you want to use only the on-device model, you can skip these steps. + +## Configure MLKit in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Visual Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Add the following line of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` namespace): + +```csharp +App.Configure (); +``` + +--- + +# Text Recognition + +With ML Kit's text recognition APIs, you can recognize text in any Latin-based language (and more, with Cloud-based text recognition). + +Text recognition can automate tedious data entry for credit cards, receipts, and business cards. With the Cloud-based API, you can also extract text from documents, which you can use to increase accessibility or translate documents. Apps can even keep track of real-world objects, such as by reading the numbers on trains. + +## Choose between on-device and Cloud APIs + +| | On-device | Cloud | +|--|-----------|-------| +| **Pricing** | Free | Free for first 1000 uses of this feature per month: see [Pricing][1] | +| **Ideal use cases** | Real-time processing—ideal for a camera or video feed
Recognizing sparse text in images | High-accuracy text recognition
Recognizing sparse text in images
Recognizing densely-spaced text in documents

See the [Cloud Vision API demo][2] | +| **Language support** | Recognizes Latin characters | Recognizes and identifies a broad range of languages and special characters | + +# Recognize Text in Images with ML Kit on iOS + +You can use ML Kit to recognize text in images. ML Kit has both a general-purpose API suitable for recognizing text in images, such as the text of a street sign, and an API optimized for recognizing the text of documents. The general-purpose API has both on-device and cloud-based models. Document text recognition is available only as a cloud-based model. See the [overview][3] for a comparison of the cloud and on-device models. + +## Input image guidelines + +* For ML Kit to accurately recognize text, input images must contain text that is represented by sufficient pixel data. Ideally, for Latin text, each character should be at least 16x16 pixels. For Chinese, Japanese, and Korean text (only supported by the cloud-based APIs), each character should be 24x24 pixels. For all languages, there is generally no accuracy benefit for characters to be larger than 24x24 pixels. + + So, for example, a 640x480 image might work well to scan a business card that occupies the full width of the image. To scan a document printed on letter-sized paper, a 720x1280 pixel image might be required. + +* Poor image focus can hurt text recognition accuracy. If you aren't getting acceptable results, try asking the user to recapture the image. +* If you are recognizing text in a real-time application, you might also want to consider the overall dimensions of the input images. Smaller images can be processed faster, so to reduce latency, capture images at lower resolutions (keeping in mind the above accuracy requirements) and ensure that the text occupies as much of the image as possible. + +## Recognize text in images + +To recognize text in an image using either an on-device or cloud-based model, run the text recognizer as described below. + +### 1. Run the text recognizer + +Pass the image as a `UIImage` or a `CMSampleBuffer` to the `VisionTextRecognizer`'s `ProcessImage` method: + +1. Get an instance of `VisionTextRecognizer` by calling either `GetOnDeviceTextRecognizer` or `GetCloudTextRecognizer`: + + ```csharp + var vision = VisionApi.Create (); + var textRecognizer = vision.GetOnDeviceTextRecognizer (); + ``` + + To use the cloud model: + + > ![note_icon] _Use of ML Kit to access Cloud ML functionality is subject to the [Google Cloud Platform License Agreement][5] and [Service Specific Terms][6], and billed accordingly. For billing information, see the Firebase [Pricing][1] page._ + + ```csharp + var vision = VisionApi.Create (); + var textRecognizer = vision.GetOnDeviceTextRecognizer (); + + // Or, to provide language hints to assist with language detection: + // See https://cloud.google.com/vision/docs/languages for supported languages + var options = new VisionCloudTextRecognizerOptions { LanguageHints = new []{ "es" } }; + var textRecognizer = vision.GetCloudTextRecognizer (options); + ``` + +2. Create a `VisionImage` object using a `UIImage` or a `CMSampleBuffer`. + + To use a `UIImage`: + + 1. If necessary, rotate the image so that its `ImageOrientation` property is `Up`. + 2. Create a `VisionImage` object using the correctly-rotated `UIImage`. Do not specify any rotation metadata—the default value, `TopLeft`, must be used. + + ```csharp + var image = new VisionImage (anImage); + ``` + + To use a `CMSampleBuffer`: + + 1. Create a `VisionImageMetadata` object that specifies the orientation of the image data contained in the `CMSampleBuffer` buffer. + + For example, if you are using image data captured from the device's back-facing camera: + + ```csharp + var metadata = new VisionImageMetadata (); + + // Using back-facing camera + var devicePosition = AVCaptureDevicePosition.Back; + + var deviceOrientation = UIDevice.CurrentDevice.Orientation; + + switch (deviceOrientation) { + case UIDeviceOrientation.Portrait: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.LeftTop : VisionDetectorImageOrientation.RightTop; + break; + case UIDeviceOrientation.LandscapeLeft: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.BottomLeft : VisionDetectorImageOrientation.TopLeft; + break; + case UIDeviceOrientation.PortraitUpsideDown: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.RightBottom : VisionDetectorImageOrientation.LeftBottom; + break; + case UIDeviceOrientation.LandscapeRight: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.TopRight : VisionDetectorImageOrientation.BottomRight; + break; + case UIDeviceOrientation.FaceUp: + case UIDeviceOrientation.FaceDown: + case UIDeviceOrientation.Unknown: + metadata.Orientation = VisionDetectorImageOrientation.LeftTop; + break; + } + ``` + + 2. Create a `VisionImage` object using the `CMSampleBuffer` object and the rotation metadata: + + ```csharp + var image = new VisionImage (buffer); + image.Metadata = metadata; + ``` + +3. Then, pass the image to the `ProcessImage` method: + + ```csharp + textRecognizer.ProcessImage (image, HandleVisionTextRecognitionCallback); + + void HandleVisionTextRecognitionCallback (VisionText text, NSError error) + { + Console.WriteLine (error?.Description ?? text?.Text); + } + + // or using async / await + + try { + var text = await textRecognizer.ProcessImageAsync (image); + Console.WriteLine (text.Text); + } catch (NSErrorException ex) { + Console.WriteLine (ex.Error.Description); + } + ``` + +### 2. Extract text from blocks of recognized text + +If the text recognition operation succeeds, it will return a `VisionText` object. A `VisionText` object contains the full text recognized in the image and zero or more `VisionTextBlock` objects. + +Each `VisionTextBlock` represents a rectangular block of text, which contain zero or more `VisionTextLine` objects. + +Each `VisionTextLine` object contains zero or more `VisionTextElement` objects, which represent words and word-like entities (dates, numbers, and so on). + +For each `VisionTextBlock`, `VisionTextLine`, and `VisionTextElement` object, you can get the text recognized in the region and the bounding coordinates of the region. + +For example: + +```csharp +foreach (var block in text.Blocks) { + var blockText = block.Text; + var blockConfidence = block.Confidence.Value; + var blockLanguage = block.RecognizedLanguages; + var blockCornerPoints = block.CornerPoints; + var blockFrame = block.Frame; + + foreach (var line in block.Lines) { + var lineText = line.Text; + var lineConfidence = line.Confidence.Value; + var lineLanguage = line.RecognizedLanguages; + var lineCornerPoints = line.CornerPoints; + var lineFrame = line.Frame; + + foreach (var element in line.Elements) { + var elementText = element.Text; + var elementConfidence = element.Confidence.Value; + var elementLanguage = element.RecognizedLanguages; + var elementCornerPoints = element.CornerPoints; + var elementFrame = element.Frame; + } + } +} +``` + +### Tips to improve real-time performance + +If you want use the on-device model to recognize text in a real-time application, follow these guidelines to achieve the best framerates: + +* Throttle calls to the text recognizer. If a new video frame becomes available while the text recognizer is running, drop the frame. +* Consider capturing images at a lower resolution. However, also keep in mind this API's image dimension requirements. + +## Recognize text in images of documents + +To recognize the text of a document, configure and run the cloud-based document text recognizer as described below. + +> ![note_icon] _Use of ML Kit to access Cloud ML functionality is subject to the [Google Cloud Platform License Agreement][5] and [Service Specific Terms][6], and billed accordingly. For billing information, see the Firebase [Pricing][1] page._ + +The document text recognition API, described below, provides an interface that is intended to be more convenient for working with images of documents. However, if you prefer the interface provided by the sparse text API, you can use it instead to scan documents by configuring the cloud text recognizer to use the dense text model. + +To use the document text recognition API: + +### 1. Run the text recognizer + +Pass the image as a `UIImage` or a `CMSampleBuffer` to the `VisionDocumentTextRecognizer`'s `ProcessImage` method: + +1. Get an instance of `VisionDocumentTextRecognizer` by calling `GetCloudDocumentTextRecognizer`: + + ```csharp + var vision = VisionApi.Create (); + var textRecognizer = vision.GetCloudDocumentTextRecognizer (); + + // Or, to provide language hints to assist with language detection: + // See https://cloud.google.com/vision/docs/languages for supported languages + var options = new VisionCloudDocumentTextRecognizerOptions { LanguageHints = new [] { "es" } }; + var textRecognizer = vision.GetCloudDocumentTextRecognizer (options); + ``` + +2. Create a `VisionImage` object using a `UIImage` or a `CMSampleBuffer`. + + To use a `UIImage`: + + 1. If necessary, rotate the image so that its `ImageOrientation` property is `Up`. + 2. Create a `VisionImage` object using the correctly-rotated `UIImage`. Do not specify any rotation metadata—the default value, `TopLeft`, must be used. + + ```csharp + var image = new VisionImage (anImage); + ``` + + To use a `CMSampleBuffer`: + + 1. Create a `VisionImageMetadata` object that specifies the orientation of the image data contained in the `CMSampleBuffer` buffer. + + For example, if you are using image data captured from the device's back-facing camera: + + ```csharp + var metadata = new VisionImageMetadata (); + + // Using back-facing camera + var devicePosition = AVCaptureDevicePosition.Back; + + var deviceOrientation = UIDevice.CurrentDevice.Orientation; + + switch (deviceOrientation) { + case UIDeviceOrientation.Portrait: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.LeftTop : VisionDetectorImageOrientation.RightTop; + break; + case UIDeviceOrientation.LandscapeLeft: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.BottomLeft : VisionDetectorImageOrientation.TopLeft; + break; + case UIDeviceOrientation.PortraitUpsideDown: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.RightBottom : VisionDetectorImageOrientation.LeftBottom; + break; + case UIDeviceOrientation.LandscapeRight: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.TopRight : VisionDetectorImageOrientation.BottomRight; + break; + case UIDeviceOrientation.FaceUp: + case UIDeviceOrientation.FaceDown: + case UIDeviceOrientation.Unknown: + metadata.Orientation = VisionDetectorImageOrientation.LeftTop; + break; + } + ``` + + 2. Create a `VisionImage` object using the `CMSampleBuffer` object and the rotation metadata: + + ```csharp + var image = new VisionImage (buffer); + image.Metadata = metadata; + ``` + +3. Then, pass the image to the `ProcessImage` method: + + ```csharp + textRecognizer.ProcessImage (image, HandleVisionDocumentTextRecognitionCallback); + + void HandleVisionDocumentTextRecognitionCallback (VisionDocumentText text, NSError error) + { + Console.WriteLine (error?.Description ?? text?.Text); + } + + // or using async / await + + try { + var text = await textRecognizer.ProcessImageAsync (image); + Console.WriteLine (text.Text); + } catch (NSErrorException ex) { + Console.WriteLine (ex.Error.Description); + } + ``` + +### 2. Extract text from blocks of recognized text + +If the text recognition operation succeeds, it will return a `VisionDocumentText` object. A `VisionDocumentText` object contains the full text recognized in the image and a hierarchy of objects that reflect the structure of the recognized document: + +* `VisionDocumentTextBlock` +* `VisionDocumentTextParagraph` +* `VisionDocumentTextWord` +* `VisionDocumentTextSymbol` + +For each `VisionDocumentTextBlock`, `VisionDocumentTextParagraph`, `VisionDocumentTextWord`, and `VisionDocumentTextSymbol` object, you can get the text recognized in the region and the bounding coordinates of the region. + +For example: + +```csharp +foreach (var block in text.Blocks) { + var blockText = block.Text; + var blockConfidence = block.Confidence.Value; + var blockLanguage = block.RecognizedLanguages; + var blockRecognizedBreak = block.RecognizedBreak; + var blockFrame = block.Frame; + + foreach (var paragraph in block.Paragraphs) { + var paragraphText = paragraph.Text; + var paragraphConfidence = paragraph.Confidence; + var paragraphLanguage = paragraph.RecognizedLanguages; + var paragraphRecognizedBreak = paragraph.RecognizedBreak; + var paragraphFrame = paragraph.Frame; + + foreach (var word in paragraph.Words) { + var wordText = word.Text; + var wordConfidence = word.Confidence; + var wordLanguage = word.RecognizedLanguages; + var wordRecognizedBreak = word.RecognizedBreak; + var wordFrame = word.Frame; + + foreach (var symbol in word.Symbols) { + var symbolText = symbol.Text; + var symbolConfidence = symbol.Confidence; + var symbolLanguage = symbol.RecognizedLanguages; + var symbolRecognizedBreak = symbol.RecognizedBreak; + var symbolFrame = symbol.Frame; + } + } + } +} +``` + +--- + +# Face Detection + +With ML Kit's face detection API, you can detect faces in an image, identify key facial features, and get the contours of detected faces. + +With face detection, you can get the information you need to perform tasks like embellishing selfies and portraits, or generating avatars from a user's photo. Because ML Kit can perform face detection in real time, you can use it in applications like video chat or games that respond to the player's expressions. + +## Key capabilities + +| | | +|-------|-------| +| **Recognize and locate facial features** | Get the coordinates of the eyes, ears, cheeks, nose, and mouth of every face detected. | +| **Get the contours of facial features** | Get the contours of detected faces and their eyes, eyebrows, lips, and nose. | +| **Recognize facial expressions** | Determine whether a person is smiling or has their eyes closed. | +| **Track faces across video frames** | Get an identifier for each individual person's face that is detected. This identifier is consistent across invocations, so you can, for example, perform image manipulation on a particular person in a video stream. | +| **Process video frames in real time** | Face detection is performed on the device, and is fast enough to be used in real-time applications, such as video manipulation. | + +# Face Detection Concepts Overview + +Face detection is the process of automatically locating human faces in visual media (digital images or video). A face that is detected is reported at a position with an associated size and orientation. Once a face is detected, it can be searched for landmarks such as the eyes and nose. + +To learn more about this, please, read the following [documentation][7]. + +# Detect Faces with ML Kit on iOS + +You can use ML Kit to detect faces in images and video. + +## Input image guidelines + +For ML Kit to accurately detect faces, input images must contain faces that are represented by sufficient pixel data. In general, each face you want to detect in an image should be at least 100x100 pixels. If you want to detect the contours of faces, ML Kit requires higher resolution input: each face should be at least 200x200 pixels. + +If you are detecting faces in a real-time application, you might also want to consider the overall dimensions of the input images. Smaller images can be processed faster, so to reduce latency, capture images at lower resolutions (keeping in mind the above accuracy requirements) and ensure that the subject's face occupies as much of the image as possible. + +Poor image focus can hurt accuracy. If you aren't getting acceptable results, try asking the user to recapture the image. + +The orientation of a face relative to the camera can also affect what facial features ML Kit detects. See [Face Detection Concepts][7]. + +## 1. Configure the face detector + +Before you apply face detection to an image, if you want to change any of the face detector's default settings, specify those settings with a `VisionFaceDetectorOptions` object. You can change the following settings: + +| Settings | Value | +|----------|-------| +| **PerformanceMode** | **Fast** (default) / **Accurate**

Favor speed or accuracy when detecting faces. | +| **LandmarkMode** | **None** (default) / **All**

Whether to attempt to detect the facial "landmarks"—eyes, ears, nose, cheeks, mouth—of all detected faces. | +| **ContourMode** | **None** (default) / **All**

Whether to detect the contours of facial features. Contours are detected for only the most prominent face in an image. | +| **ClassificationMode** | **None** (default) / **All**

Whether or not to classify faces into categories such as "smiling", and "eyes open". | +| **MinFaceSize** | **CGFloat** (default: 0.1)

The minimum size, relative to the image, of faces to detect. | +| **IsTrackingEnabled** | **False** (default) / **True**

Whether or not to assign faces an ID, which can be used to track faces across images.

Note that when contour detection is enabled, only one face is detected, so face tracking doesn't produce useful results. For this reason, and to improve detection speed, don't enable both contour detection and face tracking. + +For example, build a `VisionFaceDetectorOptions` object like one of the following examples: + +```csharp +// High-accuracy landmark detection and face classification +var options = new VisionFaceDetectorOptions { + PerformanceMode = VisionFaceDetectorPerformanceMode.Accurate, + LandmarkMode = VisionFaceDetectorLandmarkMode.All, + ClassificationMode = VisionFaceDetectorClassificationMode.All +}; + +// Real-time contour detection of multiple faces +var options = new VisionFaceDetectorOptions { + ContourMode = VisionFaceDetectorContourMode.All +}; +``` + +## 2. Run the face detector + +To detect faces in an image, pass the image as a `UIImage` or a `CMSampleBuffer` to the `VisionFaceDetector`'s `ProcessImage` method: + +1. Get an instance of VisionFaceDetector: + + ```csharp + var vision = VisionApi.Create (); + var faceDetector = vision.GetFaceDetector (); + ``` + +2. Create a `VisionImage` object using a `UIImage` or a `CMSampleBuffer`. + + To use a `UIImage`: + + 1. If necessary, rotate the image so that its `ImageOrientation` property is `Up`. + 2. Create a `VisionImage` object using the correctly-rotated `UIImage`. Do not specify any rotation metadata—the default value, `TopLeft`, must be used. + + ```csharp + var image = new VisionImage (anImage); + ``` + + To use a `CMSampleBuffer`: + + 1. Create a `VisionImageMetadata` object that specifies the orientation of the image data contained in the `CMSampleBuffer` buffer. + + For example, if you are using image data captured from the device's back-facing camera: + + ```csharp + var metadata = new VisionImageMetadata (); + + // Using back-facing camera + var devicePosition = AVCaptureDevicePosition.Back; + + var deviceOrientation = UIDevice.CurrentDevice.Orientation; + + switch (deviceOrientation) { + case UIDeviceOrientation.Portrait: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.LeftTop : VisionDetectorImageOrientation.RightTop; + break; + case UIDeviceOrientation.LandscapeLeft: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.BottomLeft : VisionDetectorImageOrientation.TopLeft; + break; + case UIDeviceOrientation.PortraitUpsideDown: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.RightBottom : VisionDetectorImageOrientation.LeftBottom; + break; + case UIDeviceOrientation.LandscapeRight: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.TopRight : VisionDetectorImageOrientation.BottomRight; + break; + case UIDeviceOrientation.FaceUp: + case UIDeviceOrientation.FaceDown: + case UIDeviceOrientation.Unknown: + metadata.Orientation = VisionDetectorImageOrientation.LeftTop; + break; + } + ``` + + 2. Create a `VisionImage` object using the `CMSampleBuffer` object and the rotation metadata: + + ```csharp + var image = new VisionImage (buffer); + image.Metadata = metadata; + ``` + +3. Then, pass the image to the `ProcessImage` method: + + ```csharp + faceDetector.ProcessImage (image, HandleVisionFaceDetectionCallback); + + void HandleVisionFaceDetectionCallback (VisionFace [] faces, NSError error) + { + if (error != null) { + Console.WriteLine (error.Description); + return; + } + + if (faces == null || faces.Length == 0) { + Console.WriteLine ("No faces were found."); + return; + } + + Console.WriteLine ("Faces were found."); + } + + // or using async / await + + try { + var faces = await faceDetector.ProcessImageAsync (image); + Console.WriteLine (faces != null || faces.Length == 0 ? "No faces were found." : "Faces were found"); + } catch (NSErrorException ex) { + Console.WriteLine (ex.Error.Description); + } + ``` + +## 3. Get information about detected faces + +If the face detection operation succeeds, the face detector passes an array of `VisionFace` objects to the completion handler. Each `VisionFace` object represents a face that was detected in the image. For each face, you can get its bounding coordinates in the input image, as well as any other information you configured the face detector to find. For example: + +```csharp +foreach (var face in faces) { + // Boundaries of face in image + var frame = face.Frame; + if (face.HasHeadEulerAngleY) { + var rotY = face.HeadEulerAngleY; // Head is rotated to the right rotY degrees + } + if (face.HasHeadEulerAngleZ) { + var rotY = face.HeadEulerAngleZ; // Head is rotated upward rotZ degrees + } + // If landmark detection was enabled (mouth, ears, eyes, cheeks, and + // nose available): + if (face.GetLandmark (FaceLandmarkType.LeftEye) is VisionFaceLandmark leftEye) { + var leftEyePosition = leftEye.Position; + } + // If contour detection was enabled: + if (face.GetContour (FaceContourType.LeftEye) is VisionFaceContour leftEyeContour) { + var leftEyePoints = leftEyeContour.Points; + } + if (face.GetContour (FaceContourType.UpperLipBottom) is VisionFaceContour upperLipBottomContour) { + var upperLipBottomPoints = upperLipBottomContour.Points; + } + // If classification was enabled: + if (face.HasSmilingProbability) { + var smilingProbability = face.SmilingProbability; + } + if (face.HasRightEyeOpenProbability) { + var rightEyeOpenProbability = face.RightEyeOpenProbability; + } + // If face tracking was enabled: + if (face.HasTrackingId) { + var trackingId = face.TrackingId; + } +} +``` + +--- + +# Barcode Scanning + +With ML Kit's barcode scanning API, you can read data encoded using most standard barcode formats. + +Barcodes are a convenient way to pass information from the real world to your app. In particular, when using 2D formats such as QR code, you can encode structured data such as contact information or WiFi network credentials. Because ML Kit can automatically recognize and parse this data, your app can respond intelligently when a user scans a barcode. + +## Key capabilities + +| | | +|-------|-------| +| **Reads most standard formats** | Linear formats: Codabar, Code 39, Code 93, Code 128, EAN-8, EAN-13, ITF, UPC-A, UPC-E

2D formats: Aztec, Data Matrix, PDF417, QR Code | +| **Automatic format detection** | Scan for all supported barcode formats at once, without having to specify the format you're looking for. Or, boost scanning speed by restricting the detector to only the formats you're interested in. | +| **Extracts structured data** | Structured data stored using one of the supported 2D formats are automatically parsed. Supported information types include URLs, contact information, calendar events, email addresses, phone numbers, SMS message prompts, ISBNs, WiFi connection information, geographic location, and AAMVA-standard driver information. | +| **Works with any orientation** | Barcodes are recognized and scanned regardless of their orientation: right-side-up, upside-down, or sideways. | + +# Scan Barcodes with ML Kit on iOS + +You can use ML Kit to recognize and decode barcodes. + +## Input image guidelines + +* For ML Kit to accurately read barcodes, input images must contain barcodes that are represented by sufficient pixel data. In general, the smallest meaningful unit of the barcode should be at least 2 pixels wide (and for 2-dimensional codes, 2 pixels tall). + + For example, EAN-13 barcodes are made up of bars and spaces that are 1, 2, 3, or 4 units wide, so an EAN-13 barcode image ideally has bars and spaces that are at least 2, 4, 6, and 8 pixels wide. Because an EAN-13 barcode is 95 units wide in total, the barcode should be at least 190 pixels wide. + + Denser formats, such as PDF417, need greater pixel dimensions for ML Kit to reliably read them. For example, a PDF417 code can have up to 34 17-unit wide "words" in a single row, which would ideally be at least 1156 pixels wide. + +* Poor image focus can hurt scanning accuracy. If you aren't getting acceptable results, try asking the user to recapture the image. + +* If you are scanning barcodes in a real-time application, you might also want to consider the overall dimensions of the input images. Smaller images can be processed faster, so to reduce latency, capture images at lower resolutions (keeping in mind the above accuracy requirements) and ensure that the barcode occupies as much of the image as possible. + +## 1. Configure the barcode detector + +If you know which barcode formats you expect to read, you can improve the speed of the barcode detector by configuring it to only detect those formats. + +For example, to detect only Aztec code and QR codes, build a `VisionBarcodeDetectorOptions` object as in the following example: + +```csharp +var options = new VisionBarcodeDetectorOptions (VisionBarcodeFormat.All); +``` + +The following formats are supported: + +* Code128 +* Code39 +* Code93 +* CodaBar +* EAN13 +* EAN8 +* ITF +* UPCA +* UPCE +* QRCode +* PDF417 +* Aztec +* DataMatrix + +> ![note_icon] **_Note:_** _For a Data Matrix code to be recognized, the code must intersect the center point of the input image. Consequently, only one Data Matrix code can be recognized in an image._ + +## 2. Run the barcode detector + +To scan barcodes in an image, pass the image as a `UIImage` or a `CMSampleBuffer` to the `VisionBarcodeDetector`'s `Detect` method: + +1. Get an instance of VisionFaceDetector: + + ```csharp + var vision = VisionApi.Create (); + var faceDetector = vision.GetBarcodeDetector (options); + ``` + +2. Create a `VisionImage` object using a `UIImage` or a `CMSampleBuffer`. + + To use a `UIImage`: + + 1. If necessary, rotate the image so that its `ImageOrientation` property is `Up`. + 2. Create a `VisionImage` object using the correctly-rotated `UIImage`. Do not specify any rotation metadata—the default value, `TopLeft`, must be used. + + ```csharp + var image = new VisionImage (anImage); + ``` + + To use a `CMSampleBuffer`: + + 1. Create a `VisionImageMetadata` object that specifies the orientation of the image data contained in the `CMSampleBuffer` buffer. + + For example, if you are using image data captured from the device's back-facing camera: + + ```csharp + var metadata = new VisionImageMetadata (); + + // Using back-facing camera + var devicePosition = AVCaptureDevicePosition.Back; + + var deviceOrientation = UIDevice.CurrentDevice.Orientation; + + switch (deviceOrientation) { + case UIDeviceOrientation.Portrait: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.LeftTop : VisionDetectorImageOrientation.RightTop; + break; + case UIDeviceOrientation.LandscapeLeft: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.BottomLeft : VisionDetectorImageOrientation.TopLeft; + break; + case UIDeviceOrientation.PortraitUpsideDown: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.RightBottom : VisionDetectorImageOrientation.LeftBottom; + break; + case UIDeviceOrientation.LandscapeRight: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.TopRight : VisionDetectorImageOrientation.BottomRight; + break; + case UIDeviceOrientation.FaceUp: + case UIDeviceOrientation.FaceDown: + case UIDeviceOrientation.Unknown: + metadata.Orientation = VisionDetectorImageOrientation.LeftTop; + break; + } + ``` + + 2. Create a `VisionImage` object using the `CMSampleBuffer` object and the rotation metadata: + + ```csharp + var image = new VisionImage (buffer); + image.Metadata = metadata; + ``` + +3. Then, pass the image to the `Detect` method: + + ```csharp + barcodeDetector.Detect (image, HandleVisionBarcodeDetectionCallback); + + void HandleVisionBarcodeDetectionCallback (VisionBarcode [] barcodes, NSError error) + { + if (error != null) { + Console.WriteLine (error.Description); + return; + } + + if (barcodes == null || barcodes.Length == 0) { + Console.WriteLine ("No barcodes were found."); + return; + } + + Console.WriteLine ("Barcodes were found."); + } + + // or using async / await + + try { + var barcodes = await barcodeDetector.DetectAsync (image); + Console.WriteLine (barcodes != null || barcodes.Length == 0 ? "No barcodes were found." : "Barcodes were found"); + } catch (NSErrorException ex) { + Console.WriteLine (ex.Error.Description); + } + ``` + +## 3. Get information from barcodes + +If the barcode recognition operation succeeds, the detector returns an array of `VisionBarcode` objects. Each `VisionBarcode` object represents a barcode that was detected in the image. For each barcode, you can get its bounding coordinates in the input image, as well as the raw data encoded by the barcode. Also, if the barcode detector was able to determine the type of data encoded by the barcode, you can get an object containing parsed data. + +For example: + +```csharp +foreach (var barcode in barcodes) { + var corners = barcode.CornerPoints; + var displayValue = barcode.DisplayValue; + var rawValue = barcode.RawValue; + var valueType = barcode.ValueType; + + switch (valueType) { + case VisionBarcodeValueType.WiFi: + var ssid = barcode.Wifi.Ssid; + var password = barcode.Wifi.Password; + var encryptionType = barcode.Wifi.Type; + break; + case VisionBarcodeValueType.Url: + var title = barcode.Url.Title; + var url = barcode.Url.Url; + break; + default: + // See API reference for all supported value types + break; + } +} +``` + +## Tips to improve real-time performance + +If you want to scan barcodes in a real-time application, follow these guidelines to achieve the best framerates: + +* Throttle calls to the detector. If a new video frame becomes available while the detector is running, drop the frame. + +* If you are using the output of the detector to overlay graphics on the input image, first get the result from ML Kit, then render the image and overlay in a single step. By doing so, you render to the display surface only once for each input frame. + +* If you use the Camera2 API, capture images in `ImageFormat.YUV_420_888` format. + + If you use the older Camera API, capture images in `ImageFormat.NV21` format. + +* Consider capturing images at a lower resolution. However, also keep in mind this API's image dimension requirements. + +--- + +# Image Labeling + +With ML Kit's image labeling APIs, you can recognize entities in an image without having to provide any additional contextual metadata, using either an on-device API or a cloud-based API. + +Image labeling gives you insight into the content of images. When you use the API, you get a list of the entities that were recognized: people, things, places, activities, and so on. Each label found comes with a score that indicates the confidence the ML model has in its relevance. With this information, you can perform tasks such as automatic metadata generation and content moderation. + +## Choose between on-device and Cloud APIs + +| | On-device | Cloud | +|--|-----------|-------| +| **Pricing** | Free | Free for first 1000 uses of this feature per month: see [Pricing][1] | +| **Label coverage** | 400+ labels that cover the most commonly-found concepts in photos. See below. | 10,000+ labels in many categories. See below.

Also, try the [Cloud Vision API demo][2] to see what labels can be found for an image you provide. | +| **Knowledge Graph entity ID support** | ![available_icon] | ![available_icon] | + +### Example on-device labels + +The device-based API supports 400+ labels, such as the following examples: + +| Category | Example labels | +|------------|--------------------------------------| +| People | Crowd
Selfie
Smile | +| Activities | Dancing
Eating
Surfing | +| Things | Car
Piano
Receipt | +| Animals | Bird
Cat
Dog | +| Plants | Flower
Fruit
Vegetable | +| Places | Beach
Lake
Mountain | + +### Example cloud labels + +The cloud-based API supports 10,000+ labels, such as the following examples: + +| Category | Example labels | Category | Example labels +| ---------|----------------|----------|--------------- +| Arts & entertainment | Sculpture
Musical Instrument
Dance | Astronomical objects | Comet
Galaxy
Star | +| Business & industrial | Restaurant
Factory
Airline | Colors | Red
Green
Blue | +| Design | Floral
Pattern
Wood Stain | Drink | Coffee
Tea
Milk | +| Events | Meeting
Picnic
Vacation | Fictional characters | Santa Claus
Superhero
Mythical creature | +| Food | Casserole
Fruit
Potato chip | Home & garden | Laundry basket
Dishwasher
Fountain | +| Activities | Wedding
Dancing
Motorsport | Materials | Ceramic
Textile
Fiber | +| Media | Newsprint
Document
Sign | Modes of transport | Aircraft
Motorcycle
Subway | +| Occupations | Actor
Florist
Police | Organisms | Plant
Animal
Fungus | +| Organizations | Government
Club
College | Places | Airport
Mountain
Tent | +| Technology | Robot
Computer
Solar panel | Things | Bicycle
Pipe
Doll | + +## Google Knowledge Graph entity IDs + +In addition the text description of each label that ML Kit returns, it also returns the label's Google Knowledge Graph entity ID. This ID is a string that uniquely identifies the entity represented by the label, and is the same ID used by the [Knowledge Graph Search API][8]. You can use this string to identify an entity across languages, and independently of the formatting of the text description. + +# Label Images with ML Kit on iOS + +You can use ML Kit to label objects recognized in an image, using either an on-device model or a cloud model. + +## On-device image labeling + +To use the on-device image labeling model, configure and run the the image labeler as described below. + +### 1. Configure the image labeler + +By default, the on-device image labeler returns only labels that have a confidence score of 0.5 or greater. If you want to change this setting, create a `VisionLabelDetectorOptions` object as in the following example: + +```csharp +var options = new VisionLabelDetectorOptions (.6f); +``` + +### 2. Run the image labeler + +To recognize and label entities in an image, pass the image as a `UIImage` or a `CMSampleBuffer` to the `VisionLabelDetector`'s `Detect` method: + +1. Get an instance of `VisionLabelDetector`: + + ```csharp + var vision = VisionApi.Create (); + var labelDetector = vision.GetLabelDetector (options); + ``` + +2. Create a `VisionImage` object using a `UIImage` or a `CMSampleBuffer`. + + To use a `UIImage`: + + 1. If necessary, rotate the image so that its `ImageOrientation` property is `Up`. + 2. Create a `VisionImage` object using the correctly-rotated `UIImage`. Do not specify any rotation metadata—the default value, `TopLeft`, must be used. + + ```csharp + var image = new VisionImage (anImage); + ``` + + To use a `CMSampleBuffer`: + + 1. Create a `VisionImageMetadata` object that specifies the orientation of the image data contained in the `CMSampleBuffer` buffer. + + For example, if you are using image data captured from the device's back-facing camera: + + ```csharp + var metadata = new VisionImageMetadata (); + + // Using back-facing camera + var devicePosition = AVCaptureDevicePosition.Back; + + var deviceOrientation = UIDevice.CurrentDevice.Orientation; + + switch (deviceOrientation) { + case UIDeviceOrientation.Portrait: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.LeftTop : VisionDetectorImageOrientation.RightTop; + break; + case UIDeviceOrientation.LandscapeLeft: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.BottomLeft : VisionDetectorImageOrientation.TopLeft; + break; + case UIDeviceOrientation.PortraitUpsideDown: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.RightBottom : VisionDetectorImageOrientation.LeftBottom; + break; + case UIDeviceOrientation.LandscapeRight: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.TopRight : VisionDetectorImageOrientation.BottomRight; + break; + case UIDeviceOrientation.FaceUp: + case UIDeviceOrientation.FaceDown: + case UIDeviceOrientation.Unknown: + metadata.Orientation = VisionDetectorImageOrientation.LeftTop; + break; + } + ``` + + 2. Create a `VisionImage` object using the `CMSampleBuffer` object and the rotation metadata: + + ```csharp + var image = new VisionImage (buffer); + image.Metadata = metadata; + ``` + +3. Then, pass the image to the `Detect` method: + + ```csharp + labelDetector.Detect (image, HandleVisionLabelDetectionCallback); + + void HandleVisionLabelDetectionCallback (VisionLabel [] labels, NSError error) + { + if (error != null) { + Console.WriteLine (error.Description); + return; + } + + if (labels == null || labels.Length == 0) { + Console.WriteLine ("No labels were found."); + return; + } + + Console.WriteLine ("Labels were found."); + } + + // or using async / await + + try { + var labels = await labelDetector.DetectAsync (image); + Console.WriteLine (labels != null || labels.Length == 0 ? "No labels were found." : "Labels were found"); + } catch (NSErrorException ex) { + Console.WriteLine (ex.Error.Description); + } + ``` + +### 3. Get information about labeled objects + +If image labeling succeeds, an array of `VisionLabel` objects will be passed to the completion handler. From each object, you can get information about a feature recognized in the image. + +For example: + +```csharp +foreach (var label in labels) { + var labelText = label.Label; + var entityId = label.EntityId; + var confidence = label.Confidence; +} +``` + +### Tips to improve real-time performance + +If you want to label images in a real-time application, follow these guidelines to achieve the best framerates: + +* Throttle calls to the image labeler. If a new video frame becomes available while the image labeler is running, drop the frame. + +## Cloud image labeling + +To use the Cloud-based image labeling model, configure and run the the image labeler as described below. + +> ![note_icon] _Use of ML Kit to access Cloud ML functionality is subject to the [Google Cloud Platform License Agreement][5] and [Service Specific Terms][6], and billed accordingly. For billing information, see the Firebase [Pricing][1] page._ + +### 1. Configure the image labeler + +By default, the Cloud detector uses the stable version of the model and returns up to 10 results. If you want to change either of these settings, specify them with a `VisionCloudDetectorOptions` object as in the following example: + +```csharp +var options = new VisionCloudDetectorOptions { + ModelType = VisionCloudModelType.Latest, + MaxResults = 5 +}; +``` + +In the next step, pass the VisionCloudDetectorOptions object when you create the Cloud detector object. + +### 2. Run the image labeler + +To recognize and label entities in an image, pass the image as a UIImage or a CMSampleBufferRef to the `VisionCloudLabelDetector`'s `Detect` method: + +1. Get an instance of `VisionCloudLabelDetector`: + +```csharp +var vision = VisionApi.Create (); +var labelDetector = vision.GetCloudLabelDetector (options); +``` + +2. Create a `VisionImage` object using a `UIImage` or a `CMSampleBuffer`. + + To use a `UIImage`: + + 1. If necessary, rotate the image so that its `ImageOrientation` property is `Up`. + 2. Create a `VisionImage` object using the correctly-rotated `UIImage`. Do not specify any rotation metadata—the default value, `TopLeft`, must be used. + + ```csharp + var image = new VisionImage (anImage); + ``` + + To use a `CMSampleBuffer`: + + 1. Create a `VisionImageMetadata` object that specifies the orientation of the image data contained in the `CMSampleBuffer` buffer. + + For example, if you are using image data captured from the device's back-facing camera: + + ```csharp + var metadata = new VisionImageMetadata (); + + // Using back-facing camera + var devicePosition = AVCaptureDevicePosition.Back; + + var deviceOrientation = UIDevice.CurrentDevice.Orientation; + + switch (deviceOrientation) { + case UIDeviceOrientation.Portrait: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.LeftTop : VisionDetectorImageOrientation.RightTop; + break; + case UIDeviceOrientation.LandscapeLeft: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.BottomLeft : VisionDetectorImageOrientation.TopLeft; + break; + case UIDeviceOrientation.PortraitUpsideDown: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.RightBottom : VisionDetectorImageOrientation.LeftBottom; + break; + case UIDeviceOrientation.LandscapeRight: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.TopRight : VisionDetectorImageOrientation.BottomRight; + break; + case UIDeviceOrientation.FaceUp: + case UIDeviceOrientation.FaceDown: + case UIDeviceOrientation.Unknown: + metadata.Orientation = VisionDetectorImageOrientation.LeftTop; + break; + } + ``` + + 2. Create a `VisionImage` object using the `CMSampleBuffer` object and the rotation metadata: + + ```csharp + var image = new VisionImage (buffer); + image.Metadata = metadata; + ``` + +3. Then, pass the image to the `Detect` method: + + ```csharp + labelDetector.Detect (image, HandleVisionCloudLabelDetectionCompletion); + + void HandleVisionCloudLabelDetectionCompletion (VisionCloudLabel [] labels, NSError error) + { + if (error != null) { + Console.WriteLine (error.Description); + return; + } + + if (labels == null || labels.Length == 0) { + Console.WriteLine ("No labels were found."); + return; + } + + Console.WriteLine ("Labels were found."); + } + + // or using async / await + + try { + var labels = await labelDetector.DetectAsync (image); + Console.WriteLine (labels != null || labels.Length == 0 ? "No labels were found." : "Labels were found"); + } catch (NSErrorException ex) { + Console.WriteLine (ex.Error.Description); + } + ``` + +### 3. Get information about labeled objects + +If image labeling succeeds, an array of `VisionCloudLabel` objects will be passed to the completion handler. From each object, you can get information about an entity recognized in the image. This information doesn't contain frame or other location data. + +For example: + +```csharp +foreach (var label in labels) { + var labelText = label.Label; + var entityId = label.EntityId; + var confidence = label.Confidence; +} +``` + +--- + +# Landmark Recognition + +With ML Kit's landmark recognition API, you can recognize well-known landmarks in an image. + +When you pass an image to this API, you get the landmarks that were recognized in it, along with each landmark's geographic coordinates and the region of the image the landmark was found. You can use this information to automatically generate image metadata, create individualized experiences for users based on the content they share, and more. + +## Key capabilities + +| | | +|-------|-------| +| **Recognizes well-known landmarks** | Get the name and geographic coordinates of natural and constructed landmarks, as well as the region of the image the landmark was found. | +| **Get Google Knowledge Graph entity IDs** | A Knowledge Graph entity ID is a string that uniquely identifies the landmark that was recognized, and is the same ID used by the [Knowledge Graph Search API][8]. You can use this string to identify an entity across languages, and independently of the formatting of the text description. | +| **Low-volume use free** | Free for first 1000 uses of this feature per month: see [Pricing][1]. | + +# Recognize Landmarks with ML Kit on iOS + +You can use ML Kit to recognize well-known landmarks in an image. + +> ![note_icon] _Use of ML Kit to access Cloud ML functionality is subject to the [Google Cloud Platform License Agreement][5] and [Service Specific Terms][6], and billed accordingly. For billing information, see the Firebase [Pricing][1] page._ + +## Configure the landmark detector + +By default, the Cloud detector uses the stable version of the model and returns up to 10 results. If you want to change either of these settings, specify them with a `VisionCloudDetectorOptions` object as in the following example: + +```csharp +var options = new VisionCloudDetectorOptions { + ModelType = VisionCloudModelType.Latest, + MaxResults = 20 +}; +``` + +In the next step, pass the VisionCloudDetectorOptions object when you create the Cloud detector object. + +## Run the landmark detector + +To recognize and label entities in an image, pass the image as a `UIImage` or a `CMSampleBuffer` to the `VisionCloudLandmarkDetector's`'s `Detect` method: + +1. Get an instance of `VisionCloudLandmarkDetector's`: + + ```csharp + var vision = VisionApi.Create (); + var landmarkDetector = vision.GetCloudLandmarkDetector (options); + ``` + +2. Create a `VisionImage` object using a `UIImage` or a `CMSampleBuffer`. + + To use a `UIImage`: + + 1. If necessary, rotate the image so that its `ImageOrientation` property is `Up`. + 2. Create a `VisionImage` object using the correctly-rotated `UIImage`. Do not specify any rotation metadata—the default value, `TopLeft`, must be used. + + ```csharp + var image = new VisionImage (anImage); + ``` + + To use a `CMSampleBuffer`: + + 1. Create a `VisionImageMetadata` object that specifies the orientation of the image data contained in the `CMSampleBuffer` buffer. + + For example, if you are using image data captured from the device's back-facing camera: + + ```csharp + var metadata = new VisionImageMetadata (); + + // Using back-facing camera + var devicePosition = AVCaptureDevicePosition.Back; + + var deviceOrientation = UIDevice.CurrentDevice.Orientation; + + switch (deviceOrientation) { + case UIDeviceOrientation.Portrait: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.LeftTop : VisionDetectorImageOrientation.RightTop; + break; + case UIDeviceOrientation.LandscapeLeft: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.BottomLeft : VisionDetectorImageOrientation.TopLeft; + break; + case UIDeviceOrientation.PortraitUpsideDown: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.RightBottom : VisionDetectorImageOrientation.LeftBottom; + break; + case UIDeviceOrientation.LandscapeRight: + metadata.Orientation = devicePosition == AVCaptureDevicePosition.Front ? VisionDetectorImageOrientation.TopRight : VisionDetectorImageOrientation.BottomRight; + break; + case UIDeviceOrientation.FaceUp: + case UIDeviceOrientation.FaceDown: + case UIDeviceOrientation.Unknown: + metadata.Orientation = VisionDetectorImageOrientation.LeftTop; + break; + } + ``` + + 2. Create a `VisionImage` object using the `CMSampleBuffer` object and the rotation metadata: + + ```csharp + var image = new VisionImage (buffer); + image.Metadata = metadata; + ``` + +3. Then, pass the image to the `Detect` method: + + ```csharp + landmarkDetector.Detect (image, HandleVisionCloudLandmarkDetectionCallback); + + void HandleVisionCloudLandmarkDetectionCallback (VisionCloudLandmark [] landmarks, NSError error) + { + if (error != null) { + Console.WriteLine (error.Description); + return; + } + + if (landmarks == null || landmarks.Length == 0) { + Console.WriteLine ("No landmarks were found."); + return; + } + + Console.WriteLine ("Landmarks were found."); + } + + // or using async / await + + try { + var landmarks = await landmarkDetector.DetectAsync (image); + Console.WriteLine (landmarks != null || landmarks.Length == 0 ? "No landmarks were found." : "Landmarks were found"); + } catch (NSErrorException ex) { + Console.WriteLine (ex.Error.Description); + } + ``` + +## Get information about the recognized landmarks + +If landmark recognition succeeds, an array of `VisionCloudLandmark` objects will be passed to the completion handler. From each object, you can get information about a landmark recognized in the image. + +For example: + +```csharp +foreach (var landmark in landmarks) { + var landmarkDesc = landmark.Landmark; + var boundingPoly = landmark.Frame; + var entityId = landmark.EntityId; + + foreach (var location in landmark.Locations) { + // A landmark can have multiple locations: for example, the location the image + // was taken, and the location of the landmark depicted. + var latitude = location.Latitude; + var longitude = location.Longitude; + } + + var confidence = landmark.Confidence; +} +``` + +--- + +# Protect your ML Kit iOS app's Cloud credentials + +If your iOS app uses one of ML Kit's cloud APIs, before you launch your app in production, you should take some additional steps to prevent unauthorized API access. + +Read this [document][9] to learn more about this. + +[1]: https://firebase.google.com/pricing +[2]: https://cloud.google.com/vision/docs/drag-and-drop +[3]: https://firebase.google.com/docs/ml-kit/recognize-text +[4]: https://firebase.google.com/docs/ml-kit/ios/secure-api-key +[5]: https://cloud.google.com/terms/ +[6]: https://cloud.google.com/terms/service-terms +[7]: https://firebase.google.com/docs/ml-kit/face-detection-concepts +[8]: https://developers.google.com/knowledge-graph/ +[9]: https://firebase.google.com/docs/ml-kit/ios/secure-api-key +[10]: https://firebase.google.com/console/ +[11]: http://support.google.com/firebase/answer/7015592 +[note_icon]: https://cdn3.iconfinder.com/data/icons/UltimateGnome/22x22/apps/gnome-app-install-star.png +[warning_icon]: https://cdn2.iconfinder.com/data/icons/freecns-cumulus/32/519791-101_Warning-20.png +[available_icon]: https://cdn3.iconfinder.com/data/icons/flat-actions-icons-9/512/Tick_Mark-24.png diff --git a/docs/Firebase/PerformanceMonitoring/Details.md b/docs/Firebase/PerformanceMonitoring/Details.md new file mode 100755 index 00000000..b9c822a3 --- /dev/null +++ b/docs/Firebase/PerformanceMonitoring/Details.md @@ -0,0 +1,47 @@ +Firebase Performance Monitoring is a service that helps you to gain insight into the performance characteristics of your iOS and Android apps. You use the Performance Monitoring SDK to collect performance data from your app, and then review and analyze that data in the Firebase console. Performance Monitoring helps you to understand where and when the performance of your app can be improved so that you can use that information to fix performance issues. + +Performance Monitoring is currently in [beta release](https://support.google.com/firebase/answer/7011258). + +## Key capabilities + +| Capability | Description | +|-----------:|-------------| +| **Automatically measure app startup time, HTTP/S network requests, and more** | When you integrate the Performance Monitoring SDK into your iOS or Android app, you don't need to write any code before your app starts monitoring several critical aspects of app performance: startup time, activity while in the foreground, activity while in the background, and HTTP/S network requests. | +| **Gain insight into situations where app performance could be improved** | Optimizing the performance of your app can be challenging when you don't know exactly why it is falling short of user expectations. That's why Performance Monitoring lets you see performance metrics broken down by country, device, app version, and OS level. | +| **Customize Performance Monitoring for your app** | You can create traces to capture your app's performance in specific situations, like when you load a new screen. And, you can create counters to count events that you define (like cache hits) during those traces. | + +## How does it work? + +Performance Monitoring monitors traces and HTTP/S network requests in your app. + +A trace is a report of performance data captured between two points in time in your app. When installed, the Performance Monitoring SDK automatically provides app start traces, which measure the time between when the user opens the app and when the app is responsive. It also provides app in foreground traces and app in background traces to give you insight into how your app performs when in the foreground or when idle. To learn more about these types of traces, see [Firebase Performance Monitoring Automatic Traces](https://firebase.google.com/docs/perf-mon/automatic). + +You can also configure custom traces. A custom trace is a report of performance data associated with some of the code in your app. You define the beginning and end of a custom trace using the APIs provided by the Performance Monitoring SDK. A custom trace can be further configured to record counters for performance-related events that occur within its scope. For example, you could create a counter for the number of cache hits and misses or the number of times that the UI becomes unresponsive for a noticeable period of time. + +An HTTP/S network request is a report that captures the time between when your app issues a request to a service endpoint and when the response from that endpoint is complete. For any endpoint that your app makes a request to, the SDK will capture several metrics: + +* **Response time**: Time between when the request is made and when the response is fully received +* **Payload size**: Byte size of the network payload downloaded and uploaded by the app +* **Success rate**: Percentage of successful responses compared to total responses (to measure network or server failures) + +For both traces and HTTP/S network requests, you can see performance monitoring data categorized as follows: + +| Traces | HTTP/S network requests | +|--------|-------------------------| +| App version | App version | +| Country | Country | +| Device | Device | +| OS | OS | +| Radio | Radio | +| Carrier | Carrier | +| | MIME type | + +## User data + +Performance Monitoring does not permanently store any personally identifiable information (such as names, email addresses, or phone numbers). While monitoring HTTP/S network requests, Performance Monitoring uses URLs (not including URL parameters) to build aggregated and anonymous URL patterns that are eventually persisted and shown in the Firebase console. + +For a full list of data collected by Performance Monitoring, see [Data collection](https://support.google.com/firebase/answer/6383877?hl=en&ref_topic=6317497). + + + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/perf-mon/) to see original Firebase documentation._ \ No newline at end of file diff --git a/docs/Firebase/PerformanceMonitoring/GettingStarted.md b/docs/Firebase/PerformanceMonitoring/GettingStarted.md new file mode 100755 index 00000000..2e804883 --- /dev/null +++ b/docs/Firebase/PerformanceMonitoring/GettingStarted.md @@ -0,0 +1,323 @@ +# Firebase Performance Monitoring for iOS + +## Table of content + +- [Prerequisites](#prerequisites) +- [Add Firebase to your app](#add-firebase-to-your-app) +- [Configure Performance Monitoring in your app](#configure-performance-monitoring-in-your-app) + - [(Optional) Define a custom trace and one or more counters in your app](#optional-define-a-custom-trace-and-one-or-more-counters-in-your-app) + - [Check the Firebase console for Performance Monitoring results](#check-the-firebase-console-for-performance-monitoring-results) + - [Deploy your app and review results in the Firebase console](#deploy-your-app-and-review-results-in-the-firebase-console) +- [Automatic Traces](#automatic-traces) + - [Automatic trace definitions](#automatic-trace-definitions) +- [Disable the Firebase Performance Monitoring SDK](#disable-the-firebase-performance-monitoring-sdk) + - [Disable Performance Monitoring during your app build process](#disable-performance-monitoring-during-your-app-build-process) + - [Disable your app at runtime using Remote Config](#disable-your-app-at-runtime-using-remote-config) +- [Known issues](#known-issues) + +## Prerequisites + +Firebase Performance Monitoring requires iOS 8 or newer. + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][1], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][2]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][2] again at any time. + +## Configure Performance Monitoring in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Xamarin Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Add the following line of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` namespace): + +```csharp +App.Configure (); +``` + +Compile your app. Automatic traces and HTTP/S network requests are now monitored. + +### (Optional) Define a custom trace and one or more counters in your app + +A custom trace is a report of performance data associated with some of the code in your app. You can have multiple custom traces in your app, and it is possible to have more than one custom trace running at a time. Each custom trace can have one or more counters to count performance-related events in your app, and those counters are associated with the traces that create them. + +1. Add the Firebase Performance Monitoring namespace to your namespaces: + + ```csharp + using Firebase.PerformanceMonitoring; + ``` + +2. Just before the code where you want to start a trace in your app, add the following lines of code to start a trace called **test trace**: + + ```csharp + var trace = Performance.StartTrace ("test trace"); + ``` + +3. To count performance-related events that occur in your app (such as cache hits or retries), add a line of code similar to the following each time that the event occurs, using a string other than retry to name that event if you are counting a different type of event: + + ```csharp + trace.IncrementCounter ("retry"); + ``` + +4. Just after the code where you want to stop your trace, add the following line of code: + + ```csharp + trace.Stop (); + ``` + +### Check the Firebase console for Performance Monitoring results + +1. Run your app in the simulator or device. +2. Confirm that Performance Monitoring results appear in the Firebase console. Results should appear within 12 hours. + +### Deploy your app and review results in the Firebase console + +After you have validated Performance Monitoring using simulators and one or more test devices, you can deploy the updated version of your app to your users and use the Firebase console to monitor performance data. + +### (Optional) Add monitoring for specific network requests + +Performance Monitoring collects network requests automatically. Although this includes most network requests for your app, some might not be reported. To include specific network requests in Performance Monitoring, add the following code to your app: + +```csharp +var metric = new HttpMetric ("https://www.google.com", HttpMethod.Get); +metric.Start (); + +var url = new NSUrl ("https://www.google.com"); +var request = new NSUrlRequest (url); +var session = NSUrlSession.FromConfiguration (NSUrlSessionConfiguration.DefaultSessionConfiguration); + +var dataTask = session.CreateDataTask (request, HandleNSUrlSessionResponse); +dataTask.Resume (); + +void HandleNSUrlSessionResponse (NSData data, NSUrlResponse response, NSError error) +{ + if (response is NSHttpUrlResponse httpResponse) + metric.ResponseCode = httpResponse.StatusCode; + + metric.Stop (); +} + +// async/await way: + +var metric = new HttpMetric ("https://www.google.com", HttpMethod.Get); +metric.Start (); + +var url = new NSUrl ("https://www.google.com"); +var request = new NSUrlRequest (url); +var session = NSUrlSession.FromConfiguration (NSUrlSessionConfiguration.DefaultSessionConfiguration); + +try { + var dataTaskResult = await session.CreateDataTaskAsync (request); + + if (dataTaskResult.Response is NSHttpUrlResponse httpResponse) + metric.ResponseCode = httpResponse.StatusCode; + + metric.Stop (); +} catch (NSErrorException ex) { + // Handle error +} +``` + +The HTTP/s network requests you specifically capture this way appear in the Firebase console along with the network requests Performance Monitoring captures automatically. + +## Automatic Traces + +A trace is a report of performance data captured between two points in time in your app. When installed, the Performance Monitoring SDK automatically provides the following types of traces: + +* _App start_ traces, which measure the time between when the user opens the app and when the app is responsive. +* _App in background_ traces, which measure the time when the app is running in the background. +* _App in foreground_ traces, which measure the time when the app is running in the foreground and available to the user. + +### Automatic trace definitions + +Performance Monitoring uses method calls and notifications in your app to determine when each type of automatic trace starts and stops: + +| Trace Name | iOS | +|------------|-----| +| App start | Starts when the application loads the first **Object** to memory and stops after the first successful run loop that occurs after the application receives the **UIApplicationDidBecomeActiveNotification** notification. | +| App in background | Starts when the application receives the **UIApplicationWillResignActiveNotification** notification and stops when it receives the **UIApplicationDidBecomeActiveNotification** notification. | +| App in foreground | Starts when the application receives the **UIApplicationDidBecomeActiveNotification** notification and stops when it receives the **UIApplicationWillResignActiveNotification** notification. | + +## Monitor Custom Attributes + +In Firebase Performance Monitoring, you can use attributes to segment performance data and focus on your app's performance in different real-world scenarios. A variety of attributes are available out-of-the-box, including operating system information, country, carrier, device, and app version. In addition, you can also create custom attributes, to segment data by categories specific to your app. For example, in a game, you can segment data by game level. + +### Create custom attributes + +You can use custom attributes on specific traces. You're limited to 5 custom attributes per trace. + +To use custom attributes, add code to your app defining the attribute and applying it to a specific trace, like the following examples: + +```csharp +var trace = Firebase.PerformanceMonitoring.Performance.SharedInstance.GetTrace ("myTrace"); +trace.SetValue ("A", "experiment"); + +// Update scenario. +trace.SetValue ("B", "experiment"); + +// Reading scenario. +var experimentValue = trace.GetValue ("experiment"); + +// Delete scenario. +trace.RemoveAttribute ("experiment"); + +// Read attributes. +var attributes = trace.Attributes; +``` + +### Monitor custom attributes + +In the Firebase console, go to the *Traces* tab in the [Performance section][6]. Each of your custom attributes has a card showing performance data for that segment. You can also filter by custom attributes. + +## Disable the Firebase Performance Monitoring SDK + +To let you users opt-in or opt-out of using Firebase Performance Monitoring, you might want to configure your app so that you can enable and disable Performance Monitoring. You might also find this capability to be useful during app development and testing. + +You can disable the Performance Monitoring SDK when building your app with the option to re-enable it at runtime, or build your app with Performance Monitoring enabled and then have the option to disable it at runtime using [Firebase Remote Config][3]. You can also completely deactivate Performance Monitoring, with no option to enable it at runtime. + +### Disable Performance Monitoring during your app build process + +One situation where disabling Performance Monitoring during your app build process could be useful is to avoid reporting performance data from a pre-release version of your app during app development and testing. + +You can add one of two keys to the property list file (**Info.plist**) for your iOS app to disable or deactivate Performance Monitoring: + +* To disable Performance Monitoring, but allow your app to enable it at runtime, set `firebase_performance_collection_enabled` to `True` in your app's **Info.plist** file. +* To completely deactivate Performance Monitoring with no option to enable it at runtime, set `firebase_performance_collection_deactivated` to true in your app's **Info.plist** file. This setting overrides the `firebase_performance_collection_enabled` setting and must be removed from your app's **Info.plist** file to re-enable Performance Monitoring. + +### Disable your app at runtime using Remote Config + +Remote Config lets you make changes to the behavior and appearance of your app, so it provides an ideal way to let you disable Performance Monitoring in deployed instances of your app. + +You can use the example code shown below to disable Performance Monitoring data collection the next time that your iOS app starts. For more information about using Remote Config in an iOS app, see [Use Firebase Remote Config on iOS][4]. + +1. In your project solution in Visual Studio, add **Xamarin.Firebase.iOS.RemoteConfig** NuGet package. +2. In your `AppDelegate` file, add the `Firebase.RemoteConfig` namespace. + + ```csharp + using Firebase.RemoteConfig; + ``` + +3. In your `AppDelegate` file, add the following code in `FinishedLaunching` method: + + ```csharp + var remoteConfig = RemoteConfig.SharedInstance; + + // You can change the "false" below to "true" to permit more fetches when validating + // your app, but you should change it back to "false" or remove this statement before + // distributing your app in production. + remoteConfig.ConfigSettings = new RemoteConfigSettings (true); + + // You can set default parameter values using an NSDictionary object or a plist file. + var defaultPlist = NSBundle.MainBundle.PathForResource ("RemoteConfigDefaults", "plist"); + + if (defaultPlist != null) { + // Load in-app defaults from a plist file that sets perf_enable + // to true until you update values in the Firebase Console. + remoteConfig.SetDefaults ("RemoteConfigDefaults"); + } else { + // If the plist file doesn't exist, load the value by code. + object [] values = { true }; + object [] keys = { "perf_enable" }; + var defaultValues = NSDictionary.FromObjectsAndKeys (values, keys, keys.Length); + remoteConfig.SetDefaults (defaultValues); + } + + // Important! This needs to be applied before App.Configure() + var isPerformanceMonitoringInstrumentation = remoteConfig ["perf_enable"].BoolValue; + + // The following line enables/disables automatic traces and HTTP/S network monitoring + Performance.SharedInstance.InstrumentationEnabled = isPerformanceMonitoringInstrumentation; + + // The following line enables/disables custom traces + Performance.SharedInstance.DataCollectionEnabled = isPerformanceMonitoringInstrumentation; + + // Use Firebase library to configure APIs + App.Configure (); + ``` + +4. In your `ViewController` add the following code to fetch and activate Remote Config values: + + ```csharp + remoteConfig.Fetch (30, (status, error) => { + switch (status) { + case RemoteConfigFetchStatus.Success: + Console.WriteLine ("Config fetched"); + remoteConfig.ActivateFetched (); + break; + + case RemoteConfigFetchStatus.Throttled: + case RemoteConfigFetchStatus.NoFetchYet: + case RemoteConfigFetchStatus.Failure: + Console.WriteLine ("Config not fetched..."); + Console.WriteLine ($"Error: {error.LocalizedDescription}"); + break; + } + }); + ``` + +5. To disable Performance Monitoring in the Firebase console, create a `perf_enable` parameter in your app's project, and then set its value to `false`. If you set the value of `perf_enable` to `true`, Performance Monitoring will remain enabled. + +#### Disable automatic or manual data collection separately + +You could make some changes to the code shown above and in the Firebase console to let you disable automatic data collection (app start traces and HTTP/S network requests) separately from manual data collection (custom traces). To do this, you would add the following code to the `FinishedLaunching` method, instead of what is shown in step 3 above: + +```csharp +var remoteConfig = RemoteConfig.SharedInstance; +remoteConfig.ConfigSettings = new RemoteConfigSettings (true); + +var defaultPlist = NSBundle.MainBundle.PathForResource ("RemoteConfigDefaults", "plist"); + +if (defaultPlist != null) { + remoteConfig.SetDefaults ("RemoteConfigDefaults"); +} else { + // If the plist file doesn't exist, load the values by code. + object [] values = { true, true }; + object [] keys = { "perf_enable_auto", "perf_enable_manual" }; + var defaultValues = NSDictionary.FromObjectsAndKeys (values, keys, keys.Length); + remoteConfig.SetDefaults (defaultValues); +} + +// Important! This needs to be applied before App.Configure() +var isPerformanceMonitoringInstrumentationEnabled = remoteConfig ["perf_enable_auto"].BoolValue; +var isPerformanceMonitoringDataCollectionEnabled = remoteConfig ["perf_enable_manual"].BoolValue; + +// The following line enables/disables automatic traces and HTTP/S network monitoring +Performance.SharedInstance.InstrumentationEnabled = isPerformanceMonitoringInstrumentationEnabled; + +// The following line enables/disables custom traces +Performance.SharedInstance.DataCollectionEnabled = isPerformanceMonitoringDataCollectionEnabled; + +// Use Firebase library to configure APIs +App.Configure (); +``` + +Then, do the following in the Firebase console: + +* To disable automatic traces and HTTP/S network monitoring, create a `perf_enable_auto` parameter in your app's project, and then set its value to `false`. +* To disable custom traces, create a `perf_enable_manual` parameter in your app's project, and then set its value to `false`. + +To enable either of these aspects of Performance Monitoring in your app, set the value of the corresponding parameter to `true` in the Firebase console. + +## View Data in the Firebase Console + +To learn how to read your data in Firebase Console, please, read the following [documentation][7]. + +## Known issues + +* Performance Monitoring has known compatibility issues with GTMSQLite. We recommend not using Performance Monitoring with apps that use GTMSQLite. +* Performance Monitoring does not support network requests made using either `WebClient` or `HttpClient` class. +* Method swizzling after calling `App.Configure ()` might interfere with the Performance Monitoring SDK. +* Known issues with the iOS 8.0-8.2 Simulator prevent Performance Monitoring from capturing performance events. These issues are fixed in the iOS 8.3 Simulator and later versions. +* Connections established using `NSUrlSession`'s BackgroundSessionConfiguration will exhibit longer than expected connection times. These connections are executed out-of-process and the timings reflect in-process callback events. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/dynamic-links/ios) to see original Firebase documentation._ + +[1]: https://firebase.google.com/console/ +[2]: http://support.google.com/firebase/answer/7015592 +[3]: https://components.xamarin.com/view/firebaseiosremoteconfig +[4]: https://components.xamarin.com/gettingstarted/firebaseiosremoteconfig +[6]: https://console.firebase.google.com/project/_/performance/ +[7]: https://firebase.google.com/docs/perf-mon/help diff --git a/docs/Firebase/RemoteConfig/Details.md b/docs/Firebase/RemoteConfig/Details.md new file mode 100755 index 00000000..65f9411c --- /dev/null +++ b/docs/Firebase/RemoteConfig/Details.md @@ -0,0 +1,31 @@ +Change the behavior and appearance of your app without publishing an app update. + +Firebase Remote Config is a cloud service that lets you change the behavior and appearance of your app without requiring users to download an app update. When using Remote Config, you create in-app default values that control the behavior and appearance of your app. Then, you can later use the Firebase console to override in-app default values for all app users or for segments of your userbase. Your app controls when updates are applied, and it can frequently check for updates and apply them with a negligible impact on performance. + +## Key capabilities + +| | | +|-:|--| +| **Quickly roll out changes to your app's userbase** | You can make changes to your app's default behavior and appearance by changing server-side parameter values. For example, you could change your app's layout or color theme to support a seasonal promotion, with no need to publish an app update. | +| **Customize your app for segments of your userbase** | You can use Remote Config to provide variations on your app's user experience to different segments of your userbase by app version, by Firebase Analytics audience, by language, and more. | +| **Run A/B tests to improve your app** | You can use Remote Config random percentile targeting with Firebase Analytics to A/B test improvements to your app across different segments of your userbase so that you can validate improvements before rolling them out to your entire userbase. | + +## How does it work? + +Remote Config includes a client library that handles important tasks like fetching parameter values and caching them, while still giving you control over when new values are activated so that they affect your app's user experience. This lets you safeguard your app experience by controlling the timing of any changes. + +The Remote Config client library get methods provide a single access point for parameter values. Your app gets server-side values using the same logic it uses to get in-app default values, so you can add the capabilities of Remote Config to your app without writing a lot of code. + +To override in-app default values, you use the Firebase console to create parameters with the same names as the parameters used in your app. For each parameter, you can set a server-side default value to override the in-app default value, and you can also create conditional values to override the in-app default value for app instances that meet certain conditions. + +## Policies and limits + +Note the following policies: + +* Don't use Remote Config to make app updates that should require a user's authorization. This could cause your app to be perceived as untrustworthy. +* Don't store confidential data in Remote Config parameter keys or parameter values. It is possible to decode any parameter keys or values stored in the Remote Config settings for your project. +* Don't attempt to circumvent the requirements of your app's target platform using Remote Config. + +Remote Config parameters and conditions are subject to certain limits. To learn more, see [Limits on parameters and conditions](https://firebase.google.com/docs/remote-config/parameters#limits_on_parameters_and_conditions). + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/remote-config/) to see original Firebase documentation._ \ No newline at end of file diff --git a/docs/Firebase/RemoteConfig/GettingStarted.md b/docs/Firebase/RemoteConfig/GettingStarted.md new file mode 100755 index 00000000..11bb505a --- /dev/null +++ b/docs/Firebase/RemoteConfig/GettingStarted.md @@ -0,0 +1,190 @@ +# Use Firebase Remote Config on iOS + +You can use Firebase Remote Config to define parameters in your app and update their values in the cloud, allowing you to modify the appearance and behavior of your app without distributing an app update. + +## Table of content + +- [Remote Config Parameters and Conditions](#remote-config-parameters-and-conditions) +- [Firebase Remote Config API Overview](#firebase-remote-config-api-overview) + - [Key Features of the Remote Config APIs](#key-features-of-the-remote-config-apis) + - [Remote Config library](#remote-config-library) + - [API architecture](#api-architecture) +- [Add Firebase to your app](#add-firebase-to-your-app) +- [Configure Remote Config in your app](#configure-remote-config-in-your-app) +- [Add Settings to Remote Config class](#add-settings-to-remote-config-class) +- [Set in-app default parameter values](#set-in-app-default-parameter-values) + - [Set in-app default parameter values with .plist file](#set-in-app-default-parameter-values-with-plist-file) + - [Set in-app default parameter values with a NSDictionary](#set-in-app-default-parameter-values-with-a-nsdictionary) +- [Set parameter values in Firebase Console](#set-parameter-values-in-firebase-console) +- [Fetch and activate values from the server](#fetch-and-activate-values-from-the-server) +- [Use parameter values](#use-parameter-values) +- [Caching and throttling](#caching-and-throttling) + +## Remote Config Parameters and Conditions + +Please, read this [Firebase documentation][5] to learn about Parameters and Conditions. + +## Firebase Remote Config API Overview + +Firebase Remote Config has APIs that make it easy to change the behavior and appearance of your app without requiring users to download an app update. This overview describes the following: + +* Key features of the Remote Config APIs. +* The Remote Config library and API architecture. + +### Key Features of the Remote Config APIs + +Remote Config APIs implement the following features: + +* **Your app controls when new parameter values are applied:** Because changes to parameter values affect the behavior and appearance of your app, the API design implements a singleton object that fetches values in the background, caches them, and then lets your app activate them at the right time. +* **In-app default parameter values:** You set in-app default values for all Remote Config parameters in your app. These values are available to your app immediately, even if a device does not have connectivity. You get fetched and activated values using the same methods that you use to get in-app default values. +* **Fetching and applying values is efficient:** Fetching and activating values from the Remote Config Server is efficient and can be done safely and repeatedly, so there is no need to add logic to your app that listens for a callback or that determines if it is safe to activate fetched values. In fact, you can write your app so that it sends a request to fetch parameter values and activates any previously fetched parameter values each time that a user starts your app, or even more frequently than that. If no fetched and activated values are available, your app will use in-app default values with a negligible impact on performance from the fetch request or the call to `ActivateFetched`. + +### Remote Config library + +The cornerstone of the Remote Config API architecture is the Remote Config Library. The Remote Config Library implements a singleton class, the `RemoteConfig` class. Use the Remote Config object to do the following: + +* **Set default values:** You don't need to manage (or even create) parameters in the Remote Config service for your app to work as intended. You can instrument your app with as many Remote Config parameters as you need and create in-app default values. Later, you can override a subset of your app's parameters by creating parameters on the Remote Config Server. +* **Fetch, store, and manage parameter values:** The Remote Config object contains three stores of parameter values: the **Default Config** (stores in-app default values), the **Active Config** (stores values that are available to the app using get methods), and the **Fetched Config** (stores values most recently fetched from the Remote Config Server). +* **Activate the Fetched Config, which updates the Active Config:** When the Fetched Config is activated, the parameter values in the Fetched Config are copied to the Active Config. This makes the recently fetched values available to your app. + +### API architecture + +The following diagram shows how your app interacts with Remote Config: + +![Firebase_Remote_Config_API_Architecture](https://firebase.google.com/docs/remote-config/images/api-use.png) + +The following table provides additional details on interactions between your app and the Remote Config Library: + +| Methods and properties | Notes | +|------------------------|-------| +| Get Remote Config object property: `SharedInstance` | Step #1: Your app calls this property to get the Remote Config object (or have it recovered from persistent storage). If the object was newly created, the Fetched Config, the Active Config, and the Default Config are initially "empty," containing no parameter values. | +| Set Default Config method: `SetDefaults` | Step #2: Your app calls this method to set values in the Default Config. If your app attempts to get a value from a new Remote Config object before that value exists in the Active Config, the value from the Default Config is provided instead. | +| Fetch method: `Fetch` | Your app uses this method to initiate a call to the Remote Config Server and obtain fresh parameter values, which are stored in the Fetched Config.
**Note:** Fetch method do not have an immediate effect on the behavior or appearance of your app. | +| Activate method: `ActivateFetched` | Your app activates the Fetched Config, which copies values stored there to the Active Config. | +| Get\ method: `GetConfigValue` | Your app calls this method to get parameter values from the Active Config. | +| Config settings method: `RemoteConfigSettings`' constructor | Used for custom settings. Currently only used for settings that allow app developers to refresh app data more quickly than is allowed for production apps. | +| Info methods and properties: `LastFetchTime` | Your app uses this property to get information about the Remote Config object. You can use these methods for debugging during app development. | + +> _**Note:**_ _Some Remote Config methods allow you to specify a namespace. These methods are reserved for future use. You do not need to specify a namespace to use Remote Config._ + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][1], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][2]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][2] again at any time. + +## Configure Remote Config in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Xamarin Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Add the following line of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` namespace): + +```csharp +App.Configure (); +``` + +## Add Settings to Remote Config class + +Create settings for `RemoteConfig` class, you can enable developer mode to allow for frequent refreshes of the cache (don't forget to import `Firebase.RemoteConfig` namespace): + +```csharp +// Enabling developer mode, allows for frequent refreshes of the cache +RemoteConfig.SharedInstance.ConfigSettings = new RemoteConfigSettings (true); +``` + +## Set in-app default parameter values + +You can set in-app default parameter values in the Remote Config object, so that your app behaves as intended before it connects to the Remote Config Server, and so that default values are available if none are set on the server. You can achieve this with a **.plist** file or with an **NSDictionary** variable. + +### Set in-app default parameter values with .plist file + +In your Xamarin Studio app project do the following steps to add default parameters with .plist file: + +1. In your project app name do Right click/Add/New File... +2. In **New File** dialog, select iOS tab and choose Property List +3. Name the Property List as you want and click **New** +4. Change the **Build ACtion** of your just created file to **BundleResource** by Right clicking it/Build Action +5. Open your **.plist** file and add all the parameters that you want. + +After you have setup all your default parameters, set the **.plist** to the `RemoteConfig` class: + +```csharp +RemoteConfig.SharedInstance.SetDefaults (""); +``` + +### Set in-app default parameter values with a NSDictionary + +Instead of using a **.plist file** to load default parameter values to your app, you can create a `NSDictionary` to load them: + +```csharp +object [] values = { 5, 20 }; +object [] keys = { "times_table", "from_zero_to" }; +var defaultValues = NSDictionary.FromObjectsAndKeys (values, keys, keys.Length); +RemoteConfig.SharedInstance.SetDefaults (defaultValues); +``` + +## Set parameter values in Firebase Console + +1. In the [Firebase console][1], open your project. +2. Select **Remote Config** from the menu to view the Remote Config dashboard. +3. Define parameters with the same names as the parameters that you defined in your **.plist** file or in your **NSDictionary**. For each parameter, you can set a default value (which will eventually override the in-app default value) and you can also set conditional values. To learn more, see [Remote Config Parameters and Conditions][3]. + +## Fetch and activate values from the server + +After you have setup your parameter values in your app and in your server, you are ready to fetch those values in your app from the server so you can override your local values. Call `RemoteConfig.Fetch` instance method to retrieve values from server and call `RemoteConfig.ActivateFetched` instance method to make fetched parameter values available to your app: + +```csharp +// CacheExpirationSeconds is set to CacheExpiration here, indicating that any previously +// fetched and cached config would be considered expired because it would have been fetched +// more than CacheExpiration seconds ago. Thus the next fetch would go to the server unless +// throttling is in progress. The default expiration duration is 43200 (12 hours). +RemoteConfig.SharedInstance.Fetch (10, (status, error) => { + switch (status) { + case RemoteConfigFetchStatus.Success: + Console.WriteLine ("Config Fetched!"); + + // Call this method to make fetched parameter values available to your app + RemoteConfig.SharedInstance.ActivateFetched (); + + // Update your UI from here + ... + break; + + case RemoteConfigFetchStatus.Throttled: + case RemoteConfigFetchStatus.NoFetchYet: + case RemoteConfigFetchStatus.Failure: + Console.WriteLine ("Config not fetched..."); + break; + } +}); +``` + +## Use parameter values + +The way you can use parameter values in your app is by calling `RemoteConfig.GetConfigValue` instance method or using `RemoteConfig` indexer method: + +```csharp +var myValue = RemoteConfig.SharedInstance ["myKey"].NumberValue; +var myOtherValue = RemoteConfig.SharedInstance.GetConfigValue ("myOtherKey").StringValue; +``` + +## Caching and throttling + +Remote Config caches values locally after the first successful request. By default the cache expires after 12 hours, but you can change the cache expiration for a specific request by passing the desired cache expiration, in seconds, to `Fetch` method. If the values in the cache are older than the desired cache expiration, Remote Config will request fresh config values from the server. If your app requests fresh values using `Fetch` several times, requests are throttled and your app is provided with a cached value. + +During app development, you might want to refresh the cache very frequently (many times per hour) to let you rapidly iterate as you develop and test your app. To accommodate rapid iteration on a project with up to 10 developers, you can temporarily add a `RemoteConfigSettings` property with `IsDeveloperModeEnabled` set to true to your app, changing the caching settings of the `RemoteConfig` object. + +## Use Firebase Remote Config with Analytics + +When you build an app that includes both Firebase Remote Config and Google Analytics for Firebase, you gain the ability to understand your app users better and to respond to their needs more quickly. Read this [Firebase documentation][6] to learn more about this. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/remote-config/use-config-ios) to see original Firebase documentation._ + +[1]: https://firebase.google.com/console/ +[2]: http://support.google.com/firebase/answer/7015592 +[3]: https://firebase.google.com/docs/remote-config/parameters +[5]: https://firebase.google.com/docs/remote-config/parameters +[6]: https://firebase.google.com/docs/remote-config/config-analytics \ No newline at end of file diff --git a/docs/Firebase/Storage/Details.md b/docs/Firebase/Storage/Details.md new file mode 100755 index 00000000..c5c3bf7f --- /dev/null +++ b/docs/Firebase/Storage/Details.md @@ -0,0 +1,27 @@ +Firebase Storage is built for app developers who need to store and serve user-generated content, such as photos or videos. + +Firebase Storage is a powerful, simple, and cost-effective object storage service built for Google scale. Firebase Storage adds Google security to file uploads and downloads for your Firebase apps, regardless of network quality. You can use it to store images, audio, video, or other user-generated content. Firebase Storage is backed by Google Cloud Storage, a powerful, simple, and cost-effective object storage service. + +## Key capabilities + +| | | +|-:|--| +| **Robust operations** | Firebase Storage performs uploads and downloads regardless of network quality. Uploads and downloads are robust, meaning they restart where they stopped, saving your users time and bandwidth. | +| **Strong security** | Firebase Storage integrates with Firebase Authentication to provide simple and intuitive authentication for developers. You can use our declarative security model to allow access based on filename, size, content type, and other metadata. | +| **High scalability** | Firebase Storage is built for exabyte scale when your app goes viral. Effortlessly grow from prototype to production using the same infrastructure that powers Spotify and Google Photos. | + +## How does it work? + +Developers use the Firebase Storage SDK to upload and download files directly from clients. If the network connection is poor, the client is able to retry the operation right where it left off, saving your users time and bandwidth. + +Firebase Storage stores your files in a [Google Cloud Storage][1] bucket, making them accessible through both Firebase and Google Cloud APIs. This allows you the flexibility to upload and download files from mobile clients via Firebase and do server-side processing such as image filtering or video transcoding using [Google Cloud Platform][2]. Firebase Storage scales automatically, meaning that there's no need to migrate from Firebase Storage to Google Cloud Storage or any other provider. Learn more about all the benefits of our [integration with Google Cloud Platform][3]. + +Firebase Storage integrates seamlessly with [Firebase Authentication][4] to identify users, and provides a [declarative security language][5] that lets you set access controls on individual files or groups of files, so you can make files as public or private as you want. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/storage/) to see original Firebase documentation._ + +[1]: https://cloud.google.com/storage +[2]: https://cloud.google.com/ +[3]: https://firebase.google.com/docs/storage/gcp-integration +[4]: https://components.xamarin.com/view/firebaseiosauth +[5]: https://firebase.google.com/docs/storage/security/start diff --git a/docs/Firebase/Storage/GettingStarted.md b/docs/Firebase/Storage/GettingStarted.md new file mode 100755 index 00000000..c852ec3b --- /dev/null +++ b/docs/Firebase/Storage/GettingStarted.md @@ -0,0 +1,810 @@ +# Get Started + +Firebase Storage lets you upload and share user generated content, such as images and video, which allows you to build rich media content into your apps. Firebase Storage stores this data in a [Google Cloud Storage][1] bucket, an exabyte scale object storage solution with high availability and global redundancy. Firebase Storage lets you securely upload these files directly from mobile devices and web browsers, handling spotty networks with ease. + +## Table of content + +- [Get Started](#get-started) + - [Table of content](#table-of-content) + - [Add Firebase to your app](#add-firebase-to-your-app) + - [Configure Storage in your app](#configure-storage-in-your-app) + - [Recommended documentation to get a better understanding of the Security & Rules of Firebase Storage](#recommended-documentation-to-get-a-better-understanding-of-the-security-rules-of-firebase-storage) + - [Set up public access](#set-up-public-access) + - [Advanced setup](#advanced-setup) + - [Use multiple storage buckets](#use-multiple-storage-buckets) + - [Working with imported buckets](#working-with-imported-buckets) + - [Use a custom Firebase App](#use-a-custom-firebase-app) +- [Create a Storage Reference](#create-a-storage-reference) + - [Create a Reference](#create-a-reference) + - [Navigate with References](#navigate-with-references) + - [Reference Properties](#reference-properties) + - [Limitations on References](#limitations-on-references) +- [Upload Files on iOS](#upload-files-on-ios) + - [Create a Reference](#create-a-reference) + - [Upload Files](#upload-files) + - [Upload from data in memory](#upload-from-data-in-memory) + - [Upload from a local file](#upload-from-a-local-file) + - [Add File Metadata](#add-file-metadata) +- [Download Files on iOS](#download-files-on-ios) + - [Create a Reference](#create-a-reference) + - [Download Files](#download-files) + - [Download in memory](#download-in-memory) + - [Download to a local file](#download-to-a-local-file) + - [Generate a download Url](#generate-a-download-url) +- [Manage Uploads and Downloads](#manage-uploads-and-downloads) + - [Monitor Upload and Download Progress](#monitor-upload-and-download-progress) + - [Error Handling](#error-handling) +- [Use File Metadata on iOS](#use-file-metadata-on-ios) + - [Get File Metadata](#get-file-metadata) + - [Update File Metadata](#update-file-metadata) + - [Error Handling](#error-handling) + - [Custom Metadata](#custom-metadata) + - [File Metadata Properties](#file-metadata-properties) +- [Delete Files on iOS](#delete-files-on-ios) + - [Delete a File](#delete-a-file) +- [Handle Errors](#handle-errors) + - [Handle Error Messages](#handle-error-messages) +- [Extend Cloud Storage with Cloud Functions](#extend-cloud-storage-with-cloud-functions) +- [Integrate with Google Cloud Platform](#integrate-with-google-cloud-platform) + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][2], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][3]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][3] again at any time. + +## Configure Storage in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Xamarin Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Add the following line of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` namespace): + +```csharp +App.Configure (); +``` + +## Recommended documentation to get a better understanding of the Security & Rules of Firebase Storage + +Before you continue, I invite you to read the following docs to make your coding easier: + +* [Understand Security][4] +* [Get Started][5] +* [Secure Files][6] +* [User Based Security][7] + +## Set up public access + +Cloud Storage for Firebase provides a declarative rules language that allows you to define how your data should be structured, how it should be indexed, and when your data can be read from and written to. By default, read and write access to Storage is restricted so only authenticated users can read or write data. To get started without setting up [Authentication][8], you can [configure your rules for public access][9]. + +This does make Storage open to anyone, even people not using your app, so be sure to restrict your Storage again when you set up authentication. + +## Advanced setup + +There are a few use cases that require additional setup: + +Using storage buckets in [multiple geographic regions][10] +Using storage buckets in [different storage classes][11] +Using storage buckets with multiple authenticated users in the same app + +The first use case is perfect if you have users across the world, and want to store their data near them. For instance, you can create buckets in the US, Europe, and Asia to store data for users in those regions to reduce latency. + +The second use case is helpful if you have data with different access patterns. For instance: you can set up a multi-regional or regional bucket that stores pictures or other frequently accessed content, and a nearline or coldline bucket that stores user backups or other infrequently accessed content. + +In either of these use cases, you'll want to [use multiple storage buckets](#use-multiple-storage-buckets). + +The third use case is useful if you're building an app, like Google Drive, which lets users have multiple logged in accounts (for instance, a personal account and a work account). You can [use a custom Firebase App](#use-a-custom-firebase-app) instance to authenticate each additional account. + +### Use multiple storage buckets + +If you want to use a storage bucket other than the default provided above, or use multiple storage buckets in a single app, you can create an instance of `Storage` that references your custom bucket: + +```csharp +// Get a non-default Storage bucket +var storage = Storage.From ("gs://my-custom-bucket"); +``` + +### Working with imported buckets + +When importing an existing Cloud Storage bucket into Firebase, you'll have to grant Firebase the ability to access these files using the `gsutil` tool, included in the [Google Cloud SDK][12]: + +``` +gsutil -m acl ch -r -u firebase-storage@system.gserviceaccount.com:O gs:// +``` + +This does not affect newly created buckets, as those have the default access control set to allow Firebase. This is a temporary measure, and will be performed automatically in the future. + +### Use a custom Firebase App + +If you're building a more complicated app using a custom `FirebaseApp`, you can create an instance of Storage initialized with that app: + +```csharp +// Get the default bucket from a custom FirebaseApp +storage = Storage.From (customApp); + +// Get a non-default bucket from a custom FirebaseApp +storage = Storage.From (customApp, "gs://my-custom-bucket"); +``` + +--- + +# Create a Storage Reference + +Your files are stored in a [Google Cloud Storage][1] bucket. The files in this bucket are presented in a hierarchical structure, just like the file system on your local hard disk, or the data in the Firebase Database. By creating a reference to a file, your app gains access to it. These references can then be used to upload or download data, get or update metadata or delete the file. A reference can either point to a specific file or to a higher level in the hierarchy. + +## Create a Reference + +Create a reference to upload, download, or delete a file, or to get or update its metadata. A reference can be thought of as a pointer to a file in the cloud. References are lightweight, so you can create as many as you need. They are also reusable for multiple operations. + +References are created from the storage service on your Firebase app by calling the `GetReferenceFromUrl` method and passing in a URL of the form **gs://\**. You can find this URL in the Storage section of the [Firebase console][2]. + +```csharp +// Get a reference to the storage service, using the default Firebase App +var storage = Storage.DefaultInstance; + +// Create a storage reference from our storage service +StorageReference rootRef = storage.GetReferenceFromUrl ("gs://") + +// This is the same result as above +StorageReference rootRef = storage.GetRootReference (); +``` + +You can create a reference to a location lower in the tree, say **images/space.jpg**, by using the `GetChild` method on an existing reference: + +```csharp +// Create a child reference +// imagesRef now points to "images" ("gs:///images") +StorageReference imagesRef = rootRef.GetChild ("images"); + +// Child references can also take paths delimited by '/' +// spaceRef now points to "images/space.jpg" ("gs:///images/space.jpg") +// imagesRef still points to "images" +StorageReference spaceRef = rootRef.GetChild ("images/space.jpg"); + +// This is equivalent to creating the full reference +StorageReference spaceRef = storage.GetReferenceFromUrl ("gs:///images/space.jpg"); +``` + +## Navigate with References + +You can also use the `Parent` and `Root` properties to navigate up in our file hierarchy. `Parent` navigates up one level, while `Root` navigates all the way to the top. + +```csharp +// Parent allows us to move to the parent of a reference +// imagesRef now points to 'images' +StorageReference imagesRef = spaceRef.Parent; + +// Root allows us to move all the way back to the top of our bucket +// rootRef now points to the root +StorageReference *rootRef = spaceRef.Parent; +``` + +`GetChild` method, `Parent` and `Root` properties can be chained together multiple times, as each returns a reference. The exception is the `Parent` of `rootRef`, which is `null`: + +```csharp +// References can be chained together multiple times +// earthRef points to "images/earth.jpg" +StorageReference earthRef = spaceRef.Parent.GetChild ("earth"); + +// nullRef is null, since the Parent of Root is null +StorageReference nullRef = spaceRef.Root.Parent; +``` + +## Reference Properties + +You can inspect references to better understand the files they point to using the `FullPath`, `Name`, and `Bucket` properties. These properties get the file's full path, name, and bucket: + +```csharp +// Reference's path is: "images/space.jpg" +// This is analogous to a file path on disk +spaceRef.FullPath; + +// Reference's name is the last segment of the full path: "space.jpg" +// This is analogous to the file name +spaceRef.Name; + +// Reference's bucket is the name of the storage bucket where files are stored +spaceRef.Bucket; +``` + +## Limitations on References + +Reference paths and names can contain any sequence of valid Unicode characters, but certain restrictions are imposed including: + +1. Total length of reference.FullPath must be between 1 and 1024 bytes when UTF-8 encoded. +2. No Carriage Return or Line Feed characters. +3. Avoid using **#**, **[**, **]**, **\***, or **?**, as these do not work well with other tools such as the Firebase Database or [gsutil][13]. + +--- + +# Upload Files on iOS + +Firebase Storage allows developers to quickly and easily upload files to a [Google Cloud Storage][1] bucket provided and managed by Firebase. + +> ![note_icon] **_Note_**: _By default, Firebase Storage buckets require Firebase Authentication to upload files. You can [change your Firebase Storage Security Rules][9] to allow unauthenticated access. Since the default Google App Engine app and Firebase share this bucket, configuring public access may make newly uploaded App Engine files publicly accessible as well. Be sure to restrict access to your Storage bucket again when you set up authentication._ + +## Create a Reference + +To upload a file, first create a Firebase Storage reference to the location in Firebase Storage you want to upload the file to. + +You can create a reference by appending child paths to the storage root: + +```csharp +// Create a root reference +StorageReference rootRef = storage.GetRootReference (); + +// Create a reference to "mountains.jpg" +StorageReference mountainsRef = root.GetChild ("mountains.jpg"); + +// Create a reference to 'images/mountains.jpg' +StorageReference mountainImagesRef = root.GetChild ("images/mountains.jpg"); + +// While the file names are the same, the references point to different files +mountainsRef.Name == mountainImagesRef.Name; // true +mountainsRef.FullPath == mountainImagesRef.FullPath; // false +``` + +You **cannot upload data** with a reference to the root of your Google Cloud Storage bucket. Your reference must point to a child URL. + +## Upload Files + +Once you have a reference, you can upload files to Firebase Storage in two ways: + +1. Upload from `NSData` in memory. +2. Upload from an `NSUrl` representing a file on device. + +### Upload from data in memory + +The `PutData` method is the simplest way to upload a file to Firebase Storage. `PutData` takes an `NSData` object and returns a `StorageUploadTask`, which you can use to manage your upload and monitor its status: + +```csharp +// Data in memory +NSData data = ... + +// Create a reference to the file you want to upload +StorageReference riversRef = rootRef.GetChild ("images/rivers.jpg"); + +// Upload the file to the path "images/rivers.jpg" +StorageUploadTask uploadTask = riversRef.PutData (data, null, HandleStorageGetPutUpdateCompletion); + +void HandleStorageGetPutUpdateCompletion (StorageMetadata metadata, NSError error) +{ + if (error != null) { + // Uh-oh, an error occurred! + return; + } + + // Metadata contains file metadata such as size, content-type, and download URL. + var downloadUrl = metadata.DownloadUrl; +} +``` + +### Upload from a local file + +You can upload local files on the devices, such as photos and videos from the camera, with the `PutFile` method. `PutFile` takes an `NSUrl` and returns a `StorageUploadTask`, which you can use to manage your upload and monitor its status: + +```csharp +// Data in memory +NSUrl localFile = ... + +// Create a reference to the file you want to upload +StorageReference riversRef = rootRef.GetChild ("images/rivers.jpg"); + +// Upload the file to the path "images/rivers.jpg" +StorageUploadTask uploadTask = riversRef.PutFile (localFile, null, HandleStorageGetPutUpdateCompletion); + +void HandleStorageGetPutUpdateCompletion (StorageMetadata metadata, NSError error) +{ + if (error != null) { + // Uh-oh, an error occurred! + return; + } + + // Metadata contains file metadata such as size, content-type, and download URL. + var downloadUrl = metadata.DownloadUrl; +} +``` + +If you want to actively manage your upload, you can use the `PutData` or `PutFile` methods and observe the upload task, rather than using the completion handler. See [Manage Uploads and Downloads](#manage-uploads-and-downloads) section below for more information. + +## Add File Metadata + +You can also include metadata when you upload files. This metadata contains typical file metadata properties such as `Name`, `Size`, and `ContentType` (commonly referred to as MIME type). The `PutFile` method automatically infers the content type from the `NSUrl` filename extension, but you can override the auto-detected type by specifying `ContentType` in the metadata. If you do not provide a `ContentType` and Cloud Storage cannot infer a default from the file extension, Firebase Storage uses **application/octet-stream**. See the [Use File Metadata](#use-file-metadata) section below for more information about file metadata. + +```csharp +// Create storage reference +StorageReference mountainsRef = rootRef.GetChild ("images/mountains.jpg"); + +// Create file metadata including the content type +var imageMetadata = new StorageMetadata { ContentType = "image/jpeg" }; + +// Upload data and metadata +StorageUploadTask uploadTask = mountainsRef.PutData (data, metadata); + +// Upload file and metadata +StorageUploadTask uploadTask = mountainsRef.PutFile (localFile, metadata); +``` + +--- + +# Download Files on iOS + +Firebase Storage allows developers to quickly and easily download files from a [Google Cloud Storage][1] bucket provided and managed by Firebase. + +> ![note_icon] **_Note_**: _By default, Cloud Storage buckets require Firebase Authentication to download files. You can change your [Firebase Storage Security Rules][9] to allow unauthenticated access. Since the default Google App Engine app and Firebase share this bucket, configuring public access may make newly uploaded App Engine files publicly accessible as well. Be sure to restrict access to your Storage bucket again when you set up authentication._ + +## Create a Reference + +To download a file, first create a Firebase Storage reference to the file you want to download. + +You can create a reference by appending child paths to the storage root, or you can create a reference from an existing **gs://** or **https://** URL referencing an object in Cloud Storage: + +```csharp +// Create a reference with an initial file path and name +var pathRef = storage.GetReferenceFromPath ("images/stars.jpg"); + +// Create a reference from a Google Cloud Storage URI +var gsRef = storage.GetReferenceFromUrl ("gs:///images/stars.jpg"); + +// Create a reference from an HTTPS URL +// Note that in the URL, characters are URL escaped! +var httpRef = storage.GetReferenceFromUrl ("https://firebasestorage.googleapis.com/b/bucket/o/images%20stars.jpg"); +``` + +## Download Files + +Once you have a reference, you can download files from Firebase Storage in three ways: + +1. Download to `NSData` in memory. +2. Download to an `NSUrl` representing a file on device. +3. Generate an `NSUrl` representing the file online. + +### Download in memory + +Download the file to an `NSData` object in memory using the `GetData` method. This is the easiest way to quickly download a file, but it must load entire contents of your file into memory. If you request a file larger than your app's available memory, your app will crash. To protect against memory issues, make sure to set the max size to something you know your app can handle, or use another download method: + +```csharp +// Create a reference to the file you want to download +StorageReference islandRef = rootRef.GetChild ("images/island.jpg"); + +// Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes) +islandRef.GetData (1 * 1024 * 1024, HandleStorageGetDataCompletion); + +void HandleStorageGetDataCompletion (NSData data, NSError error) +{ + if (error != nil) { + // Uh-oh, an error occurred! + return; + } + + // Data for "images/island.jpg" is returned + var islandImage = UIImage.LoadFromData (data); +} +``` + +### Download to a local file + +The `WriteToFile` method downloads a file directly to a local device. Use this if your users want to have access to the file while offline or to share in a different app. `WriteToFile` returns an `StorageDownloadTask` which you can use to manage your download and monitor the status of the upload: + +```csharp +// Create a reference to the file you want to download +StorageReference islandRef = rootRef.GetChild ("images/island.jpg"); + +// Create local filesystem Url +var localUrl = NSUrl.FromString ("path/to/images/island.jpg"); + +// Download to the local filesystem +StorageDownloadTask downloadTask = islandRef.WriteToFile (localUrl, HandleStorageWriteToFileCompletion); + +void HandleStorageWriteToFileCompletion (NSUrl url, NSError error) +{ + if (error != null) { + // Uh-oh, an error occurred! + return; + } + + // Local file Url for "images/island.jpg" is returned +} +``` + +If you want to actively manage your download, you can use the `WriteToFile` method and observe the download task, rather than use the completion handler. See [Manage Uploads and Downloads](#manage-uploads-and-downloads) section below for more information. + +### Generate a download Url + +If you already have download infrastructure based around Urls, or just want a Url to share, you can get the download Url for a file by calling the `GetDownloadUrl` method on a storage reference: + +```csharp +// Create a reference to the file you want to download +StorageReference starsRef = rootRef.GetChild ("images/stars.jpg"); + +// Fetch the download Url +starsRef.GetDownloadUrl (HandleStorageDownloadUrlCompletion); + +void HandleStorageDownloadUrlCompletion (NSUrl url, NSError error) +{ + if (error != null) { + // Handle any errors + return; + } + + // Get the download URL for 'images/stars.jpg' +} +``` + +An `async`/`await` version of this: + +```csharp +// Create a reference to the file you want to download +StorageReference starsRef = rootRef.GetChild ("images/stars.jpg"); + +try { + NSUrl url = await starsRef.GetDownloadUrlAsync (); + + // Get the download URL for 'images/stars.jpg' +} catch (NSErrorException ex) { + // Handle any errors +} +``` + +--- + +# Manage Uploads and Downloads + +In addition to starting uploads and downloads, you can pause, resume, and cancel uploads and downloads, using the `Pause`, `Resume`, and `Cancel` methods. These methods raise pause, resume, and cancel events that you can observe. Canceling an upload causes the upload to fail with an error indicating that the upload was canceled: + +```csharp +// Start uploading/downloading a file +StorageUploadTask uploadTask = mountainsRef.PutFile (localFile, metadata); +StorageDownloadTask downloadTask = islandRef.WriteToFile (localUrl); + +// Pause the upload/download +uploadTask.Pause (); +downloadTask.Pause (); + +// Resume the upload/download +uploadTask.Resume (); +downloadTask.Resume (); + +// Cancel the upload/download +uploadTask.Cancel (); +downloadTask.Cancel (); +``` + +## Monitor Upload and Download Progress + +You can attach observers to `StorageUploadTask` in order to monitor the progress of the upload. Adding an observer returns a `string` that can be used to remove the observer: + +```csharp +// Add a progress observer to an upload task +string observer = uploadTask.ObserveStatus (StorageTaskStatus.Progress, (snapshot) => { + // A progress event occurred +}); + +// Add a progress observer to a download task +string observer = downloadTask.ObserveStatus (StorageTaskStatus.Progress, (snapshot) => { + // A progress event occurred +}); +``` + +These observers can be added to an `StorageTaskStatus` event: + +| StorageTaskStatus Event | Typical Usage | +|:------------------------------:|---------------| +| **StorageTaskStatus.Resume** | This event fires when the task starts or resumes uploading/downloading, and is often used in conjunction with the `StorageTaskStatus.Pause` event. | +| **StorageTaskStatus.Progress** | This event fires any time data is uploading/downloading to Firebase Storage, and can be used to populate an upload progress indicator. | +| **StorageTaskStatus.Pause** | This event fires any time the upload/download is paused, and is often used in conjunction with the `StorageTaskStatus.Resume` event. | +| **StorageTaskStatus.Success** | This event fires when a upload/download has completed sucessfully. | +| **StorageTaskStatus.Failure** | This event fires when a upload/download has failed. Inspect the error to determine the failure reason. | + +When an event occurs, an `StorageTaskSnapshot` object is passed back. This snapshot is an immutable view of the task, at the time the event occurred. This object contains the following properties/methods: + +| Name | Kind | Type | Description | +|:----------------:|:------------:|:----------------------------------------------:|-------------| +| **Progress** | **Property** | **NSProgress** | An `NSProgress` object containing the progress of the upload/download. | +| **Error** | **Property** | **NSError** | An error that occurred during upload/download, if any. | +| **Metadata** | **Property** | **StorageMetadata** | During upload contains metadata being uploaded. After an `StorageTaskStatus.Success` event, contains the uploaded file's metadata. `null` on downloads. | +| **GetTask\** | **Method** | **StorageUploadTask/
StorageDownloadTask** | The task this is a snapshot of, which can be used to manage (pause, resume, cancel) the task. | +| **Reference** | **Property** | **StorageReference** | The reference this task came from. | + +You can also remove observers, either individually, by status, or by removing all of them: + +```csharp +// Create a task listener handle +string observer = ... + +// Remove an individual observer +uploadTask.RemoveObserver (observer); +downloadTask.RemoveObserver (observer); + +// Remove all observers of a particular status +uploadTask.RemoveAllObservers (StorageTaskStatus.Progress); +downloadTask.RemoveAllObservers (StorageTaskStatus.Progress); + +// Remove all observers +uploadTask.RemoveAllObservers (); +downloadTask.RemoveAllObservers (); +``` + +To prevent memory leaks, all observers are removed after an `StorageTaskStatus.Success` or `StorageTaskStatus.Failure` occurs. + +## Error Handling + +There are a number of reasons why errors may occur on upload, including the local file not existing, or the user not having permission to upload the desired file. You can find more information about errors in the [Handle Errors](#handle-errors) section below of the docs. + +--- + +# Use File Metadata on iOS + +After uploading a file to Firebase Storage reference, you can also get and update the file metadata, for example to update the content type. Files can also store custom key/value pairs with additional file metadata. + +> ![note_icon] **_Note_**: _By default, Cloud Storage buckets require Firebase Authentication to get and update metadata. You can change your [Firebase Storage Security Rules][9] to allow unauthenticated access. Since the default Google App Engine app and Firebase share this bucket, configuring public access may make newly uploaded App Engine files publicly accessible as well. Be sure to restrict access to your Storage bucket again when you set up authentication._ + +## Get File Metadata + +File metadata contains common properties such as `Name`, `Size`, and `ContentType` (often referred to as MIME type) in addition to some less common ones like `ContentDisposition` and `TimeCreated`. This metadata can be retrieved from a Firebase Storage reference using the `GetMetadata` method: + +```csharp +// Create reference to the file whose metadata we want to retrieve +StorageReference forestRef = rootRef.GetChild ("images/forest.jpg"); + +// Get metadata properties +forestRef.GetMetadata (HandleStorageGetPutUpdateCompletion); + +void HandleStorageGetPutUpdateCompletion (StorageMetadata metadata, NSError error) +{ + if (error != null) { + // Uh-oh, an error occurred! + return; + } + + // Metadata now contains the metadata for 'images/forest.jpg' +} +``` + +An `async`/`await` version of this: + +```csharp +// Create reference to the file whose metadata we want to retrieve +StorageReference forestRef = rootRef.GetChild ("images/forest.jpg"); + +try { + StorageMetadata metadata = await starsRef.GetMetadataAsync (); + + // Metadata now contains the metadata for 'images/forest.jpg' +} catch (NSErrorException ex) { + // Uh-oh, an error occurred! +} +``` + +## Update File Metadata + +You can update file metadata at any time after the file upload completes by using the `UpdateMetadata` method. Refer to the [full list](#ile-metadata-properties) for more information on what properties can be updated. Only the properties specified in the metadata are updated, all others are left unmodified. + +```csharp +// Create reference to the file whose metadata we want to change +StorageReference forestRef = rootRef.GetChild ("images/forest.jpg"); + +// Create file metadata to update +var newMetadata = new StorageMetadata { + CacheControl = "public,max-age=300", + ContentType = "image/jpeg" +}; + +// Update metadata properties +forestRef.UpdateMetadata (newMetadata, HandleStorageGetPutUpdateCompletion); + +void HandleStorageGetPutUpdateCompletion (StorageMetadata metadata, NSError error) +{ + if (error != null) { + // Uh-oh, an error occurred! + return; + } + + // Updated metadata for 'images/forest.jpg' is returned +} +``` + +An `async`/`await` version of this: + +```csharp +// Create reference to the file whose metadata we want to change +StorageReference forestRef = rootRef.GetChild ("images/forest.jpg"); + +// Create file metadata to update +var newMetadata = new StorageMetadata { + CacheControl = "public,max-age=300", + ContentType = "image/jpeg" +}; + +try { + StorageMetadata metadata = await forestRef.UpdateMetadataAsync (newMetadata); + + // Updated metadata for 'images/forest.jpg' is returned +} catch (NSErrorException ex) { + // Uh-oh, an error occurred! +} +``` + +You can delete writable metadata properties by passing the empty string: + +```csharp +var newMetadata = new StorageMetadata { + ContentType = "" +}; + +// Delete the metadata property +forestRef.UpdateMetadata (newMetadata, HandleStorageGetPutUpdateCompletion); + +void HandleStorageGetPutUpdateCompletion (StorageMetadata metadata, NSError error) +{ + if (error != null) { + // Uh-oh, an error occurred! + return; + } + + // Updated metadata for 'images/forest.jpg' is returned +} +``` + +## Error Handling + +There are a number of reasons why errors may occur on getting or updating metadata, including the local file not existing, or the user not having permission to upload the desired file. You can find more information about errors in the [Handle Errors](#handle-errors) section below of the docs. + +## Custom Metadata + +You can specify custom metadata as an `NSDictionary` containing `NSString` properties: + +```csharp +object [] keys = { "location", "activity" }; +object [] values = { "Yosemite, CA, USA", "Hiking" }; +var customMetadata = NSDictionary.FromObjectsAndKeys (values, keys, keys.Length); + +var metadata = NSDictionary.FromObjectAndKey (customMetadata, new NSString ("customMetadata")); +``` + +You can store app-specific data for each file in custom metadata, but Firebase highly recommend using a database (such as the [Firebase Database][14]) to store and synchronize this type of data. + +### File Metadata Properties + +A full list of metadata properties on a file is available below: + +| Property | Type | Writable | +|:----------------------:|:--------------------------------:|:--------:| +| **Bucket** | string | NO | +| **Generation** | long | NO | +| **Metageneration** | long | NO | +| **Path** | string | NO | +| **Name** | string | NO | +| **Size** | long | NO | +| **TimeCreated** | NSDate | NO | +| **Updated** | NSDate | NO | +| **Md5Hash** | string | YES | +| **CacheControl** | string | YES | +| **ContentDisposition** | string | YES | +| **ContentEncoding** | string | YES | +| **ContentLanguage** | string | YES | +| **ContentType** | string | YES | +| **DownloadUrls** | NSUrl [] | NO | +| **CustomMetadata** | NSDictionary | YES | + +> ![note_icon] _**Note:**_ _at present, setting the md5Hash property on upload doesn't affect the upload, as hash verification is not yet implemented._ + +--- + +# Delete Files on iOS + +> ![note_icon] **_Note_**: _By default, Cloud Storage buckets require Firebase Authentication to delete files. You can change your [Firebase Storage Security Rules][9] to allow unauthenticated access. Since the default Google App Engine app and Firebase share this bucket, configuring public access may make newly uploaded App Engine files publicly accessible as well. Be sure to restrict access to your Storage bucket again when you set up authentication._ + +## Delete a File + +To delete a file, first create a reference to that file. Then call the `Delete` method on that reference: + +```csharp +// Create a reference to the file to delete +StorageReference desertRef = rootRef.GetChild ("images/desert.jpg"); + +// Delete the file +desertRef.Delete (HandleStorageDeleteCompletion); + +void HandleStorageDeleteCompletion (NSError error) +{ + if (error != null) { + // Uh-oh, an error occurred! + return; + } + + // File deleted successfully +} +``` + +An `async`/`await` version of this: + +```csharp +// Create a reference to the file to delete +StorageReference desertRef = rootRef.GetChild ("images/desert.jpg"); + +// Delete the file +desertRef.Delete (HandleStorageDeleteCompletion); + +try { + await desertRef.DeleteAsync (); + + // File deleted successfully +} catch (NSErrorException ex) { + // Uh-oh, an error occurred! +} +``` + +> ![note_icon] **_Note:_** _Deleting a file is a permenant action! If you care about restoring deleted files, make sure to back up your files, or [enable object versioning][15] on your Google Cloud Storage bucket._ + +--- + +# Handle Errors + +Sometimes when you're building an app, things don't go as planned and an error occurs. + +When in doubt, check the error returned, and see what the error message says. + +> ![note_icon] **_Note_**: _By default, Cloud Storage buckets require Firebase Authentication to perform any action. You can change your [Firebase Storage Security Rules][9] to allow unauthenticated access. Since the default Google App Engine app and Firebase share this bucket, configuring public access may make newly uploaded App Engine files publicly accessible as well. Be sure to restrict access to your Storage bucket again when you set up authentication._ + +If you've checked the error message and have Storage Security Rules that allow your action, but are still struggling to fix the error, visit [Firebase Support page][16] and let them know how they can help. + +### Handle Error Messages + +There are a number of reasons why errors may occur, including the file not existing, the user not having permission to access the desired file, or the user cancelling the file upload. + +To properly diagnose the issue and handle the error, here is a full list of all the errors our client will raise, and how they can occur. + +| Name | Reason | +|:-----------------------------------------:|--------| +| **StorageErrorCode.Unknown** | An unknown error occurred. | +| **StorageErrorCode.ObjectNotFound** | No object exists at the desired reference. | +| **StorageErrorCode.BucketNotFound** | No bucket is configured for Firebase Storage. | +| **StorageErrorCode.ProjectNotFound** | No project is configured for Firebase Storage. | +| **StorageErrorCode.QuotaExceeded** | Quota on your Firebase Storage bucket has been exceeded. If you're on the free tier, upgrade to a paid plan. If you're on a paid plan, reach out to Firebase support. +| **StorageErrorCode.Unauthenticated** | User is unauthenticated. Authenticate and try again. | +| **StorageErrorCode.Unauthorized** | User is not authorized to perform the desired action. Check your rules to ensure they are correct. | +| **StorageErrorCode.RetryLimitExceeded** | The maximum time limit on an operation (upload, download, delete, etc.) has been exceeded. Try uploading again. | +| **StorageErrorCode.NonMatchingChecksum** | File on the client does not match the checksum of the file recieved by the server. Try uploading again. | +| **StorageErrorCode.Cancelled** | User cancelled the operation. +| **StorageErrorCode.DownloadSizeExceeded** | Size of the downloaded file exceeds the amount of memory allocated for the download. Increase memory cap and try downloading again. | + +--- + +# Extend Cloud Storage with Cloud Functions + +You can trigger a function in response to the uploading, updating, or deleting of files and folders in Cloud Storage. For more information, read the following [documentation][17]. + +--- + +# Integrate with Google Cloud Platform + +Firebase Storage is tightly integrated with [Google Cloud Platform][1]. The Firebase SDKs for Storage store files directly in [Google Cloud Storage buckets][18], and as your app grows, you can easily integrate other Cloud services, such as managed compute like App Engine or Cloud Functions, or machine learning APIs like Cloud Vision or Google Translate. + +For more information, visit the following [documentation][19]. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://firebase.google.com/docs/storage/ios/start) to see original Firebase documentation._ + +[1]: https://cloud.google.com/storage +[2]: https://firebase.google.com/console/ +[3]: http://support.google.com/firebase/answer/7015592 +[4]: https://firebase.google.com/docs/storage/security +[5]: https://firebase.google.com/docs/storage/security/start +[6]: https://firebase.google.com/docs/storage/security/secure-files +[7]: https://firebase.google.com/docs/storage/security/user-security +[8]: https://components.xamarin.com/view/firebaseiosauth +[9]: https://firebase.google.com/docs/storage/security/start#sample-rules +[10]: https://cloud.google.com/storage/docs/bucket-locations +[11]: https://cloud.google.com/storage/docs/storage-classes +[12]: https://cloud.google.com/sdk/docs/ +[13]: https://cloud.google.com/storage/docs/gsutil +[14]: https://components.xamarin.com/view/firebaseiosdatabase +[15]: https://cloud.google.com/storage/docs/object-versioning#_Enabling +[16]: https://firebase.google.com/support +[17]: https://firebase.google.com/docs/storage/extend-with-functions +[18]: https://cloud.google.com/storage/docs/key-terms#buckets +[19]: https://firebase.google.com/docs/storage/gcp-integration +[note_icon]: https://cdn3.iconfinder.com/data/icons/UltimateGnome/22x22/apps/gnome-app-install-star.png +[warning_icon]: https://cdn2.iconfinder.com/data/icons/freecns-cumulus/32/519791-101_Warning-20.png diff --git a/docs/Google/Analytics/Details.md b/docs/Google/Analytics/Details.md new file mode 100755 index 00000000..97182fb0 --- /dev/null +++ b/docs/Google/Analytics/Details.md @@ -0,0 +1,7 @@ +Set up and customize tracking for your mobile apps. + +## Report and analyze + +Use our simple and powerful APIs to retrieve report data from Google Analytics. With the reporting APIs, you can save time by automating complex reporting tasks. You can also integrate Google Analytics data with your own business data for deeper insights. + + diff --git a/docs/Google/Analytics/GettingStarted.md b/docs/Google/Analytics/GettingStarted.md new file mode 100755 index 00000000..e82ad0ff --- /dev/null +++ b/docs/Google/Analytics/GettingStarted.md @@ -0,0 +1,97 @@ +# Get your Google Analytics Tracking Id + +Follow the instructions in [this page](https://support.google.com/analytics/answer/2614741) to set up and get the tracking ID for a new app property in either a new or existing Google Analytics account. + +# iOS Getting Started + +There are two steps to getting started with the iOS SDK: + +1. Initialize the tracker +2. Add screen measurement + +After completing these steps, you'll be able to measure the following with Google Analytics: + +* App installations +* Active users and demographics +* Screens and user engagement +* Crashes and exceptions + +## Initializing the tracker + +To initialize the tracker, use the `Google.Analytics` namespace in your AppDelegate and add this code to your AppDelegate's `FinishedLaunching` method: + +```csharp +using Google.Analytics; +//... + +// Shared GA tracker +public ITracker Tracker; + +// Learn how to get your own Tracking Id from: +// https://support.google.com/analytics/answer/2614741?hl=en +public static readonly string TrackingId = "UA-TRACKING-ID"; + +public override bool FinishedLaunching (UIApplication app, NSDictionary options) +{ + + // Optional: set Google Analytics dispatch interval to e.g. 20 seconds. + Gai.SharedInstance.DispatchInterval = 20; + + // Optional: automatically send uncaught exceptions to Google Analytics. + Gai.SharedInstance.TrackUncaughtExceptions = true; + + // Initialize tracker. + Tracker = Gai.SharedInstance.GetTracker (TrackingId); +} +``` + +**Note**: When you obtain a tracker for a given tracking Id, the tracker instance is persisted in the library. When you call `GetTracker` with the same tracking Id later, the same tracker instance will be returned. Also, the Google Analytics SDK exposes a default tracker instance that gets set to the first tracker instance created. It can be accessed by: + +```csharp +Gai.SharedInstance.DefaultTracker +``` + +Note that in the above example, "UA-TRACKING-ID" here is a placeholder for the tracking ID assigned to you when you created your Google Analytics property. If you are only using one property ID in your app, using the default tracker method is best. + +## Implementing screen measurement + +To implement it you must provide the view name to be used in your Google Analytics reports. A good place to put this is the view controller's initializer method, if you have one, or the `ViewDidAppear` method: + +```csharp +using Google.Analytics; +//... + +public override void ViewDidAppear (bool animated) +{ + base.ViewDidAppear (animated); + + // This screen name value will remain set on the tracker and sent with + // hits until it is set to a new value or to null. + Gai.SharedInstance.DefaultTracker.Set (GaiConstants.ScreenName, "Main View"); + + Gai.SharedInstance.DefaultTracker.Send (DictionaryBuilder.CreateAppView ().Build ()); +} +``` + +**Note**: `GaiConstants` is a static class containing all the constants you can set in the `Set` method from the `DictionaryBuilder` class. + +To learn more about screen measurement, see the [Screens Developer Guide](https://developers.google.com/analytics/devguides/collection/ios/v3/screens). + +Congratulations! Your Xamarin.iOS app is now setup to send data to Google Analytics. + +## Next steps + +You can do much more with Google Analytics, including measuring campaigns, in-app payments and transactions, and user interaction events. See the following developer guides to learn how to add these features to your implementation: + +* [Advanced Configuration](https://developers.google.com/analytics/devguides/collection/ios/v3/advanced) – Learn more about advanced configuration options, including using multiple trackers. +* [Measuring Campaigns](https://developers.google.com/analytics/devguides/collection/ios/v3/campaigns) – Learn how to implement campaign measurement to understand which channels and campaigns are driving app installs. +* [Measuring Events](https://developers.google.com/analytics/devguides/collection/ios/v3/events) – Learn how to measure user engagement with interactive content like buttons, videos, and other media using Events. +* [Measuring In-App Payments](https://developers.google.com/analytics/devguides/collection/ios/v3/ecommerce) – Learn how to measure in-app payments and transactions. +* [User timings](https://developers.google.com/analytics/devguides/collection/ios/v3/usertimings) – Learn how to measure user timings in your app to measure load times, engagement with media, and more. + +# More Resources + +* [Google Analytics Developer Portal](https://developers.google.com/analytics/devguides/collection/) + + +###### The [original content material](https://developers.google.com/analytics/devguides/collection/) of this page is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/) and has been adapted to match this page format. \ No newline at end of file diff --git a/docs/Google/AppIndexing/Details.md b/docs/Google/AppIndexing/Details.md new file mode 100755 index 00000000..a6eb9157 --- /dev/null +++ b/docs/Google/AppIndexing/Details.md @@ -0,0 +1,23 @@ +App Indexing puts your app in front of users who use Google Search. It works by indexing the URL patterns you provide in your app manifest and using API calls from your app to make content within your app available to both existing and new users. Specifically, when you support URLs for your app content, iOS users can go directly to it from Search results on their device. Finally, Google uses App Indexing API calls from your app as a ranking signal for your URLs. + +## How App Indexing works for iOS + +### Support HTTP URLs to your mobile app. + +iOS apps support HTTP URLs and use site association to verify the relationship between the app and its website. This allows Google systems to index URLs that work for both your site and your app and to serve them in search results. + +Once you associate your app to your website, Google systems quickly recognize the association and begin the crawling and discovery process for your app URLs. This can take between 24 hours and a number of days, depending on a number of factors. Because most app content is currently associated with web content, crawl scheduling works in a fashion similar to that of webpages: Google take into account multiple factors like frequency of content updates, server load, importance, and freshness of the page. Google will send you a message in Search console when content from your app shows up in search (“first impression”). If it’s been a couple weeks and you still don’t see any app links in search, check for crawl errors. Read about [Google Search crawl frequency][1] on Google Help Center. + +[1]: https://support.google.com/webmasters/answer/34439 + +### Add the App Indexing API or SDK. + +Use the App Indexing SDK for iOS 9. The use of Google SDK enhances ranking performance for the URLs and provides the basis for link titles and content snippets. This provides the titles and description snippets associated with your content, as well as the history of actions to your app. Users of your app can delete past activity in apps at https://history.google.com/. + +### Check your implementation. + +Test URLs to your app in your development environment to make sure they lead to the correct content. + +## Technical requirements + +Google App Indexing documentation for iOS 9 serves iOS universal links from Google Search in Safari. App Indexing for iOS versions 7 and 8 is now deprecated and no longer available for new integrations. \ No newline at end of file diff --git a/docs/Google/AppIndexing/GettingStarted.md b/docs/Google/AppIndexing/GettingStarted.md new file mode 100755 index 00000000..dafa2693 --- /dev/null +++ b/docs/Google/AppIndexing/GettingStarted.md @@ -0,0 +1,114 @@ +## Support HTTP URLs + +App Indexing for iOS 9 uses HTTP URLs to direct users to content in your app. If you’ve already followed the instructions to [support universal links][1] in your app, you can skip this section. Otherwise, do the following: + +### Create the app-to-site association. + +This involves two things: + +- Add a `com.apple.developer.associated-domains` entitlement in entitlement.plist file that lists each domain associated with your app. +- Create an `apple-app-site-association` file for each associated domain with the content your app supports and host it at the root level. + +*Note: The association file must be hosted on a domain that supports HTTPS/TLS, even if the HTTP deep links are not themselves served via HTTPS.* + +#### Preparing Your Website + +1. Create a apple-app-site-association file. Note that there’s no .json file type +2. Place the apple-app-site-association file and identify app IDs and paths on your website. You can define several apps in this file and iOS will follow the app order when looking for a match so you can specify different apps to handle different paths on your website. + +Here's an example of file's content: + +``` +{ + "applinks": { + "apps": [], + "details": [ + { + "appID": "9JA89QQLNQ.com.apple.wwdc", + "paths": [ "/wwdc/news/", "/videos/wwdc/2015/*"] + }, + { + "appID": "TeamID.BundleID2", + "paths": [ "*" ] + } + ] + } +} +``` + +The apps key in an apple-app-site-association file must be present and its value must be an empty array. The value of the details key is an array of dictionaries, one dictionary per app that your website supports. The order of the dictionaries in the array determines the order the system follows when looking for a match, so you can specify an app to handle a particular part of your website. + +Each app-specific dictionary contains an appID key and a paths key. The value of the appID key is the app’s team ID and the bundle ID; the value of the paths key is an array of strings that specify the parts of your website that are supported by the app and the parts of your website that you don’t want to associate with the app. To specify an area that should not be handled as a universal link, add “NOT ” (including a space after the T) to the beginning of the path string. For example: + +``` +"paths": [ "/wwdc/news/", "NOT /videos/wwdc/2010/*", "/videos/wwdc/201?/*"] +``` + +Upload the apple-app-site-association file in the root of your HTTPS web server. The file should be accessible without redirects at http:///apple-app-site-association + +### Preparing your App + +#### Creating an App ID and provisioning profile + +To make Universal Links works, you need to create a Provisioning Profile with an Explicit App Id with Associated Domains Services enabled. To achieve this, follow this steps: + +1. On the Apple Developer Member Center home page, click Certificates, Identifiers & Profiles. +2. On the Certificates, Identifiers & Profiles page, under the iOS Apps column, click Identifiers. +3. On the iOS App IDs page, click on the **+** button. +4. On the Registering an App ID page, enter an App ID Description/Name, select **Explicit App ID**, enter your app’s Bundle ID, enable **Associated Domains +** Service and then click Continue. +5. On the Confirm your App ID page, click Submit. +6. On the Registration complete page, click Done. +7. Create a provisioning profile with the new App ID. + +#### Enabling Associated Domains in your app + +1. In you Xamarin iOS project, open the Entitlements.plist file +2. Open the Associated Domains header and check **Enable Associated Domains** +3. Add your domains starting with `applinks:`, for example: `applinks:xamarin.com` + +#### Add handling for universal links to your app. + +Adopt the [UIApplicationDelegate][2] methods so that your app opens the appropriate app content in response to the user’s click from search results. + +```csharp +public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler) +{ + if (userActivity.ActivityType == NSUserActivityType.BrowsingWeb) { + // Do your magic here. + } + + return true; +} +``` + +[1]: https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html#//apple_ref/doc/uid/TP40016308-CH12-SW1 +[2]: https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/index.html#//apple_ref/occ/intf/UIApplicationDelegate + +## Register your app to Google system + +Call the `RegisterApp` method in your `FinishedLaunching` method. + +```csharp +AppIndexing.SharedInstance.RegisterApp ("YOUR_ITUNES_ID"); +``` + +You're all set! Once Google's crawlers discover the the URLs in your association file, Google will automatically start indexing any existing or new HTTP deep links to your app. + +## Check Your Implementation + +Once you've completed setup for App Indexing, you can verify your universal links prior to their appearance in Google Search by tapping a universal link in Safari on your device and making sure that it takes you to the right place in the app. + +*Note: You cannot test universal links on simulator.* + +## Enhance Your Search Performance + +A key way to enhance your app's ranking is to provide high-quality content in your site and your app. Specifically, keep in mind the following: + +- Make sure your associated site meets Google's [design and content guidelines][3]. +- Follow the same layout practices recommended for your [mobile-friendly homepage][4]. For example, keep your titles short and avoid full-width menus. + +In order to ensure a great search experience for users, Google may take corrective action in cases where they see abuse, deception, or other actions that hurt the search experience for users. This can include actions such as demoting or removing your app deep links from Google Search results. + +[3]: https://support.google.com/webmasters/answer/35769?vid=1-635785548570479109-2344616627#design_and_content_guidelines +[4]: https://developers.google.com/web/fundamentals/layouts/principles/ \ No newline at end of file diff --git a/docs/Google/AppInvite/Details.md b/docs/Google/AppInvite/Details.md new file mode 100755 index 00000000..0445d101 --- /dev/null +++ b/docs/Google/AppInvite/Details.md @@ -0,0 +1,27 @@ + +App Invites provide a powerful way to organically grow your app, user-to-user. Your users recommend your app to their friends using personalized, contextual invitations powered by Google. App Invites provide a great onboarding experience to your new users. Google optimizes your app install rates by reducing friction and using relevant context at every step of the user invitation flow. + +### App Invites flow + +App Invites always begins with a user sending an invite from your app. The following diagram illustrates the App Invites flow. + +![AppInviteFlow](https://developers.google.com/app-invites/images/ai-ios-flow.svg) + +### Sending an invitation + + +You allow a user to send an invitation from your application by using the `IInviteBuilder` interface in your app’s ViewController file. The invite must include a title and can also include a message and deep link data. + +The Open method of `IInviteBuilder` opens the contact chooser dialog where the user selects the contacts to invite. The invite can be sent via email or SMS. + +### Receiving an invitation + +When a user receives and choose to open an invitation URL, the invitation flow branches according to whether or not your app is already installed on the recipient’s device. If the recipient doesn’t have your app, the flow branches to let the user install it. If the recipient is already one of your users, then the optional deep link information in the invite passes to your app for processing. + +**App is already installed** + +If the recipient has already installed your app, the app will receive the invite URL containing the optional deep link data. + +**App is not installed** + +If the recipient has not yet installed the app, they can choose to install the app from the iTunes App Store. When the app is opened for the first time, the App Invites SDK will supply a deep link if one is available. diff --git a/docs/Google/AppInvite/GettingStarted.md b/docs/Google/AppInvite/GettingStarted.md new file mode 100755 index 00000000..1967e56c --- /dev/null +++ b/docs/Google/AppInvite/GettingStarted.md @@ -0,0 +1,113 @@ +Configuring your App +-------------------- + +Google provides an easy to use configuration web tool to generate a config file for your app: + +1. Open [Google's configuration tool][1] to create a config file for your app. +2. Enter your app's name and iOS Bundle ID and click continue +3. Enter your App Store ID and click *Enable App Invites* + (If you don't have an App Store ID you can use one of our favorites - 1000846120 or 783488172) +4. Click *continue* to generate the configuration files +5. Click *Download Google-Service-Info.plist* +6. Add `GoogleService-Info.plist` to your Xamarin.iOS app project and set the *Build Action* to `BundleResource` +7. In your Xamarin.iOS app project's `Info.plist` file, add the following URL Types: + - Identifier: `google` Role: `Editor` URL Schemes: `your.app.bundle.id` + - Identifier: `google` Role: `Editor` URL Schemes: `value of REVERSED_CLIENT_ID from GoogleService-Info.plist` + + +Setup your AppDelegate +---------------------- + +In order for App Invites to work properly, you must tell the SDK about some of your application lifecycle events. + +In your `AppDelegate.cs`, in the `FinishedLaunching (..)` override, you should add the following code to the start of the method: + +```csharp +NSError configureError; +Context.SharedInstance.Configure (out configureError); +if (configureError != null) + Console.WriteLine ("Error configuring the Google context: {0}", configureError); + +Invite.ApplicationDidFinishLaunching (); +``` + +Next, add the following override to your AppDelegate class (or if it already exists, add the code inside the method to the existing implementation: + +```csharp +public override bool OpenUrl (UIApplication application, NSUrl url, string sourceApplication, NSObject annotation) +{ + // Handle App Invite requests + var invite = Invite.HandleUrl (url, sourceApplication, annotation); + if (invite != null) { + var message =string.Format ("Deep link from {0} \nInvite ID: {1}\nApp URL: {2}", + sourceApplication, invite.InviteId, invite.DeepLink); + new UIAlertView (@"Deep-link Data", message, null, "OK").Show (); + return true; + } + + return SignIn.SharedInstance.HandleUrl (url, sourceApplication, annotation); +} +``` + + +Signing In +---------- + +In order for app invites to work, your app's user must sign in to their Google account. To do this, you should request a login in your View Controller. This can be done in the `ViewDidLoad` override: + +```csharp +// Sign the user in automatically +SignIn.SharedInstance.UIDelegate = this; +SignIn.SharedInstance.Delegate = this; +SignIn.SharedInstance.SignInUser (); +``` +You also must implement `ISignInDelegate` as well as `ISignInUIDelegate` and provide a `DidSignIn` method to know when the sign-in completed and if it was successful: + +```csharp +public void DidSignIn (SignIn signIn, GoogleUser user, NSError error) +{ + if (user != null && error == null) + // Enable App Invite button +} +``` +For more information on Google Sign-In please refer to the [Getting Started Guide][2] + +Displaying the AppInvite UI +--------------------------- + +Once your user is signed in, you can display the AppInvite UI with the following code: + +```csharp +var inviteDialog = Invite.InviteDialog; +inviteDialog.SetInviteDelegate (this); + +// NOTE: You must have the App Store ID set in your developer console project +// in order for invitations to successfully be sent. +var message = string.Format ("Try this out!\n -{0}", + SignIn.SharedInstance.CurrentUser.Profile.Name); + +// A message hint for the dialog. Note this manifests differently depending on the +// received invation type. For example, in an email invite this appears as the subject. +inviteDialog.SetMessage (message); + +// Title for the dialog, this is what the user sees before sending the invites. +inviteDialog.SetTitle ("App Invites Example"); +inviteDialog.SetDeepLink ("app_url"); +inviteDialog.Open (); +``` + +You can should also implement `IInviteDelegate` and optionally the `InviteFinished` method to be notified when the user has completed the AppInvite dialog: + +```csharp +[Export ("inviteFinishedWithInvitations:error:")] +public virtual void InviteFinished (string[] invitationIds, NSError error) +{ + var message = error != null ? error.LocalizedDescription : + string.Format ("{0} invites sent", invitationIds.Length); + + new UIAlertView ("Done", message, null, "OK").Show (); +} +``` + +[1]: https://developers.google.com/mobile/add?platform=ios&cntapi=appinvite +[2]: http://components.xamarin.com/gettingstarted/googleiosappinvite \ No newline at end of file diff --git a/docs/Google/Cast/Details.md b/docs/Google/Cast/Details.md new file mode 100755 index 00000000..6bbdd013 --- /dev/null +++ b/docs/Google/Cast/Details.md @@ -0,0 +1,11 @@ +Google Cast is a technology that enables multi-screen experiences and lets a user send and control content like video from a small computing device like a phone, tablet, or laptop to a large display device like a television. + +The sender may be a phone or tablet running on Android or iOS, or it may be a laptop computer running Chrome OS, Mac OS, or Windows. A sender application running on the sender device uses the Google Cast API appropriate to its operating system to discover and transmit to the receiver application running on the receiver device. You can use the sender APIs to enable your Android, iOS, or Chrome app to send content to a large display. + +The receiver device is optimized for video playback with a receiver application that receives data over Internet Protocol and transmits it to the television. The receiver API lets you customize the messaging between the sender and receiver applications for authentication and other scenarios. + +![https://developers.google.com/cast/images/Diagram.png](https://developers.google.com/cast/images/Diagram.png) + +While content is playing on a TV, a user can multitask on their device without interrupting the video playback. For example, a user can search for a video on their phone’s YouTube application and then send the video to their TV via a Google Cast device. The user can play, pause, seek, and control volume using their phone, they can search for other videos to watch, and even check their email while the content keeps playing on the TV. + +#####Portions of this page are modifications based on [work](https://developers.google.com/cast/) created and shared by [Google Inc.](http://google.com) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). \ No newline at end of file diff --git a/docs/Google/Cast/GettingStarted.md b/docs/Google/Cast/GettingStarted.md new file mode 100755 index 00000000..49ff3751 --- /dev/null +++ b/docs/Google/Cast/GettingStarted.md @@ -0,0 +1,708 @@ +# Table of Content + +- [Table of Content](#table-of-content) +- [Integrate CAF Sender into your iOS App](#integrate-caf-sender-into-your-ios-app) + - [App flow](#app-flow) + - [Call methods from main thread](#call-methods-from-main-thread) + - [Initialize the Cast Context](#initialize-the-cast-context) + - [The Cast UX Widgets](#the-cast-ux-widgets) + - [Add a Cast Button](#add-a-cast-button) + - [Configure device discovery](#configure-device-discovery) + - [How Session Management Works](#how-session-management-works) + - [Automatic Reconnection](#automatic-reconnection) + - [How Media Control Works](#how-media-control-works) + - [Set Media Metadata](#set-media-metadata) + - [Load media](#load-media) + - [4K Video Format](#4k-video-format) + - [Add Mini Controllers](#add-mini-controllers) + - [Add Expanded Controller](#add-expanded-controller) + - [Handle Errors](#handle-errors) + - [Logging](#logging) +- [Add Advanced CAF Sender Features to your iOS App](#add-advanced-caf-sender-features-to-your-ios-app) + - [Style the Widgets](#style-the-widgets) + - [Customize the Widgets](#customize-the-widgets) + - [Choose Controller Buttons](#choose-controller-buttons) + - [How Volume Control Works](#how-volume-control-works) + - [Using Media Tracks](#using-media-tracks) + - [Styling Text Tracks](#styling-text-tracks) + - [Receive Status Updates](#receive-status-updates) + - [Satisfy CORS Requirements](#satisfy-cors-requirements) + - [Add a Custom Channel](#add-a-custom-channel) + - [Use Queueing](#use-queueing) + - [Create and Load Media Queue Items](#create-and-load-media-queue-items) + - [Receive Media Queue Status Update](#receive-media-queue-status-update) + - [Edit the Queue](#edit-the-queue) + - [Supporting Autoplay](#supporting-autoplay) + - [Override Image Selection and Caching](#override-image-selection-and-caching) +- [Apply Custom Styles to Your iOS App](#apply-custom-styles-to-your-ios-app) + - [Applying a style to a UI element of a widget](#applying-a-style-to-a-ui-element-of-a-widget) + - [Table of Views and Styles](#table-of-views-and-styles) + +# Integrate CAF Sender into your iOS App + +The mobile device or laptop is the sender which controls the playback, and the Google Cast device is the receiver which displays the content on the TV. + +The sender framework refers to the Cast framework bundle, which consists of the class library binary and associated header files and resources. The sender app or Cast app refers to an app running on the sender. The receiver app refers to the HTML application running on the receiver. + +Google Cast uses the delegation pattern to inform the sender app of events and to move between various states of the Cast app life cycle. + +## App flow + +The following steps describe the typical high-level execution flow for a sender iOS app: + +* The Cast framework automatically starts device discovery based on the view controller lifecycle. +* When the user clicks on the Cast button, the framework presents the Cast dialog with the list of discovered Cast devices. +* When the user selects a Cast device, the framework attempts to launch the receiver app on the Cast device. +* The framework invokes callbacks in the sender app to confirm that the receiver app was launched. +* The framework creates a communication channel between the sender and receiver apps. +* The framework uses the communication channel to load and control media playback on the receiver. +* The framework synchronizes the media playback state between sender and receiver: when the user makes sender UI actions, the framework passes those media control requests to the receiver, and when the receiver sends media status updates, the framework updates the state of the sender UI. +* When the user clicks on the Cast button to disconnect from the Cast device, the framework will disconnect the sender app from the receiver. + +To troubleshoot your sender, you need to enable [logging](#logging). + +## Call methods from main thread + +All SDK methods must be called from the main thread. + +## Initialize the Cast Context + +The Cast framework has a global singleton object, the `CastContext`, which coordinates all of the framework's activities. This object must be initialized early in the application's lifecycle, typically in the `FinishedLaunching` method of the app delegate, so that automatic session resumption on sender app restart can trigger properly. + +A `CastOptions` object must be supplied when initializing the `CastContext`. This class contains options that affect the behavior of the framework. The most important of these is the **Receiver Application ID**, which is used to filter discovery results and to launch the receiver app when a Cast session is started. + +The `FinishedLaunching` method is also a good place to set up a **Logging Delegate** to receive the logging messages from the framework. These can be useful for debugging and troubleshooting: + +```csharp +using Google.Cast; + +... + +// You can add your own app id here that you get by registering +// with the Google Cast SDK Developer Console https://cast.google.com/publish +public static readonly string ReceiverApplicationId = "A1B2C3D4"; + +public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) +{ + + // Contains options that affect the behavior of the framework. + var discoveryCreiteria = new DiscoveryCriteria (ReceiverApplicationId); + var options = new CastOptions (discoveryCreiteria); + + // CastContext coordinates all of the framework's activities. + CastContext.SetSharedInstance (options); + + // Google Cast Logger + Logger.SharedInstance.Delegate = this; + + ... + + return true; +} + +#region Logger Delegate + +[Export ("logMessage:fromFunction:")] +void LogMessage (string message, string function) +{ + Console.WriteLine ($"{function} {message}"); +} + +#endregion +``` + +## The Cast UX Widgets + +The Cast framework provides these widgets that comply with the Cast Design Checklist: + +* [Introductory Overlay][1]: The `CastContext` class has a method, `PresentCastInstructionsViewControllerOnce`, which can be used to spotlight the **Cast button** the first time a Cast receiver is available. The Sender app can customize the text, position of the title text and the Dismiss button. +* [Cast Button][2]: The cast button is visible when a receiver is discovered that supports your app. When the user first clicks on the cast button, a cast dialog is displayed which lists the discovered devices. When the user clicks on the cast button while the device is connected, it displays the current media metadata (such as title, name of the recording studio and a thumbnail image) or allows the user to disconnect from the cast device. +* [Mini Controller][3]: When the user is casting content and has navigated away from the current content page or expanded controller to another screen in the sender app, the mini controller is displayed at the bottom of the screen to allow the user to see the currently casting media metadata and to control the playback. +* [Expanded Controller][4]: When the user is casting content, if they click on the media notification or mini controller, the expanded controller launches, which displays the currently playing media metadata and provides several buttons to control the media playback. + +## Add a Cast Button + +The framework provides a Cast button component as a `UIButton` subclass. It can be added to the app's title bar by wrapping it in a `UIBarButtonItem`. A typical `UIViewController` subclass can install a Cast button as follows: + +```csharp +// Cast button to allow the user to select a Cast device. +var btnCast = new UICastButton (new CGRect (0, 0, 24, 24)) { TintColor = UIColor.White }; +NavigationItem.RightBarButtonItem = new UIBarButtonItem (btnCast); +``` + +By default, tapping the button will open the Cast dialog that is provided by the framework. + +`UICastButton` can also be added directly to the storyboard. + +## Configure device discovery + +In the framework, device discovery happens automatically. There is no need to explicitly start or stop the discovery process. + +Discovery in the framework is managed by the class `DiscoveryManager`, which is a property of the `CastContext`. The framework provides a default Cast dialog component for device selection and control. The device list is ordered lexicographically by device friendly name. + +## How Session Management Works + +The Cast framework introduces the concept of a Cast session, the establishment of which combines the steps of connecting to a device, launching (or joining) a receiver app, connecting to that app, and initializing a media control channel, if appropriate. + +Sessions are managed by the class `SessionManager`, which is a property of the `CastContext`. Individual sessions are represented by subclasses of the class `Session`: for example, `CastSession` represents sessions with Cast devices. You can access the currently active Cast session (if any), as the `CurrentCastSession` property of `SessionManager` class. + +The `ISessionManagerListener` interface can be used to monitor session events, such as session creation, suspension, resumption, and termination. The framework automatically suspends sessions when the sender app is going into the background and attempts to resume them when the app returns to the foreground (or is relaunched after an abnormal/abrupt app termination while a session was active). + +If the Cast dialog is being used, then sessions are created and torn down automatically in response to user gestures. Otherwise, the app can start and end sessions explicitly via methods on `SessionManager`. + +If the app needs to do special processing in response to session lifecycle events, it can register one or more `ISessionManagerListener` instances with the `SessionManager`. `ISessionManagerListener` is an interface which defines callbacks for such events as session start, session end, and so on. + +## Automatic Reconnection + +The Cast framework adds reconnection logic to automatically handle reconnection in many subtle corner cases, such as: + +* Recover from a temporary loss of WiFi +* Recover from device sleep +* Recover from backgrounding the app +* Recover if the app crashed + +## How Media Control Works + +If a Cast session is established with a receiver app that supports the media namespace, an instance of RemoteMediaClient will be created automatically by the framework; it can be accessed as the `RemoteMediaClient` property of the `CastSession` instance. + +All methods on `RemoteMediaClient` which issue requests to the receiver will return a `Request` object which can be used to track that request. A `IRequestDelegate` can be assigned to this object to receive notifications about the eventual result of the operation. + +It is expected that the instance of `RemoteMediaClient` may be shared by multiple parts of the app, and indeed some internal components of the framework such as the Cast dialog and mini media controls do share the instance. To that end, `RemoteMediaClient` supports the registration of multiple `IRemoteMediaClientListeners`, unlike `MediaControlChannel`, which only supported a single delegate. + +## Set Media Metadata + +The `MediaMetadata` class represents information about a media item you want to cast. The following example creates a new `MediaMetadata` instance of a movie and sets the title, subtitle, recording studio's name, and two images: + +```csharp +var metadata = new MediaMetadata (MediaMetadataType.Movie); +metadata.SetString (video.Title, MetadataKey.Title); +metadata.SetString (video.Subtitle, MetadataKey.Subtitle); +metadata.SetString (video.Studio, MetadataKey.Studio); +metadata.AddImage (new Image (video.ImageUrl, 480, 720)); +metadata.AddImage (new Image (video.PosterUrl, 780, 1200)); +``` + +## Load media + +To load a media item, create a `MediaInformation` instance using the media's metadata. Then get the current `CastSession` and use its `RemoteMediaClient` to load the media on the receiver app. You can then use `RemoteMediaClient` for controlling a media player app running on the receiver, such as for play, pause and stop: + +```csharp +var mediaInformation = new MediaInformation (videoUrl, + MediaStreamType.Buffered, + "video/mp4", + metadata, + video.Duration, + null, + null, + null); + +var castSession = CastContext.SharedInstance.SessionManager.CurrentCastSession; + +// Make sure that we are connected to a Cast device. +if (castSession != null) { + // Play video on Cast device. + castSession.RemoteMediaClient.LoadMedia (mediaInformation, true); +} +``` + +### 4K Video Format + +To determine what video format your media is, use the property `VideoInfo` of `MediaStatus` class to get the current instance of `VideoInfo`. This instance contains the type of HDR TV format and the height and width in pixels. Variants of 4K format are indicated in the hdrType property by enum values `VideoInfoHdrType`. + +## Add Mini Controllers + +According to the [Cast Design Checklist][5], a sender app should provide a persistent control known as the [mini controller][6] that should appear when the user navigates away from the current content page. The mini controller provides instant access and a visible reminder for the current Cast session. + +The Cast framework provides a control bar, `UIMiniMediaControlsViewController`, which can be added to the scenes in which you want to show the mini controller. + +There are two ways to add the mini controller to a sender app: + +* Let the Cast framework manage the layout of the mini controller by wrapping your existing view controller with its own view controller. +* Manage the layout of the mini controller widget yourself by adding it to your existing view controller by providing a subview in the storyboard. + +The first way is to use the `UICastContainerViewController` which wraps another view controller and adds a `UIMiniMediaControlsViewController` at the bottom. This approach is limited in that you cannot customize the animation and cannot configure the behavior of the container view controller. + +This first way is typically done in the `FinishedLaunching` method of the app delegate: + +```csharp +public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) +{ + ... + + var appStoryboard = UIStoryboard.FromName ("Main", null); + var navigationController = appStoryboard.InstantiateViewController ("MainNavigation") as UINavigationController; + var castContainer = CastContext.SharedInstance.CreateCastContainerController (navigationController); + castContainer.MiniMediaControlsItemEnabled = true; + + ... +} +``` + +Add this property to control the visibility of the mini controller: + +```csharp +public bool CastControlBarsEnabled { + get { + var castContainer = Window.RootViewController as UICastContainerViewController; + return castContainer.MiniMediaControlsItemEnabled; + } + set { + var castContainer = Window.RootViewController as UICastContainerViewController; + castContainer.MiniMediaControlsItemEnabled = value; + } +} +``` + +The second way is to add the mini controller directly to your existing view controller by using `CreateMiniMediaControlsViewController` to create a `UIMiniMediaControlsViewController` instance and then adding it to the container view controller as a subview: + +```csharp +using Google.Cast; + +UIView MiniMediaControlsContainerView; +bool miniMediaControlsViewEnabled; +NSLayoutConstraint miniMediaControlsHeightConstraint; +UIMiniMediaControlsViewController miniMediaControlsViewController; + +public override void ViewDidLoad () +{ + base.ViewDidLoad (); + + miniMediaControlsViewController = CastContext.SharedInstance.CreateMiniMediaControlsViewController (); + miniMediaControlsViewController.ShouldAppear += (sender, e) => { + UpdateControlBarsVisibility (); + }; + AddChildViewController (miniMediaControlsViewController); + + miniMediaControlsViewController.View.Frame = MiniMediaControlsContainerView.Bounds; + MiniMediaControlsContainerView.AddSubview (miniMediaControlsViewController.View); + miniMediaControlsViewController.DidMoveToParentViewController (this); + + UpdateControlBarsVisibility (); +} +``` + +The `UIMiniMediaControlsViewController.ShouldAppear` event tells the host view controller when the mini controller should be visible: + +```csharp +public override void ViewDidLoad () +{ + ... + + miniMediaControlsViewController.ShouldAppear += (sender, e) => { + UpdateControlBarsVisibility (); + }; + + ... +} + +void UpdateControlBarsVisibility () +{ + if (miniMediaControlsViewEnabled && + miniMediaControlsViewController.Active) { + miniMediaControlsHeightConstraint.Constant = miniMediaControlsViewController.MinHeight; + } else { + miniMediaControlsHeightConstraint.Constant = 0; + } + + UIView.Animate (0.5, () => { View.LayoutIfNeeded () }); +} + +``` + +When your sender app is playing a video or audio live stream, the SDK automatically displays a play/stop button in place of the play/pause button in the mini controller. + +See **Style the Widgets** section below for how your sender app can configure the appearance of the widgets across your app. + +## Add Expanded Controller + +The Google Cast Design Checklist requires a sender app to provide an expanded controller for the media being cast. The expanded controller is a full screen version of the mini controller. + +The expanded controller is a full screen view which offers full control of the remote media playback. This view should allow a casting app to manage every manageable aspect of a cast session, with the exception of receiver volume control and session lifecycle (connect/stop casting). It also provides all the status information about the media session (artwork, title, subtitle, and so forth). + +The functionality of this view is implemented by the `UIExpandedMediaControlsViewController` class. + +The first thing you have to do is enable the default expanded controller in the cast context. Modify `FinishedLaunching` to enable the default expanded controller: + +```csharp +public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) +{ + ... + + // Use Default Expanded Media Controls + CastContext.SharedInstance.UseDefaultExpandedMediaControls = true; + + ... +} +``` + +Add the following code in your controller to load the expanded controller when the user starts to cast a video: + +```csharp +// Reproduces the video on Cast device. +void PlayVideoRemotely (MediaInformation mediaInformation) +{ + ... + + NavigationItem.BackBarButtonItem = new UIBarButtonItem ("", UIBarButtonItemStyle.Plain, null, null); + (UIApplication.SharedApplication.Delegate as AppDelegate).CastControlBarsEnabled = false; + CastContext.SharedInstance.PresentDefaultExpandedMediaControls (); + + ... +} +``` + +The expanded controller will also be launched automatically when the user taps the mini controller. + +When your sender app is playing a video or audio live stream, the SDK automatically displays a play/stop button in place of the play/pause button in the expanded controller. + +See **Style the Widgets** section below for how your sender app can configure the appearance of the widgets across your app. + +## Handle Errors + +It is very important for sender apps to handle all error callbacks and decide the best response for each stage of the Cast life cycle. The app can display error dialogs to the user or it can decide to end the Cast session. + +## Logging + +`Logger` class is a singleton used for logging by the framework. Use the `ILoggerDelegate` to customize how you handle log messages: + +```csharp +public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) +{ + ... + + // Google Cast Logger + Logger.SharedInstance.Delegate = this; + + ... +} + +#region Logger Delegate + +[Export ("logMessage:fromFunction:")] +void LogMessage (string message, string function) +{ + Console.WriteLine ($"{function} {message}"); +} + +#endregion +``` + +> ***Note:*** *Don't forget to implement the `ILoggerDelegate` interface to your class.* + +--- + +# Add Advanced CAF Sender Features to your iOS App + +## Style the Widgets + +You can customize [Cast widgets][7] by setting the colors, styling the buttons, text and thumbnail appearance, and by choosing the types of buttons to display + +### Customize the Widgets + +The Cast framework widgets supports the [Apple UIAppearance Protocol in UIKit][8] to change the appearance of the widgets across your app, such as the position or border of a button. Use this protocol to style the Cast framework widgets to match an existing apps styling. + +### Choose Controller Buttons + +Both the expanded controller class (`UIExpandedMediaControlsViewController`) and the mini controller class (`UIMiniMediaControlsViewController`) contain a button bar, and clients can configure which buttons are presented on those bars. This is achieved by both classes conforming to `IUIMediaButtonBarProtocol` interface. + +The mini controller bar has 3 configurable slots for buttons: + +``` +SLOT SLOT SLOT + 1 2 3 +``` + +The expanded controller bar has a permanent play/pause toggle button in the middle of the bar plus 4 configurable slots: + +``` +SLOT SLOT PLAY/PAUSE SLOT SLOT + 1 2 BUTTON 3 4 +``` + +Your app can get a reference to the expanded controller with the `CastContext.DefaultExpandedMediaControlsViewController` property and can get a reference to the mini controller if using `CastContext.CreateMiniMediaControlsViewController` method. + +Each slot can contain either a framework button, a custom button, or be empty. The list of framework control buttons are defined as: + +| UIMediaButtonType | Description | +|----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **None** | No button, results in empty space at a button position. | +| **PlayPauseToggle** | A default button that toggles between play and pause states. | +| **SkipNext** | A default "next" button. When tapped, playback moves to the next media item in the queue. It becomes disabled if there is no next media item in the queue. | +| **SkipPrevious** | A default "previous" button. When tapped, playback moves to the previous media item in the queue. It becomes disabled if there is no previous media item in the queue. | +| **Rewind30Seconds** | A default "rewind 30 seconds" button. When tapped, playback skips 30 seconds back in the currently playing media item. | +| **Forward30Seconds** | A default "forward 30 seconds" button. When tapped, playback skips 30 seconds forward in the currently playing media item. | +| **MuteToggle** | A default "mute toggle" button. When tapped, the receiver's mute state is toggled. | +| **ClosedCaptions** | A default "closed captions" button. When the button is tapped, the media tracks selection UI is displayed to the user. | +| **Stop** | A default "stop" button. When the button is tapped, playback of the current media item is terminated on the receiver. | +| **Custom** | A button created and managed by the client. | + +Add a button as follows: + +* To add a framework button to a bar requires only a call to `IUIMediaButtonBarProtocol.SetButtonType` method. +* To add a custom button to a bar, your app must call `IUIMediaButtonBarProtocol.SetButtonType` with buttonType parameter set to `UIMediaButtonType.Custom`, and then call `IUIMediaButtonBarProtocol.SetCustomButton` passing the `UIButton` with the same index. + +## How Volume Control Works + +The Cast framework automatically manages the volume for the sender app. The framework automatically synchronizes with the receiver volume for the supplied UI widgets. To sync a slider provided by the app, use `UIDeviceVolumeController`. + +## Using Media Tracks + +A media track can be an audio or video stream object, or a text object (subtitle or caption). + +> ***Note:*** *Currently, the Styled and Default Media Receivers allow you to use only the text tracks with the API. To work with the audio and video tracks, you must develop a Custom Receiver.* + +A `MediaTrack` object represents a track. It consists of a unique numeric identifier and other attributes such as a content ID and title. A `MediaTrack` instance can be created as follows: + +```csharp +var track = new MediaTrack (1, + "https://some-url/caption_en.vtt", + "text/vtt", + MediaTrackType.Text, + MediaTextTrackSubtype.Captions, + "English Captions", + "en", + null); +``` + +A media item can have multiple tracks; for example, it can have multiple subtitles (each for a different language) or multiple alternative audio streams (for different languages). `MediaInformation` is the class that represents a media item. To associate a collection of `MediaTrack` objects with a media item, your app should update its `MediaTracks` property. Your app needs to make his association before it loads the media to the receiver, as in the following code: + +```csharp +MediaTrack [] tracks = { track }; + +var mediaInformation = new MediaInformation ("https://some-url/", + MediaStreamType.None, + "video/mp4", + metadata, + 0, + tracks, + null, + null); +``` + +Activate one or more tracks that were associated with the media item (after the media is loaded) by calling `RemoteMediaClient.SetActiveTrackIDs` method and passing the IDs of the tracks to be activated. For example, the following code activates the captions track created above: + +```csharp +remoteMediaClient.SetActiveTrackIDs (new nuint [] { 1 }); +``` + +To deactivate a track on the current media item, call `RemoteMediaClient.SetActiveTrackIDs` method with an empty array or `null`. The following code disables the captions track: + +```csharp +remoteMediaClient.SetActiveTrackIDs (new nuint [] { }); +// or +remoteMediaClient.SetActiveTrackIDs (null); +``` + +### Styling Text Tracks + +The `MediaTextTrackStyle` class encapsulates the style information of a text track. A track style can be applied to the currently playing media item by calling `RemoteMediaClient.SetTextTrackStyle` method. The track style created in the code below turns text red (FF) at 50% opacity (80) and sets a serif font: + +```csharp +var textTrackStyle = MediaTextTrackStyle.CreateDefault (); +textTrackStyle.ForegroundColor = new Color ("#FF000080"); +textTrackStyle.FontFamily = "serif"; +styleChangeRequest = remoteMediaClient.SetTextTrackStyle (textTrackStyle); +``` + +You can use the returned GCKRequest object for tracking this request. + +```csharp +styleChangeRequest.Completed += (sender, e) => { + var request = sender as Request; + + if (request == styleChangeRequest) { + Console.WriteLine ("Style update completed."); + styleChangeRequest = null; + } +}; +``` + +See **Status updates** section below for more information. Apps should allow users to update the style for text tracks, either using the settings provided by the system or by the app itself. There is a default style provided (in iOS 7 and later), which can be retrieved via the static method `MediaTextTrackStyle.CreateDefault`. The following text track style elements can be changed: + +* Foreground (text) color and opacity +* Background color and opacity +* Edge type +* Edge Color +* Font Scale +* Font Family +* Font Style + +### Receive Status Updates + +When multiple senders are connected to the same receiver, it is important for each sender to be aware of the changes on the receiver even if those changes were initiated from other senders. + +> ***Note:*** *this is important for all apps, not only those that explicitly support multiple senders, because some Cast devices have control inputs (remotes, buttons) that behave as virtual senders, affecting the status on the receiver.* + +To this end, your app should register a `IRemoteMediaClientListener`. If the `MediaTextTrackStyle` of the current media changes, then all of the connected senders will be notified through both the `MediaControlChannelDelegate.DidUpdateMetadata` interface method or `MediaControlChannel.StatusUpdated` event and the `MediaControlChannelDelegate.DidUpdateStatus` interface method or `MediaControlChannel.MetadataUpdated` event. In this case, the receiver classes do not verify whether the new style is different from the previous one and notifies all the connected senders regardless. If, however, the list of active tracks is updated, only the `MediaControlChannelDelegate.DidUpdateStatus` interface method or `MediaControlChannel.MetadataUpdated` event in connected senders will be notified. + +> ***Note:*** *The list of tracks associated with the currently loaded media cannot be changed. If needed, you have to update the tracks information on the MediaInformation object and reload the media.* + +### Satisfy CORS Requirements + +For adaptive media streaming, Google Cast requires the presence of CORS headers, but even simple mp4 media streams require CORS if they include Tracks. If you want to enable Tracks for any media, you must enable CORS for both your track streams and your media streams. So, if you do not have CORS headers available for your simple mp4 media on your server, and you then add a simple subtitle track, you will not be able to stream your media unless you update your server to include the appropriate CORS header. In addition, you need to allow at least the following headers: **Content-Type**, **Accept-Encoding**, and **Range**. Note that the last two headers are additional headers that you may not have needed previously. + +## Add a Custom Channel + +Cast v3.x retains the `CastChannel` and `GenericChannel` classes of Cast v2.x. The former class is meant to be subclassed to implement non-trivial channels that have associated state. The latter class is provided as an alternative to subclassing; it passes its received messages to a delegate so that they can be processed elsewhere. + +In Cast v2.x, custom channels were enabled/disabled by registering/unregistering them with the `DeviceManager`. That class is deprecated in Cast SDK v3.x; channels must now be registered/unregistered with the `CastSession` instead, using its `AddChannel` and `RemoveChannel` methods. + +The Cast framework provides two ways to create a channel to send custom messages to a receiver: + +1. `CastChannel` class is meant to be subclassed to implement non-trivial channels that have associated state. +2. `GenericChannel` is provided as an alternative to subclassing; it passes its received messages to a delegate so that they can be processed elsewhere. + +Channels must be registered/unregistered with the `CastSession` class, using its `AddChannel` and `RemoveChannel` methods. + +Here is an example of a GCKCastChannel implementation: + +```csharp +public class MyTextChannel : CastChannel +{ + public MyTextChannel (string protocolNamespace) : base (protocolNamespace) + { + } + + public override void DidReceiveTextMessage (string message) + { + Console.WriteLine ($"Received message: {message}"); + } +} +``` + +A channel can be registered at any time; if the session is not currently in a connected state, the channel will automatically become connected when the session itself is connected, provided that the channel's namespace is present in the receiver app metadata's list of supported namespaces. + +Each custom channel is defined by a unique namespace and must start with the prefix `urn:x-cast:`, for example, `urn:x-cast:com.example.custom`. It is possible to have multiple custom channels, each with a unique namespace. The receiver app can also send and receive messages using the same namespace: + +```csharp +var myTextChannel = new MyTextChannel ("urn:x-cast:com.google.cast.sample.helloworld"); +castSession.AddChannel (myTextChannel); +myTextChannel.SendTextMessage ("Hello World!"); +``` + +To provide logic that needs to execute when a particular channel becomes connected or disconnected, override the `DidConnect` and `DidDisconnect` methods if using `CastChannel`, or provide implementations for the `DidConnect` and `DidDisconnect` methods of the `IGenericChannelDelegate` interface or `Connected` and `Disconnected` events if using `GenericChannel`. + +## Use Queueing + +The Cast framework provides queueing APIs that support the creation of lists of content items, such as video or audio streams, to play sequentially on the Cast receiver. The queue of content items may be edited, reordered, updated, and so forth. + +Review the [Google Cast Autoplay UX Guidelines][9] for best practices when designing an autoplay/queueing experience for Cast. + +The Cast receiver classes maintain the queue and responds to operations on the queue as long as the queue has at least one item currently active (playing or paused). Senders can join the session and add items to the queue. The receiver maintains a session for queue items until the last item completes playback or the sender stops the playback and terminates the session, or until a sender loads a new queue on the receiver. By default, the receiver does not maintain any information about terminated queues. Once the last item in the queue finishes, the media session ends and the queue vanishes. + +> ***Note:*** *The [styled media receiver][10] and [default media receiver][11] do not support a queue of images; only a queue of audio or video streams is supported in the styled and default receivers.* + +### Create and Load Media Queue Items + +In iOS, a media queue item is represented in the Cast framework as a `MediaQueueItem` instance. When you create a media queue item, if you are using the Media Player Library with adaptive content, you can set the preload time so that the player can begin buffering the media queue item before the item ahead of it in the queue finishes playing. Setting the item's autoplay attribute to true allows the receiver to play it automatically. For example, you can use a builder pattern to create your media queue item as follows: + +```csharp +var builder = new MediaQueueItemBuilder { + MediaInformation = mediaInformation, + Autoplay = true, + PreloadTime = NSUserDefaults.StandardUserDefaults.DoubleForKey (prefPreloadTimeKey) +}; +MediaQueueItem newItem = builder.Build (); +``` + +Load an array of media queue items in the queue by using the appropriate `QueueLoadItems` method of the `RemoteMediaClient` class. + +### Receive Media Queue Status Update + +When the receiver loads a media queue item, it assigns a unique ID to the item which persists for the duration of the session (and the life of the queue). You can learn the status of the queue indicating which item is currently loaded (it might not be playing), loading, or preloaded. You can also get an ordered list of all the items in the queue. The `MediaStatus` class provides this status information: + +* PreloadedItemID property - The ID of the item that is currently preloaded, if any. +* LoadingItemID property - The ID of the item that is currently loading, +* CurrentItemID property - The ID of the current queue item, if any. +* QueueItemCount property - Returns the number of items in the playback queue. +* QueueItemAt method - Returns the item at the specified index in the playback queue. + +Use these members together with the other media status members to inform your app about the status of the queue and the items in the queue. In addition to media status updates from the receiver, you can listen for changes to the queue by implementing `IRemoteMediaClientListener.DidUpdateQueue` interface method. + +> ***Note:*** *To provide the best user experience, the sender app must show the next autoplay item in the queue in the sender UI.* + +### Edit the Queue + +To work with the items in the queue, use the queue methods of `RemoteMediaClient`, you have several APIs. These let you load an array of items into a new queue, insert items into an existing queue, update the properties of items in the queue, make an item jump forward or backward in the queue, set the properties of the queue itself (for example, change the RepeatMode that selects the next item), remove items from the queue, and reorder the items in the queue. + +## Supporting Autoplay + +See [Autoplay & Queueing APIs][12]. + +## Override Image Selection and Caching + +Various components of the framework (namely the Cast dialog, the mini controller, the expanded controller, and the `UIMediaController` if so configured) will display artwork for the currently casting media. The URLs to the image artwork are typically included in the `MediaMetadata` for the media, but the sender app may have an alternate source for the URLs. The `IUIImagePicker` interface defines a means for selecting an appropriate image for a given usage and desired size. It has a single method, `GetImage`, which takes a `UIImageHints` object and a `MediaMetadata` object as parameters, and returns a `Image` object as a result. The framework provides a default implementation of `IUIImagePicker` which always selects the first image in the list of images in the `MediaMetadata` object, but the app can provide an alternate implementation by setting the ImagePicker property of the `CastContext` singleton. + +The `IUIImageCache` interface defines a means of caching images that are downloaded by the framework via HTTPS. The framework provides a default implementation of `UIImageCache` which stores downloaded image files in the app's cache directory, but the app can provide an alternate implementation by setting the ImageCache property of the CastContext singleton. + +--- + +# Apply Custom Styles to Your iOS App + +The Cast framework enables you to style the font, color, and images of UI elements of the default widgets in your Cast application, giving the views a look and feel that matches the rest of your app. This styling mechanism is available only with Cast iOS SDK v3 or later. + +The following section shows you how to apply custom styles to any of the Cast widgets or group of widgets. + +## Applying a style to a UI element of a widget + +This procedure uses the example of setting the body text color of your app's mini controller to red. + +1. Look in the [table of views and styles](#table-of-views-and-styles) to find the view name of the widget or group of widgets that you want to style. Group names are marked with ▼. + + Example: `MiniController` widget view + +2. Find the names of the attributes you want to change from the list of properties in the corresponding style class listed in this table. + + Example: `BodyTextColor` is a property of the `UIStyleAttributesMiniController` class. + +3. Write the code. + + Example: + + ```csharp + var castStyle = UIStyle.SharedInstance; + castStyle.CastViews.MediaControl.MiniController.BodyTextColor = UIColor.Red; + // Assign colors, fonts and images to other properties, as needed + castStyle.ApplyStyle (); + ``` + + Descriptions of each line of code: + + 1. Get the shared instance of `Google.Cast.UIStyle`. + 2. Create a color using the Apple class `UIColor`, then assign the color to the `BodyTextColor` property of the `MiniController`. + 3. Similarly, assign other colors, fonts (using `UIFont`), and images (using `UIImage`) to properties of widgets and groups. + 4. Use the `ApplyStyle` method to refresh all currently visible views with their assigned styles. + +Use this pattern for applying any style to any UI element of any widget. + +## Table of Views and Styles + +This table shows the seven widget views and three groups (marked with ▼) that you can apply styles to. + +| View name | Type | Style class | +|-------------------------|--------|-----------------------------------------| +| ▼ CastViews | Group | UIStyleAttributesCastViews | +| ▼ DeviceControl | Group | UIStyleAttributesDeviceControl | +| DeviceChooser | Widget | UIStyleAttributesDeviceChooser | +| ConnectionController | Widget | UIStyleAttributesConnectionController | +| GuestModePairingDialog | Widget | UIStyleAttributesGuestModePairingDialog | +| ▼ MediaControl | Group | UIStyleAttributesMediaControl | +| MiniController | Widget | UIStyleAttributesMiniController | +| ExpandedController | Widget | UIStyleAttributesExpandedController | +| TrackSelector | Widget | UIStyleAttributesTrackSelector | +| Instructions | Widget | UIStyleAttributesInstructions | + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://developers.google.com/cast/docs/ios_sender_integrate) to see original Google documentation._ + +[1]: https://developers.google.com/cast/docs/design_checklist/cast-button#prompting +[2]: https://developers.google.com/cast/docs/design_checklist/cast-button#sender-cast-icon-available +[3]: https://developers.google.com/cast/docs/design_checklist/sender#sender-mini-controller +[4]: https://developers.google.com/cast/docs/design_checklist/sender#sender-control-elements +[5]: https://developers.google.com/cast/docs/design_checklist/sender#sender-mini-controller +[6]: https://developers.google.com/cast/docs/design_checklist/sender#mini-controller +[7]: https://developers.google.com/cast/docs/android_sender_integrate#the_cast_ux_widgets +[8]: https://developer.apple.com/reference/uikit/uiappearance +[9]: https://developers.google.com/cast/downloads/GoogleCastAutoplayUXGuidelines.pdf +[10]: https://developers.google.com/cast/docs/styled_receiver +[11]: https://developers.google.com/cast/docs/receiver_apps#default +[12]: https://developers.google.com/cast/docs/autoplay diff --git a/docs/Google/GoogleCloudMessaging/Details.md b/docs/Google/GoogleCloudMessaging/Details.md new file mode 100755 index 00000000..7e6df8cd --- /dev/null +++ b/docs/Google/GoogleCloudMessaging/Details.md @@ -0,0 +1,14 @@ +Send data from your server to your users' devices, and receive messages from devices on the same connection. The GCM service handles all aspects of queueing of messages and delivery to client applications running on target devices, and it is completely free. + +### Versatile Messaging Targets + +Distribute messages to your client app in any of three ways — to single devices, to groups of devices, or to devices subscribed to topics. + +### Downstream Messaging + +For purposes such as alerting users, chat messaging or kicking off background processing before the user opens the client app, GCM provides a reliable and battery-efficient connection between your server and devices. + +### Upstream Messaging + +Send acknowledgments, chats, and other messages from devices back to your server over GCM’s reliable and battery-efficient connection channel. + diff --git a/docs/Google/GoogleCloudMessaging/GettingStarted.md b/docs/Google/GoogleCloudMessaging/GettingStarted.md new file mode 100755 index 00000000..9b94d115 --- /dev/null +++ b/docs/Google/GoogleCloudMessaging/GettingStarted.md @@ -0,0 +1,204 @@ +Configuring APNS +---------------- + +To allow Google to send APNS notifications on your behalf, you will need to export your Developer and/or your Production APNS certificates from the Apple Developer portal as .p12 files. + +For more information on this process, see [Apple's Documentation][2]. + + + +Configuring your App +-------------------- + +Google provides an easy to use configuration web tool to generate a config file for your app: + +1. Open [Google's configuration tool][1] to create a config file for your app. +2. Enter your app's name and iOS Bundle ID and click continue +3. Upload your Developer APNS .p12 certificate. You may also want to upload your production .p12 certificate at this time as well. +3. Click *Enable Cloud Messaging* +4. Click *continue* to generate the configuration files +5. Click *Download Google-Service-Info.plist* +6. Add `GoogleService-Info.plist` to your Xamarin.iOS app project and set the *Build Action* to `BundleResource` +7. Note the *Sender ID* value. You will use this in your code as the `GCM_SENDER_ID` value. + + + +Setting up your AppDelegate +--------------------------- + +Your `AppDelegate` will contain most of the GCM related code. + +In your `FinishedLaunching` method you should start GCM and request to register for remote notifications: + +```csharp +public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) +{ + // Configure and Start GCM + var gcmConfig = Google.GoogleCloudMessaging.Config.DefaultConfig; + gcmConfig.ReceiverDelegate = this; + Service.SharedInstance.Start (gcmConfig); + + // Register for remote notifications + var notTypes = UIUserNotificationType.Sound | UIUserNotificationType.Alert | UIUserNotificationType.Badge; + var settings = UIUserNotificationSettings.GetSettingsForTypes (notTypes, null); + UIApplication.SharedApplication.RegisterUserNotificationSettings (settings); + UIApplication.SharedApplication.RegisterForRemoteNotifications (); + + // Your user code, if any, here + + return true; +} +``` + +Since in the code above the GCM Config's delegate was set to `this` (the AppDelegate), you should also implement `IReceiverDelegate` in your `AppDelegate` class: + +Next, add an override for handling the registration for remote notifications: + +```csharp +public override void RegisteredForRemoteNotifications (UIApplication application, NSData deviceToken) +{ + // Save our token in memory for future calls to GCM + DeviceToken = deviceToken; + + // Configure and start Instance ID + var config = Google.InstanceID.Config.DefaultConfig; + InstanceId.SharedInstance.Start (config); + + // Get a GCM token + GetToken (); +} +``` + +In this method, you will save the `deviceToken` to a variable for later use, as well as configure and start InstanceID which is needed to obtain a GCM Registration Token. + +Finally, this method calls `GetToken()` to actually request a GCM Registration token, which is defined as: + +```csharp +void GetToken () +{ + // Register APNS Token to GCM + var options = new NSDictionary (); + options.SetValueForKey (DeviceToken, Constants.RegisterAPNSOption); + options.SetValueForKey (new NSNumber(true), Constants.APNSServerTypeSandboxOption); + + // Get our token + InstanceId.SharedInstance.Token ( + GCM_SENDER_ID, + Constants.ScopeGCM, + options, + (token, error) => Log ("GCM Registration ID: " + token)); +} +``` + +If the `error` value is null in the callback that was passed into the `Token()` method, you should have a valid GCM Registration token which you will then use on your server to send messages to this device. + + +### Receiving Notifications + +Notifications (both APNS and from GCM) will occur inside of the `DidReceiveRemoteNotification` override. When you receive a message you should inform GCM that you successfully received it: + +```csharp +public override void DidReceiveRemoteNotification (UIApplication application, NSDictionary userInfo, Action completionHandler) +{ + // Your own notification handling logic here + + // Notify GCM we received the message + Service.SharedInstance.AppDidReceiveMessage (userInfo); +} +``` + + + +### Connecting to GCM Servers Directly + +When your application is in the foreground you should connect directly to GCM's servers: + +```csharp +public override void OnActivated (UIApplication application) +{ + Service.SharedInstance.Connect (error => { + if (error != null) + Console.WriteLine ("Could not connect to GCM: {0}", error.LocalizedDescription); + else + Console.WriteLine ("Connected to GCM"); + }); +} +``` + +It's also important to disconnect from the GCM Server when your application enters the background so you can continue to receive APNS notifications instead: + +```csharp +public override void DidEnterBackground (UIApplication application) +{ + Service.SharedInstance.Disconnect (); +} +``` + + +### Unregistering from GCM + +It's possible to unregister from GCM by deleting your token: + +```csharp +public void DeleteToken () +{ + InstanceId.SharedInstance.DeleteToken ( + GCM_SENDER_ID, + Constants.ScopeGCM, + error => { + // Callback, non-null error if there was a problem + if (error != null) + Console.WriteLine ("Deleted Token"); + else + Console.WriteLine ("Error deleting token"); + }); +} +``` + +### Sending Upstream Messages + +One of the advantages to being directly connected to GCM's Servers when your app is in the foreground is the ability to send upstream messages back to the server: + +```csharp +int messageId = 1; + +// We can send upstream messages back to GCM +public void SendUpstreamMessage () +{ + var msg = new NSDictionary (); + msg.SetValueForKey (new NSString ("1234"), new NSString ("userId")); + msg.SetValueForKey (new NSString ("hello world"), new NSString ("msg")); + + var to = GCM_SENDER_ID + "@gcm.googleapis.com"; + + Service.SharedInstance.SendMessage (msg, to, (messageId++).ToString ()); +} +``` + +### Handling Callbacks for Upstream Messages + +Since you implemented `IReceiverDelegate` in your AppDelegate, you can add some methods to be invoked as callbacks for upstream messages: + +```csharp +[Export ("didDeleteMessagesOnServer")] +public void DidDeleteMessagesOnServer () +{ + // ... +} + +[Export ("didSendDataMessageWithID:")] +public void DidSendDataMessage (string messageID) +{ + // ... +} + +[Export ("willSendDataMessageWithID:error:")] +public void WillSendDataMessage (string messageID, NSError error) +{ + // ... +} +``` + + +[1]: https://developers.google.com/mobile/add?platform=ios&cntapi=signin +[2]: https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/ConfiguringPushNotifications/ConfiguringPushNotifications.html \ No newline at end of file diff --git a/docs/Google/InstanceID/Details.md b/docs/Google/InstanceID/Details.md new file mode 100755 index 00000000..0153bc15 --- /dev/null +++ b/docs/Google/InstanceID/Details.md @@ -0,0 +1,32 @@ +Instance ID provides a unique ID per instance of your Android and iOS apps. + +## Key features + +In addition to providing unique IDs for authentication, Instance ID can generate security tokens for use with other services. Other features include: + +### Generate Security Tokens + +Instance ID provides a simple API to generate security tokens that authorize third parties to access your app's server side managed resources. Use these tokens now to authorize push messages for your apps via Google Cloud Messaging. + + +### Confirm app device is active + +The Instance ID server can tell you when the device on which your app is installed was last used. Use this to decide whether to keep data from your app or send a push message to reengage with your users. + + +### Identify and track apps + +Instance ID is unique across all app instances across the world, so your database can use it to uniquely identify and track app instances. Your server-side code can verify, via the Instance ID cloud service, that an Instance ID is genuine and is the same ID as the original app that registered with your server. For privacy, your app can delete an Instance ID so it is no longer associated with any history in the database. The next time your app calls Instance ID it will get an entirely new Instance ID with no relationship to its previous one. + + +## Instance ID lifecycle + +The Instance ID service issues an InstanceID when your app comes online. The InstanceID is backed by a public/private key pair with the private key stored on the local device and the public key registered with the Instance ID service. + +Your app can request a fresh InstanceID whenever needed using the `GetID (..)` method. Your app can store it on your server if you have one that supports your app. + +Your app can request tokens from the Instance ID service as needed using the `GetToken (..)` method, and like InstanceID, your app can also store tokens on your own server. All tokens issued to your app belong to the app's InstanceID. + +Tokens are unique and secure, but your app or the Instance ID service may need to refresh tokens in the event of a security issue or when a user uninstalls and reinstalls your app during device restoration. Your app must implement a listener to respond to token refresh requests from the Instance ID service. + +![InstanceIDLifecycle](https://developers.google.com/instance-id/guides/images/iid-lifecycle.png) \ No newline at end of file diff --git a/docs/Google/InstanceID/GettingStarted.md b/docs/Google/InstanceID/GettingStarted.md new file mode 100755 index 00000000..40017eba --- /dev/null +++ b/docs/Google/InstanceID/GettingStarted.md @@ -0,0 +1,29 @@ + +## Generating / Fetching an Instance ID + +Before attempting to generate or fetch an Instance ID, you must start the InstanceID engine and pass it an instance of `Config`. You can use the Default instance of `Config`: + +```csharp +InstanceId.SharedInstance.Start (Config.DefaultConfig); +``` + +After you've called `Start (..)`, generating and fetching an InstanceID is done the same way: + +```csharp +var instanceId = await InstanceId.SharedInstance.GetIDAsync (); +``` + +This call may take several seconds to complete, so you should plan on not blocking the UI when making this call. You should always receive the same Instance ID value back after the first time you call this method. + +## Deleting an Instance ID + +It is possible to delete an Instance ID. Once you have deleted a particular Instance ID the next time you call `GetID` or `GetIDAsync` to generate an Instance ID, you will receive a new value. + +To delete an instance ID, simply call `DeleteID` or `DeleteIDAsync`: + +```csharp +await InstanceId.SharedInstance.DeleteIDAsync (); +``` + +**Important Note**: In this version of Instance ID, Google is internally invoking the callback handler that you pass into the `DeleteID` method. They first call back with a non-null error value (error Code 1), and then immediately call back again with a null error, indicating the call succeeded. To work around this, the `DeleteIDAsync` call ignores + \ No newline at end of file diff --git a/docs/Google/Maps/Details.md b/docs/Google/Maps/Details.md new file mode 100755 index 00000000..5c8b5c61 --- /dev/null +++ b/docs/Google/Maps/Details.md @@ -0,0 +1,62 @@ +With the Google Maps SDK for iOS, you can add maps based on Google maps data to your application. The SDK automatically handles access to the Google Maps servers, map display, and response to user gestures such as clicks and drags. You can also add markers, polylines, ground overlays and info windows to your map. These objects provide additional information for map locations, and allow user interaction with the map. + +Showing a Map +============= + +### AppDelegate + +```csharp +using Google.Maps; +... + +const string MapsApiKey = ""; + +public override bool FinishedLaunching (UIApplication app, NSDictionary options) +{ + MapServices.ProvideAPIKey (MapsApiKey); + ... +} +``` + +### Your View Controller + +```csharp +using Google.Maps; +... + +MapView mapView; + +public override void LoadView () +{ + base.LoadView (); + + CameraPosition camera = CameraPosition.FromCamera (latitude: 37.797865, + longitude: -122.402526, + zoom: 6); + mapView = MapView.FromCamera (CGRect.Empty, camera); + mapView.MyLocationEnabled = true; + + View = mapView; +} + +public override void ViewWillAppear (bool animated) +{ + base.ViewWillAppear (animated); + mapView.StartRendering (); +} + +public override void ViewWillDisappear (bool animated) +{ + mapView.StopRendering (); + base.ViewWillDisappear (animated); +} +``` + +Attribution Requirements +======================== + +If you use the Google Maps SDK for iOS in your application, you must include the attribution text as part of a legal notices section in your application. Including legal notices as an independent menu item, or as part of an "About" menu item, is recommended. + +You can get the attribution text by making a call to `Google.Maps.MapServices.OpenSourceLicenseInfo` + +*Screenshots assembled with [PlaceIt](http://placeit.breezi.com/).* diff --git a/docs/Google/Maps/GettingStarted.md b/docs/Google/Maps/GettingStarted.md new file mode 100755 index 00000000..f25213cd --- /dev/null +++ b/docs/Google/Maps/GettingStarted.md @@ -0,0 +1,112 @@ +Showing a Map +============= + +### AppDelegate + +```csharp +using Google.Maps; +... + +const string MapsApiKey = ""; + +public override bool FinishedLaunching (UIApplication app, NSDictionary options) +{ + MapServices.ProvideAPIKey (MapsApiKey); + ... +} +``` + +### Your View Controller + +```csharp +using Google.Maps; +... + +MapView mapView; + +public override void LoadView () +{ + base.LoadView (); + + // Create a GMSCameraPosition that tells the map to display the + // coordinate 37.79,-122.40 at zoom level 6. + var camera = CameraPosition.FromCamera (latitude: 37.79, + longitude: -122.40, + zoom: 6); + mapView = MapView.FromCamera (CGRect.Empty, camera); + mapView.MyLocationEnabled = true; + View = mapView; +} +``` + +Attribution Requirements +======================== + +If you use the Google Maps SDK for iOS in your application, you must include the attribution text as part of a legal notices section in your application. Including legal notices as an independent menu item, or as part of an "About" menu item, is recommended. + +You can get the attribution text by making a call to `Google.Maps.MapServices.OpenSourceLicenseInfo` + +Enable the required APIs on the Google Developers Console +=== + +You need to activate the Google Maps SDK for iOS, and optionally the Google Places API for iOS, in your project on the Google Developers Console. + +If you want to be guided through the process and activate both the APIs automatically, click [this link][1]. + +Alternatively, you can activate the APIs yourself in the Developers Console, by doing the following: + +- Go to the [Google Developers Console][2]. +- Select a project, or create a new one. +- Enable the **Google Maps SDK for iOS**, and optionally the **Google Places API for iOS**: [Open the API Library][3] in the Google Developers Console. If prompted, select a project or create a new one. Select the **Enabled APIs** link in the API section to see a list of all your enabled APIs. Make sure that the API is on the list of enabled APIs. If you have not enabled it, select the API from the list of APIs, then select the **Enable API** button for the API. + +The Google Maps API Key +======================= + +Using an API key enables you to monitor your application's Maps API usage, and ensures that Google can contact you about your application if necessary. The key is free, you can use it with any of your applications that call the Maps API, and it supports an unlimited number of users. You obtain a Maps API key from the Google APIs Console by providing your application's bundle identifier. Once you have the key, you add it to your AppDelegate as described in the next section. + +### Obtaining an API Key + +You can obtain a key for your app in the [Google APIs Console](https://code.google.com/apis/console/). + +1. Click on "Use Google APIs" blue card. +2. In the sidebar on the left, select Credentials. +3. If your project doesn't already have an iOS API key, create one now by selecting **Add credentials** > **API key** > **iOS key**. +4. In the resulting dialog, enter your app's bundle identifier, such as `com.example.myapp`. +5. Click **Create**. Your new iOS API key appears in the list of API keys for your project. +6. Add your API key to your `AppDelegate` class as follows: + +```csharp +using Google.Maps; +... + +[Register ("AppDelegate")] +public partial class AppDelegate : UIApplicationDelegate +{ + const string MapsApiKey = ""; + + public override bool FinishedLaunching (UIApplication app, NSDictionary options) + { + MapServices.ProvideAPIKey (MapsApiKey); + ... + } +} +``` + +Declare the URL schemes used by the API +=== + +With iOS 9, apps must declare the URL schemes that they intend to open, by specifying the schemes in the app's Info.plist file. The Google Maps SDK for iOS opens the Google Maps mobile app when the user clicks the Google logo on the map, and your app therefore needs to declare the relevant URL schemes. + +To declare the URL schemes used by the Google Maps SDK for iOS, add the following lines to your Info.plist: + +```xml +LSApplicationQueriesSchemes + + googlechromes + comgooglemaps + +``` + +[1]: https://console.developers.google.com/flows/enableapi?apiid=placesios,maps_ios_backend&keyType=CLIENT_SIDE_IOS +[2]: https://console.developers.google.com/ +[3]: https://console.developers.google.com/project/_/apiui/apis/library \ No newline at end of file diff --git a/docs/Google/MobileAds/Details.md b/docs/Google/MobileAds/Details.md new file mode 100755 index 00000000..92438cf7 --- /dev/null +++ b/docs/Google/MobileAds/Details.md @@ -0,0 +1,12 @@ +Quickly monetize your app with Google AdMob, one of the world's largest mobile +advertising platforms. This SDK features: + +* Simplified APIs +* Access to the latest HTML5 ad units from AdMob + +With this component, developers can easily incorporate Google AdMob ads into their mobile +apps. Mobile-friendly text and image banners are available, along with rich, full-screen +web apps known as interstitials. An ever-growing set of "calls-to-action" are supported +in response to user-generated events, including redirection to the App Store, iTunes, +mapping applications, videos, and the dialer. Ads can be targeted by location and +demographic data. \ No newline at end of file diff --git a/docs/Google/MobileAds/GettingStarted.md b/docs/Google/MobileAds/GettingStarted.md new file mode 100755 index 00000000..59a2cab7 --- /dev/null +++ b/docs/Google/MobileAds/GettingStarted.md @@ -0,0 +1,71 @@ +Quickly monetize your app with Google AdMob, one of the world's largest mobile +advertising platforms. This SDK features: + +* Simplified APIs +* Access to the latest HTML5 ad units from AdMob + +With this component, developers can easily incorporate Google AdMob ads into their mobile +apps. Mobile-friendly text and image banners are available, along with rich, full-screen +web apps known as interstitials. An ever-growing set of "calls-to-action" are supported +in response to user-generated events, including redirection to the App Store, iTunes, +mapping applications, videos, and the dialer. Ads can be targeted by location and +demographic data. + +## Banners + +Creating a banner ad unit and loading the request: + +```csharp +using Google.MobileAds; +... + +const string bannerId = ""; + +BannerView adView; +bool viewOnScreen = false; + +public void AddBanner () +{ + // Setup your BannerView, review AdSizeCons class for more Ad sizes. + adView = new BannerView (size: AdSizeCons.Banner, origin: new CGPoint (-10, 0)) { + AdUnitID = bannerId, + RootViewController = this + }; + + // Wire AdReceived event to know when the Ad is ready to be displayed + adView.AdReceived += (object sender, EventArgs e) => { + if (!viewOnScreen) View.AddSubview (adView); + viewOnScreen = true; + }; + + adView.LoadRequest (Request.GetDefaultRequest ()); +} +``` + +## Interstitial Ad + +Creating an Interstitial ad and loading the request: + +```csharp +using Google.MobileAds; +... + +const string intersitialId = ""; + +Interstitial adInterstitial; + +public void AddInterstitial () +{ + adInterstitial = new Interstitial (intersitialId); + adInterstitial.LoadRequest (Request.GetDefaultRequest ()); + + // We need to wait until the Intersitial is ready to show + do { + await Task.Delay (100); + } while (!adInterstitial.IsReady); + + // Once is ready, show the ad on Main thread + InvokeOnMainThread (() => adInterstitial.PresentFromRootViewController (navController)); +} + +``` \ No newline at end of file diff --git a/docs/Google/Places/Details.md b/docs/Google/Places/Details.md new file mode 100755 index 00000000..72ebd4ed --- /dev/null +++ b/docs/Google/Places/Details.md @@ -0,0 +1,12 @@ +Get data from the same database used by Google Maps. Places features over 100 million businesses and points of interest that are updated frequently through owner-verified listings and user-moderated contributions. + +* **Place picker**: Add the built-in place picker UI widget to your app, so users can choose from a set of nearby places displayed on a map. +* **Place autocomplete**: Assist users by automatically completing the name and address of a place as they type. +* **Place details**: Retrieve rich details about a place, including name, address, phone number, website link and more. + +Attribution Requirements +======================== + +If you use the Google Places SDK for iOS in your application, you must include the attribution text as part of a legal notices section in your application. Including legal notices as an independent menu item, or as part of an "About" menu item, is recommended. + +You can get the attribution text by making a call to `Google.Places.PlacesClient.OpenSourceLicenseInfo` diff --git a/docs/Google/Places/GettingStarted.md b/docs/Google/Places/GettingStarted.md new file mode 100755 index 00000000..0fe75cb2 --- /dev/null +++ b/docs/Google/Places/GettingStarted.md @@ -0,0 +1,1093 @@ +# Get Started with the Places API for iOS + +> _**Note**_: _The Google Places API for iOS enforces a default limit of 1,000 requests per 24 hour period. You can request an increase by following a simple review process. See the instructions in the [usage limits][1] guide._ + +## Add Google Places for iOS to your project + +### Via Components Store + +1. In your Visual Studio project, do double click on **Components** folder. +2. If needed, click on **Log in to your Xamarin Account** button and enter your credentials. +3. Click on **Get More Components** button +4. Search for **Google Places for iOS** and click on the result +5. Click on **Add to App** + +### Via NuGet + +1. In your Visual Studio project, do double click on **Packages** folder. +2. Search for **Xamarin.Google.iOS.Places** +3. Do double click on the result to install it or check the result and click on **Add Package** button + +### Get an API key + +To get started using the Google Places API for iOS, follow these steps to get an API key: + +1. Go to the [Google API Console][2]. +2. Create or select a project. +3. Click **Continue** to enable the Google Places API for iOS. +4. On the **Credentials** page, get an **API key**. + + Note: If you have a key with iOS restrictions, you may use that key. You can use the same key with any of your iOS applications within the same project. + +5. From the dialog displaying the API key, select **Restrict key** to set an iOS restriction on the API key. +6. In the **Restrictions** section, select **iOS apps**, then enter your app's bundle identifier. For example: `com.example.helloplaces`. +7. Click **Save**. + +Your new iOS key appears in the list of API keys for your project. An API key is a string of characters, something like this: + +``` +AIzaSyBdVl-cTICSwYKrZ95SuvNw7dbMuDt1KG0 +``` + +You can also [look up an existing key][3] in the Google API Console. + +For more information on using the Google API Console, see [API Console Help][4]. + +### Add the API key to your application + +The following code examples show how to add the API key to an application: + +* Add the following namespace statement: + + ```csharp + using Google.Places; + ``` + +* Add the following to your `FinishedLaunching` method, replacing `YOUR_API_KEY` with your API key: + + ```csharp + PlacesClient.ProvideApiKey (YOUR_API_KEY); + ``` + +* If you are also using the Place Picker, you will need to add the Google.Maps components or NuGet and add your key again as shown here: + + ```csharp + using Google.Maps; + + MapServices.ProvideAPIKey (YOUR_API_KEY); + ``` + +--- + +# Place Picker + +> **Deprecation notice: `PlacePicker` class** +> +> _**Notice**_: _The implementation of the place picker has changed. As of version 2.3 of the Google Places API for iOS, the `PlacePicker` class is deprecated, replaced by `PlacePickerViewController` class. Use of the `PlacePicker` class will only be supported until May 1, 2018. We recommend that you update your code to use `PlacePickerViewController` when possible._ + +The place picker is a simple and yet flexible built-in UI widget, part of the Google Places API for iOS. + +## Introducing the place picker + +The `PlacePickerViewController` class provides a UI that displays an interactive map and a list of nearby places, including places corresponding to geographical addresses and local businesses. Users can choose a place, and your app can then retrieve the details of the selected place. + +The place picker provides the following advantages over developing your own UI widget: + +* The user experience is consistent with other apps using the place picker, including Google apps and third parties. This means users of your app already know how to interact with the place picker. +* The map is integrated into the place picker. +* Accessibility is built in. +* It saves development time. + +The place picker features autocomplete functionality, which displays place predictions based on user search input. This functionality is present in all place picker integrations, so you don't need to do anything extra to enable autocomplete. For more information about autocomplete, go to [Place Autocomplete](#place-autocomplete) section below. + +## Request location authorization + +If your app uses the place picker, you must request permission to use location services. First add one or both of the following keys to your **Info.plist** file, to request 'when in use' or 'always' authorization: + +* `NSLocationWhenInUseUsageDescription` +* `NSLocationAlwaysUsageDescription` + +For the place picker, it's enough to request 'when in use' authorization, but you may want to request 'always' authorization for other functionality in your app. For each key, add a string informing the user why you need the location services. For example: + +```xml +NSLocationWhenInUseUsageDescription +Show your location on the map +``` + +## Add a place picker + +The code snippet below shows how to create a `PlacePickerViewController` instance centered at the current device location, and display details of the selected place: + +```csharp +public partial class ViewController : UIViewController, IPlacePickerViewControllerDelegate +{ + partial void BtnPickPlace_TouchUpInside (UIButton sender) + { + var config = new PlacePickerConfig (null); + placePickerViewController = new PlacePickerViewController (config) { Delegate = this }; + PresentViewController (placePickerViewController, true, null); + } + + // To receive the results from the place picker 'self' will need to conform to + // IPlacePickerViewControllerDelegate interface and implement this code. + public void DidPickPlace (PlacePickerViewController viewController, Place place) + { + // Dismiss the place picker, as it cannot dismiss itself. + DismissViewController (true, null); + + Console.WriteLine ($"Place name: {place.Name}"); + Console.WriteLine ($"Place address: {place.FormattedAddress}"); + Console.WriteLine ($"Place attributions: {place.Attributions}"); + } + + [Export ("placePickerDidCancel:")] + void DidCancel (PlacePickerViewController viewController) + { + // Dismiss the place picker, as it cannot dismiss itself. + DismissViewController (true, null); + Console.WriteLine ("No place selected"); + } +} +``` + +As shown in the above code sample, you can initalize the place picker with a given configuration using a `PlacePickerConfig` object. If a value of `null` is assigned to the viewport, the place picker will center on the current device location. To center on a specific location, specify a **viewport** containing a `CoordinateBounds` object defining the initial rectangular map area to display. The following example shows creating a `CoordinateBounds` to center the map on Sydney, Australia: + +```csharp +var center = new CLLocationCoordinate2D (-33.865143, 151.2099); +var northEast = new CLLocationCoordinate2D (center.Latitude + .001, center.Longitude + .001); +var southWest = new CLLocationCoordinate2D (center.Latitude - .001, center.Longitude - .001); +var viewport = new Google.Maps.CoordinateBounds (northEast, southWest); +var config = new PlacePickerConfig (viewport); +``` + +To be notified when the user selects a place you must set the delegate of the place picker to be an object implementing `IPlacePickerViewControllerDelegate` interface. When you do this your app will receive a callback to the `DidPickPlace` method implemented by the delegate and is passed the place the user selected. If the user did not select a place, `DidCancel` method is called instead. + +As the place picker is a normal view controller it can be displayed any way you want. For example, in a popover, fullscreen, pushed onto a navigation stack, or even as part of a custom app UI. Because of this flexibility the place picker is unable to dismiss itself, so your app must programmatically dismiss it when `DidPickPlace` is called. In some cases it may be necessary to dismiss the place picker from `DidCancel` as well. + +## Display attributions in your app + +When your app displays information obtained via the place picker, the app must also display attributions. See this [Attribution's documentation][8] to learn more about this. + +## Old Client Libraries + +Prior to version 2.3 of the Google Places API for iOS `PlacePicker` was the only available way to use the place picker. This class has now been deprecated and it is recommended that `PlacePickerViewController` is used instead. This new class is more flexible and is not limited to being displayed full-screen. + +--- + +# Current Place + +Using the Google Places API for iOS, you can discover the place where the device is currently located. That is, the place at the device's currently-reported location. Examples of places include local businesses, points of interest, and geographic locations. + +## Request location authorization + +If your app uses `PlacesClient` `CurrentPlace` instance method, you must request permission to use location services. Add the `NSLocationWhenInUseUsageDescription` key to your **Info.plist** file, to define the string informing the user why you need the location services. For example: + +```xml +NSLocationWhenInUseUsageDescription +Show your location on the map +``` + +If you want to call `PlacesClient` `CurrentPlace` instance method when the app is in the background, without triggering a confirmation dialog, take the following steps prior to making the call: + +1. Add the `NSLocationAlwaysUsageDescription` key to your **Info.plist** file. +2. Call `RequestAlwaysAuthorization` on any instance of `CLLocationManager` before calling the method. + +Request authorization from `CLLocationManager` as follows: + +```csharp +locationManager.RequestAlwaysAuthorization (); +``` + +## Usage limits + +Use of the `PlacesClient` `CurrentPlace` instance method is subject to tiered query limits. See the documentation on [usage limits][1]. + +## Get the current location + +To find the local business or other place where the device is currently located, call `PlacesClient.CurrentPlace`. Include a callback method to handle the results. + +The API invokes the specified callback method, passing in an array of `PlaceLikelihood` objects. + +Each `PlaceLikelihood` object represents a place. For each place, the result includes an indication of the likelihood that the place is the right one. A higher value means a greater probability that the place is the best match. The buffer may be empty, if there is no known place corresponding to the device location. + +The following code sample retrieves the list of places where the device is most likely to be located, and logs the name, likelihood, and other details for each place: + +```csharp +placesClient.CurrentPlace ((likelihoodList, error) => { + if (error != null) { + Console.WriteLine ($"Pick Place error: {error.LocalizedDescription}"); + return; + } + + foreach (var likelihood in likelihoodList?.Likelihoods) { + var place = likelihood.Place; + Console.WriteLine ($"Current Place name: {place.Name} at likelihood: {likelihood.Likelihood}"); + Console.WriteLine ($"Current Place address: {place.FormattedAddress}"); + Console.WriteLine ($"Current Place attributions: {place.Attributions}"); + Console.WriteLine ($"Current Place Id: {place.Id}"); + } +}); +``` + +Notes about the likelihood values: + +* The likelihood provides a relative probability of the place being the best match within the list of returned places for a single request. You can't compare likelihoods across different requests. +* The value of the likelihood will be between 0 and 1.0. +* The sum of the likelihoods in a returned array of `PlaceLikelihood` objects is always less than or equal to 1.0. Note that the sum isn't necessarily 1.0. + +For example, to represent a 55% likelihood that the correct place is Place A, and a 35% likelihood that it's Place B, the likelihood array has two members: Place A with a likelihood of 0.55 and Place B with a likelihood of 0.35. + +## Display attributions in your app + +When your app displays information obtained from `PlacesClient` `CurrentPlace` instance method, the app must also display attributions. See this [Attribution's documentation][8] to learn more about this. + +--- + +# Place Autocomplete + +The autocomplete service in the Google Places API for iOS returns place predictions in response to user search queries. As the user types, the autocomplete service returns suggestions for places such as businesses, addresses and points of interest. + +You can add autocomplete to your app in the following ways: + +* Add an autocomplete UI control to save development time and ensure a consistent user experience. +* Get place predictions programmatically to create a customized user experience. + +## Add an autocomplete UI control + +The autocomplete UI control is a search dialog with built-in autocomplete functionality. As a user enters search terms, the control presents a list of predicted places to choose from. When the user makes a selection, a `Place` instance is returned, which your app can then use to get details about the selected place. + +You can add the autocomplete UI control to your app in the following ways: + +* Add a full-screen control +* Add a results controller +* Use a table data source + +### Add a full-screen control + +Use the full-screen control when you want a modal context, where the autocomplete UI temporarily replaces the UI of your app until the user has made their selection. This functionality is provided by the `AutocompleteViewController` class. When the user selects a place, your app receives a callback. + +To add a full-screen control to your app: + +* Create a UI element in your main app to launch the autocomplete UI control, for example a touch handler on a `UIButton`. +* Implement the `IAutocompleteViewControllerDelegate` interface in the parent view controller. +* Create an instance of `AutocompleteViewController` and assign the parent view controller as the `Delegate` property. +* Present the `AutocompleteViewController` using `PresentViewController` method. +* Handle the user's selection in the `DidAutocomplete` interface method. +* Dismiss the controller in the `DidAutocomplete`, `DidFailAutocomplete`, and `WasCancelled` interface methods. + +The following example demonstrates one possible way to launch `AutocompleteViewController` in response to the user tapping on a button: + +```csharp +using UIKit; +using Foundation; +using Google.Places; + +public partial class ViewController : UIViewController, IAutocompleteViewControllerDelegate +{ + partial void BtnShow_TouchUpInside (UIButton sender) + { + var autocompleteViewController = new AutocompleteViewController { Delegate = this }; + PresentViewController (autocompleteViewController, true, null); + } + + #region AutocompleteViewController Delegate + + public void DidAutocomplete (AutocompleteViewController viewController, Place place) + { + // Handle the user's selection. + DismissViewController (true, null); + Console.WriteLine ($"Place name: {place.Name}"); + Console.WriteLine ($"Place address: {place.FormattedAddress}"); + Console.WriteLine ($"Place attributions: {place.Attributions}"); + } + + public void DidFailAutocomplete (AutocompleteViewController viewController, NSError error) + { + // TODO: handle the error. + DismissViewController (true, null); + Console.WriteLine ($"Error: {error.LocalizedDescription}"); + } + + // User canceled the operation. + public void WasCancelled (AutocompleteViewController viewController) + { + DismissViewController (true, null); + } + + // Turn the network activity indicator on and off again. + [Export ("didRequestAutocompletePredictions:")] + public void DidRequestAutocompletePredictions (AutocompleteViewController viewController) + { + UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true; + } + + [Export ("didUpdateAutocompletePredictions:")] + public void DidUpdateAutocompletePredictions (AutocompleteViewController viewController) + { + UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false; + } + + #endregion +} +``` + +### Add a results controller + +Use a results controller when you want more control over the text input UI. The results controller dynamically toggles the visibility of the results list based on input UI focus. + +To add a results controller to your app: + +1. Create a `AutocompleteResultsViewController`. +2. Implement the `IAutocompleteResultsViewControllerDelegate` interface in the parent view controller and assign the parent view controller as the `Delegate` property. +3. Create a `UISearchController` object, passing in the `AutocompleteResultsViewController` as the results controller argument. +4. Set the `AutocompleteResultsViewController` as the `SearchResultsUpdater` property of the `UISearchController`. +5. Add the `SearchBar` for the `UISearchController` to your app's UI. +6. Handle the user's selection in the `DidAutocomplete` interface method. + +There are several ways to place the search bar of a `UISearchController` into your app's UI: + +* Add a search bar to the navigation bar +* Add a search bar to the top of a view +* Add a search bar using popover results + +#### Add a search bar to the navigation bar + +The following code example demonstrates adding a results controller, adding the `SearchBar` to the navigation bar, and handling the user's selection: + +```csharp +using UIKit; +using Foundation; +using Google.Places; + +public partial class ViewController : UIViewController, IAutocompleteResultsViewControllerDelegate +{ + UISearchController searchController; + AutocompleteResultsViewController autocompleteResultsViewController; + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + + autocompleteResultsViewController = new AutocompleteResultsViewController { Delegate = this }; + searchController = new UISearchController (autocompleteResultsViewController) { + // Prevent the navigation bar from being hidden when searching. + HidesNavigationBarDuringPresentation = false, + SearchResultsUpdater = autocompleteResultsViewController + }; + + // Put the search bar in the navigation bar. + searchController.SearchBar.SizeToFit (); + NavigationItem.TitleView = searchController.SearchBar; + + // When UISearchController presents the results view, present it in + // this view controller, not one further up the chain. + DefinesPresentationContext = true; + } + + #region AutocompleteResultsViewController Delegate + + // Handle the user's selection. + public void DidAutocomplete (AutocompleteResultsViewController resultsController, Place place) + { + searchController.Active = false; + + // Do something with the selected place. + Console.WriteLine ($"Place name: {place.Name}"); + Console.WriteLine ($"Place address: {place.FormattedAddress}"); + Console.WriteLine ($"Place attributions: {place.Attributions}"); + } + + public void DidFailAutocomplete (AutocompleteResultsViewController resultsController, NSError error) + { + searchController.Active = false; + + // TODO: handle the error. + Console.WriteLine ($"Error: {error.LocalizedDescription}"); + } + + // Turn the network activity indicator on and off again. + [Export ("didRequestAutocompletePredictionsForResultsController:")] + public void DidRequestAutocompletePredictions (AutocompleteResultsViewController resultsController) + { + UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true; + } + + [Export ("didUpdateAutocompletePredictionsForResultsController:")] + public void DidUpdateAutocompletePredictions (AutocompleteResultsViewController resultsController) + { + UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false; + } + + #endregion +} +``` + +> _**Note:**_ _For the search bar to display properly, your app's view controller must be enclosed within a `UINavigationController`._ + +#### Add a search bar to the top of a view + +The following code example shows adding the `SearchBar` to the top of a view: + +```csharp +using UIKit; +using Foundation; +using CoreGraphics; +using Google.Places; + +public partial class ViewController : UIViewController, IAutocompleteResultsViewControllerDelegate +{ + UISearchController searchController; + AutocompleteResultsViewController autocompleteResultsViewController; + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + + autocompleteResultsViewController = new AutocompleteResultsViewController { Delegate = this }; + searchController = new UISearchController (autocompleteResultsViewController) { + // Prevent the navigation bar from being hidden when searching. + HidesNavigationBarDuringPresentation = false, + SearchResultsUpdater = autocompleteResultsViewController + }; + + var subview = new UIView (new CGRect (0, 65, 350, 45)); + subview.AddSubview (searchController.SearchBar); + View.AddSubview (subview); + searchController.SearchBar.SizeToFit (); + + // When UISearchController presents the results view, present it in + // this view controller, not one further up the chain. + DefinesPresentationContext = true; + } + + #region AutocompleteResultsViewController Delegate + + // Handle the user's selection. + public void DidAutocomplete (AutocompleteResultsViewController resultsController, Place place) + { + searchController.Active = false; + + // Do something with the selected place. + Console.WriteLine ($"Place name: {place.Name}"); + Console.WriteLine ($"Place address: {place.FormattedAddress}"); + Console.WriteLine ($"Place attributions: {place.Attributions}"); + } + + public void DidFailAutocomplete (AutocompleteResultsViewController resultsController, NSError error) + { + searchController.Active = false; + + // TODO: handle the error. + Console.WriteLine ($"Error: {error.LocalizedDescription}"); + } + + // Turn the network activity indicator on and off again. + [Export ("didRequestAutocompletePredictionsForResultsController:")] + public void DidRequestAutocompletePredictions (AutocompleteResultsViewController resultsController) + { + UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true; + } + + [Export ("didUpdateAutocompletePredictionsForResultsController:")] + public void DidUpdateAutocompletePredictions (AutocompleteResultsViewController resultsController) + { + UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false; + } + + #endregion +} +``` + +By default, `UISearchController` hides the navigation bar when presenting (this can be disabled). In cases where the navigation bar is visible and opaque, `UISearchController` does not set the placement correctly. Use the following code as a workaround: + +```csharp +NavigationController.NavigationBar.Translucent = false; +searchController.HidesNavigationBarDuringPresentation = false; + +// This makes the view area include the nav bar even though it is opaque. +// Adjust the view placement down. +ExtendedLayoutIncludesOpaqueBars = true; +EdgesForExtendedLayout = UIRectEdge.Top; +``` + +#### Add a search bar using popover results + +The following code example shows placing a search bar on the right side of the navigation bar, and displaying results in a popover: + +> _**Caution:**_ _Popovers will only appear on iPad, and on the iPhone 6+ in landscape mode. On all other devices this will fallback to a fullscreen view controller._ + +```csharp +```csharp +using UIKit; +using Foundation; +using CoreGraphics; +using Google.Places; + +public partial class ViewController : UIViewController, IAutocompleteResultsViewControllerDelegate +{ + UISearchController searchController; + AutocompleteResultsViewController autocompleteResultsViewController; + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + + autocompleteResultsViewController = new AutocompleteResultsViewController { Delegate = this }; + searchController = new UISearchController (autocompleteResultsViewController) { + // Prevent the navigation bar from being hidden when searching. + HidesNavigationBarDuringPresentation = false, + ModalPresentationStyle = UIModalPresentationStyle.Popover, + SearchResultsUpdater = autocompleteResultsViewController + }; + + // Add the search bar to the right of the nav bar, + // use a popover to display the results. + // Set an explicit size as we don't want to use the entire nav bar. + searchController.SearchBar.Frame = new CGRect (0, 0, 250, 44); + NavigationItem.RightBarButtonItem = new UIBarButtonItem (searchController.SearchBar); + + // When UISearchController presents the results view, present it in + // this view controller, not one further up the chain. + DefinesPresentationContext = true; + } + + #region AutocompleteResultsViewController Delegate + + // Handle the user's selection. + public void DidAutocomplete (AutocompleteResultsViewController resultsController, Place place) + { + searchController.Active = false; + + // Do something with the selected place. + Console.WriteLine ($"Place name: {place.Name}"); + Console.WriteLine ($"Place address: {place.FormattedAddress}"); + Console.WriteLine ($"Place attributions: {place.Attributions}"); + } + + public void DidFailAutocomplete (AutocompleteResultsViewController resultsController, NSError error) + { + searchController.Active = false; + + // TODO: handle the error. + Console.WriteLine ($"Error: {error.LocalizedDescription}"); + } + + // Turn the network activity indicator on and off again. + [Export ("didRequestAutocompletePredictionsForResultsController:")] + public void DidRequestAutocompletePredictions (AutocompleteResultsViewController resultsController) + { + UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true; + } + + [Export ("didUpdateAutocompletePredictionsForResultsController:")] + public void DidUpdateAutocompletePredictions (AutocompleteResultsViewController resultsController) + { + UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false; + } + + #endregion +} +``` + +> _**Note:**_ _For the search bar to display properly, your app's view controller must be enclosed within a `UINavigationController`._ + +### Use a table data source + +If your app must support iOS 7, you can use the `AutocompleteTableDataSource` class to drive the table view of a `UISearchDisplayController`. + +* The use of `UISearchDisplayController` is recommended only for iOS 7 support. We recommend using `UISearchController` in all other cases. +* The full-screen control is also supported in iOS 7. + +To use `AutocompleteTableDataSource` to display a search controller: + +1. Implement the `IAutocompleteTableDataSourceDelegate` and `IUISearchDisplayDelegate` interfaces in the parent view controller. +2. Create a `AutocompleteTableDataSource` instance and assign the parent view controller as the `Delegate` property. +3. Create an instance of `UISearchDisplayController`. +4. Add the `SearchBar` for the `UISearchController` to your app's UI. +5. Handle the user's selection in the `DidAutocomplete` interface method. +6. Dismiss the controller in the `DidAutocomplete`, `DidFailAutocomplete`, and `WasCancelled` interface methods. + +The following code example demonstrates using the `AutocompleteTableDataSource` class to drive the table view of a `UISearchDisplayController`. + +```csharp +using UIKit; +using Foundation; +using CoreGraphics; +using Google.Places; + +public partial class ViewController : UIViewController, IAutocompleteTableDataSourceDelegate, IUISearchDisplayDelegate +{ + UISearchBar searchBar; + AutocompleteTableDataSource autocompleteTableDataSource; + UISearchDisplayController searchDisplayController; + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + // Perform any additional setup after loading the view, typically from a nib. + + searchBar = new UISearchBar (new CGRect (0, 0, 250, 44)); + autocompleteTableDataSource = new AutocompleteTableDataSource { Delegate = this }; + searchDisplayController = new UISearchDisplayController (searchBar, this) { + SearchResultsDataSource = autocompleteTableDataSource, + SearchResultsDelegate = autocompleteTableDataSource, + Delegate = this + }; + + View.AddSubview (searchBar); + } + + #region UISearchDisplay Delegate + + [Export ("searchDisplayController:shouldReloadTableForSearchString:")] + public bool ShouldReloadForSearchString (UISearchDisplayController controller, string forSearchString) + { + return false; + } + + #endregion + + #region AutocompleteTable Data Source Delegate + + public void DidAutocomplete (AutocompleteTableDataSource tableDataSource, Place place) + { + searchDisplayController.Active = false; + + // Do something with the selected place. + Console.WriteLine ($"Place name: {place.Name}"); + Console.WriteLine ($"Place address: {place.FormattedAddress}"); + Console.WriteLine ($"Place attributions: {place.Attributions}"); + } + + public void DidFailAutocomplete (AutocompleteTableDataSource tableDataSource, NSError error) + { + searchDisplayController.Active = false; + + // TODO: handle the error. + Console.WriteLine ($"Error: {error.LocalizedDescription}"); + } + + [Export ("tableDataSource:didSelectPrediction:")] + public bool DidSelectPrediction (AutocompleteTableDataSource tableDataSource, AutocompletePrediction prediction) + { + return true; + } + + [Export ("didRequestAutocompletePredictionsForTableDataSource:")] + public void DidRequestAutocompletePredictions (AutocompleteTableDataSource tableDataSource) + { + // Turn the network activity indicator on. + UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true; + + // Reload table data. + searchDisplayController.SearchResultsTableView.ReloadData (); + } + + [Export ("didUpdateAutocompletePredictionsForTableDataSource:")] + public void DidUpdateAutocompletePredictions (AutocompleteTableDataSource tableDataSource) + { + // Turn the network activity indicator off. + UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false; + + // Reload table data. + searchDisplayController.SearchResultsTableView.ReloadData (); + } + + #endregion +} +``` + +> _**Note:**_ _The `UISearchDisplayController` API does not support the concept of asynchronous data updates, so it is necessary to force table updates by reloading table data in the `DidUpdateAutocompletePredictions` and `DidRequestAutocompletePredictions` methods of the `IAutoCompleteResultsDelegate` interface._ + +## Get place predictions programmatically + +You can create a custom search UI as an alternative to the UI provided by the autocomplete widget. To do this, your app must get place predictions programmatically. Your app can get a list of predicted place names and/or addresses in one of the following ways: + +* Call `PlacesClient` +* Use the fetcher + +### Call PlacesClient + +To get a list of predicted place names and/or addresses, call the `PlacesClient` `Autocomplete` instance method with the following parameters: + +* An `autocompleteQuery` string containing the text typed by the user. +* A `CoordinateBounds` object biasing the results to a specific area specified by latitude and longitude bounds. +* A `AutocompleteFilter` restricting the results to a specific type of place. To retrieve all types, supply a value of `null`. The following place types are supported in the filter: + * `PlacesAutocompleteTypeFilter.NoFilter` – An empty filter; all results are returned. + * `PlacesAutocompleteTypeFilter.Geocode` – Returns only geocoding results, rather than businesses. Use this request to disambiguate results where the specified location may be indeterminate. + * `PlacesAutocompleteTypeFilter.Address` – Returns only autocomplete results with a precise address. Use this type when you know the user is looking for a fully specified address. + * `PlacesAutocompleteTypeFilter.Establishment` – Returns only places that are businesses. + * `PlacesAutocompleteTypeFilter.Region` – Returns only places that match one of the following types: + * locality + * sublocality + * postal_code + * country + * administrative_area_level_1 + * administrative_area_level_2 + * `PlacesAutocompleteTypeFilter.City` – Returns only results matching `locality` or `administrative_area_level_3`. + +For more information, see [place types][5]. + +* A callback method to handle the returned predictions. + +The code examples below show a simplified call to `Autocomplete` method: + +```csharp +void PlaceAutocomplete () +{ + var filter = new AutocompleteFilter { Type = PlacesAutocompleteTypeFilter.Establishment }; + PlacesClient.SharedInstance.Autocomplete ("Sydney Oper", null, filter, AutocompletePredictionsHandler); + + void AutocompletePredictionsHandler (AutocompletePrediction [] results, NSError error) + { + if (error != null) { + Console.WriteLine ($"Autocomplete error: {error.LocalizedDescription}"); + return; + } + + foreach (var result in results) + Console.WriteLine ($"Result: {result.AttributedFullText} with Place Id: {result.PlaceId}"); + } +} +``` + +The API invokes the specified callback method, passing in an array of `AutocompletePrediction` objects. + +Each `AutocompletePrediction` object contains the following information: + +* `AttributedFullText` – The full text of the prediction, in the form of an `NSAttributedString`. For example, 'Sydney Opera House, Sydney, New South Wales, Australia'. Every text range that matches the user input has an attribute, `AutocompleteMatchAttribute`. You can use this attribute to highlight the matching text in the user's query, for example, as shown below. +* `PlaceId` – The place ID of the predicted place. A place ID is a textual identifier that uniquely identifies a place. For more information about place IDs, see the [Place ID overview][6]. + +The following code example illustrates how to highlight in bold text the parts of the result that match text in the user's query, using EnumerateAttribute: + +```csharp +var regularFont = UIFont.SystemFontOfSize (UIFont.LabelFontSize); +var boldFont = UIFont.BoldSystemFontOfSize (UIFont.LabelFontSize); + +var bolded = prediction.AttributedFullText.MutableCopy () as NSMutableAttributedString; +bolded.EnumerateAttribute (AutocompletePrediction.AutocompleteMatchAttribute, new NSRange (0, bolded.Length), 0, EnumerateAttributeCallback); +label.AttributedText = bolded; + +void EnumerateAttributeCallback (NSObject value, NSRange range, ref bool stop) +{ + var font = value == null ? regularFont : boldFont; + bolded.AddAttribute (UIStringAttributeKey.Font, font, range); +} +``` + +### Use the fetcher + +If you want to build your own autocomplete control from scratch, you can use `AutocompleteFetcher`, which wraps the `Autocomplete` method on `PlacesClient` class. The fetcher throttles requests, returning only results for the most recently entered search text. It provides no UI elements. + +To implement `AutocompleteFetcher`, take the following steps: + +1. Implement the `IAutocompleteFetcherDelegate` interface. +2. Create a `AutocompleteFetcher` object. +3. Call `SourceTextHasChanged` on the fetcher as the user types. +4. Handle predictions and errors using the `DidAutcomplete` and `DidFailAutocomplete` interface methods. + +The following code example demonstrates using the fetcher to take user input and display place matches in a text view. Functionality for selecting a place has been omitted. `FetcherSampleViewController` derives from `UIViewController` in FetcherSampleViewController.h. + +```csharp +using UIKit; +using Foundation; +using CoreLocation; +using CoreGraphics; +using Google.Places; +using Google.Maps; + +public partial class FetcherSampleViewController : UIViewController, IAutocompleteFetcherDelegate +{ + UITextField textField; + UITextView resultText; + AutocompleteFetcher fetcher; + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + // Perform any additional setup after loading the view, typically from a nib. + + View.BackgroundColor = UIColor.White; + EdgesForExtendedLayout = UIRectEdge.All; + + // Set bounds to inner-west Sydney Australia. + var neBoundsCorner = new CLLocationCoordinate2D (-33.843366, 151.134002); + var swBoundsCorner = new CLLocationCoordinate2D (-33.875725, 151.200349); + var bounds = new CoordinateBounds (neBoundsCorner, swBoundsCorner); + + // Set up the autocomplete filter. + var filter = new AutocompleteFilter { Type = PlacesAutocompleteTypeFilter.Establishment }; + + // Create the fetcher. + fetcher = new AutocompleteFetcher (bounds, filter) { Delegate = this }; + + textField = new UITextField (new CGRect (5, 10, View.Bounds.Width - 5, 44)) { + AutoresizingMask = UIViewAutoresizing.FlexibleWidth + }; + textField.ValueChanged += TextField_ValueChanged; + + resultText = new UITextView (new CGRect (0, 45, View.Bounds.Width, View.Bounds.Height - 45)) { + BackgroundColor = UIColor.FromWhiteAlpha (.95f, 1), + Text = "No Results", + Editable = false + }; + + View.AddSubview (textField); + View.AddSubview (resultText); + } + + void TextField_ValueChanged (object sender, EventArgs e) + { + fetcher.SourceTextHasChanged (textField.Text); + } + + #region AutocompleteFetcher Delegate + + public void DidAutocomplete (AutocompletePrediction [] predictions) + { + var results = string.Empty; + foreach (var prediction in predictions) + results += prediction.AttributedPrimaryText.Value; + resultText.Text = results; + } + + public void DidFailAutocomplete (NSError error) + { + resultText.Text = error.LocalizedDescription; + } + + #endregion +} +``` + +## Set the CoordinateBounds of autocomplete + +You can supply a `CoordinateBounds` to autocomplete to guide the supplied completions. For example, if you already have a Google Map in your view controller, you can use the bounds of the current viewport to bias autocomplete results: + +```csharp +void PlaceAutocomplete () +{ + var visibleRegion = mapView.Projection.VisibleRegion; + var bounds = new Google.Maps.CoordinateBounds (visibleRegion.FarLeft, visibleRegion.NearRight); + + var filter = new AutocompleteFilter { Type = PlacesAutocompleteTypeFilter.Establishment }; + PlacesClient.SharedInstance.Autocomplete ("Sydney Oper", bounds, filter, AutocompletePredictionsHandler); + + void AutocompletePredictionsHandler (AutocompletePrediction [] results, NSError error) + { + if (error != null) { + Console.WriteLine ($"Autocomplete error: {error.LocalizedDescription}"); + return; + } + + foreach (var result in results) + Console.WriteLine ($"Result: {result.AttributedFullText} with Place Id: {result.PlaceId}"); + } +} +``` + +## Usage limits + +Use of the `PlacesClient` `Autocomplete` instance method is subject to tiered query limits. See the documentation on [usage limits][1]. + +## Display attributions in your app + +* If your app uses the autocomplete service programmatically, your UI must either display a 'Powered by Google' attribution, or appear within a Google-branded map. +* If your app uses the autocomplete UI control no additional action is required (the required attribution is displayed by default). +* If you retrieve and display additional place information after getting a place by ID, you must display third-party attributions too. + +See this [Attribution's documentation][8] to learn more about this. + +## Controlling the network activity indicator + +To control the network activity indicator in the applications status bar you must implement the appropriate optional delegate methods for the autocomplete class you are using and turn the network indicator on and off yourself. + +* For `AutocompleteViewController` you must implement the interface methods `DidRequestAutocompletePredictions` and `DidUpdateAutocompletePredictions`. +* For `AutocompleteResultsViewController` you must implement the interface methods `DidRequestAutocompletePredictions` and `DidUpdateAutocompletePredictions`. +* For `AutocompleteTableDataSource` you must implement the delegate methods `DidRequestAutocompletePredictions` and `DidUpdateAutocompletePredictions`. + +By implementing these methods and setting `UIApplication.SharedApplication.NetworkActivityIndicatorVisible` to `true` and `false` respectively the status bar will correctly match the autocomplete UI. + +## Troubleshooting + +Although a wide variety of errors can occur, the majority of the errors your app is likely to experience are usually caused by configuration errors (for example, the wrong API key was used, or the API key was configured incorrectly), or quota errors (your app has exceeded its quota). See [Usage Limits][1] for more information about quotas. + +Errors that occur in the use of the autocomplete controls are returned in the `DidFailAutocomplete` method of the various interface protocols. The code property of the supplied `NSError` object is set to one of the values of the `PlacesErrorCode` enumeration. + +--- + +# Place Photos + +You can use the Google Places API for iOS to request place photos to display in your application. Photos returned by the photos service come from a variety of sources, including business owners and user-contributed photos. To retrieve images for a place, you must take the following steps: + +1. Call `PlacesClient` `LookUpPhotos` instance method, passing a string with a place ID and a callback. This will call the callback with a `PlacePhotoMetadataList` object. +2. On the `PlacePhotoMetadataList` object access the `Results` property and select the photos to load from the array. +3. For each `PlacePhotoMetadata` to load from this list call `PlacesClient` `LoadPlacePhoto` instance method. These will call the callback with a usable UIImage. + +> _**Note:**_ _Whenever you display a place photo make sure that the attribution guidelines are being followed. See this [Attribution's documentation][8] to learn more about this._ + +## Sample code + +The following example method takes a place ID and gets the first photo in the returned list. You can use this method as a template for the method you will create in your own app: + +```csharp +void LoadFirstPlacePhoto (string placeId) +{ + PlacesClient.SharedInstance.LookUpPhotos (placeId, PlacePhotoMetadataResultHandler); + + void PlacePhotoMetadataResultHandler (PlacePhotoMetadataList photos, NSError error) + { + if (error != null) { + Console.WriteLine ($"Error: {error.LocalizedDescription}"); + return; + } + + var firstPhoto = photos.Results.FirstOrDefault (); + if (firstPhoto != null) + LoadImage (firstPhoto); + } +} + +void LoadImage (PlacePhotoMetadata photoMetadata) +{ + PlacesClient.SharedInstance.LoadPlacePhoto (photoMetadata, PlacePhotoImageResultHandler); + + void PlacePhotoImageResultHandler (UIImage photo, NSError error) + { + if (error != null) { + Console.WriteLine ($"Error: {error.LocalizedDescription}"); + return; + } + + imageView.Image = photo; + attributionTextView.AttributedText = photoMetadata.Attributions; + } +} +``` + +## Caching + +Photos loaded using `PlacesClient` `LoadPlacePhoto` instance method are cached both on disk and in-memory by the [Foundation URL loading system][7] in the shared `NSUrlCache`. + +To configure the caching behavior you can change the shared URL cache using `NSUrlCache.SharedCache` in your application delegate's `FinishedLaunching` method. + +If you do not want your application to share a `NSUrlCache` with the Google Places API for iOS you can create a new `NSUrlCache` and use this exclusively within your app without setting it as the shared cache. + +## Attributions + +In most cases, place photos can be used without attribution, or will have the required attribution included as part of the image. However, if the returned `PlacePhotoMetadata` instance includes an attribution, you must include the additional attribution in your application wherever you display the image. Note that links in the attribution must be tappable. See this [Attribution's documentation][8] to learn more about this. + +## Usage limits + +Retrieving an image costs one unit of quota; there are no usage limits for retrieving photo metadata. See the documentation on [usage limits][1]. + +--- + +# Place Report + +An app can report that the device is currently located at a particular place. By reporting places that users have confirmed, you can help Google build a local model of the world. You should report that a device is at a place only if you're confident that the user is at the place, at the time when you report it. + +To indicate that a device is located at a specific place, call `PlacesClient` `ReportDeviceAtPlace` instance method, passing the `placeId` of the place you are reporting. You can retrieve this place ID from the `Place` object. For more information about place IDs, see the [place ID overview][6]. + +Reporting the location of a device is similar to a checkin. It's not possible to retrieve the report later, and the report is not linked to the user's account. + +The following sample reports that the device is at Darling Island Wharf, in Pyrmont, Australia: + +```csharp +// Place ID for Darling Island Wharf Pyrmont +var placeId = "ChIJO1H1TzeuEmsR5bJONIMc4jk" +PlacesClient.SharedInstance.ReportDeviceAtPlace (placeId); +``` + +--- + +# Place IDs and Details + +The Google Places API for iOS provides your app with rich information about places, including the place's name and address, the geographical location specified as latitude/longitude coordinates, the type of place (such as night club, pet store, museum), and more. To access this information for a specific place, you can use the place ID, a stable identifier that uniquely identifies a place. + +## Place details + +The `Place` class provides information about a specific place. You can get hold of a `Place` object in the following ways: + +* Call `PlacesClient` `CurrentPlace` instance method. See the guide to [getting current place](#current-place). +* Add a `PlacePicker` UI widget, which allows the user to select a place. Call `PickPlace` and supply a callback to receive the chosen place. See the guide to [adding a place picker](#place-picker). +* Call `PlacesClient` `LookUpPlaceId` instance method, passing a place ID and a callback method. See the [Get a place by ID](#get-a-place-by-id) section below. + +The `Place` class provides the following information: + +* `Name` – The place's name. +* `Id` – The textual identifier for the place. Read more about place IDs in the rest of this page. +* `Coordinate` – The geographical location of the place, specified as latitude and longitude coordinates. +* `OpenNowStatus` – Indicates whether the place is open at the time when the request for place information is made. +* `PhoneNumber` – The place's phone number, in international format. +* `FormattedAddress` – The human-readable address of this location. + + Often this address is equivalent to the postal address. Note that some countries, such as the United Kingdom, do not allow distribution of true postal addresses due to licensing restrictions. + + The formatted address is logically composed of one or more address components. For example, the address "111 8th Avenue, New York, NY" consists of the following components: "111" (the street number), "8th Avenue" (the route), "New York" (the city) and "NY" (the US state). + + Do not parse the formatted address programmatically. Instead you should use the individual address components, which the API response includes in addition to the formatted address field. + +* Rating – An aggregated rating of the place, returned as a float with values ranging from 1.0 to 5.0, based on aggregated user reviews. +* PriceLevel – The price level for this place, returned as an integer with values ranging from 0 (cheapest) to 4 (most expensive). +* Types – A list of place types that characterize this place. See the documentation for the [supported types][9]. +* Website – The URI of the place's website, if known. This is the website maintained by the business or other entity associated with the place. +* Attributions – An `NSAttributedString` containing the attributions that you must display to the user if your app uses place details retrieved from the Google Places API for iOS. For details on retrieving and displaying attributions, See this [Attribution's documentation][8] to learn more about this. +* AddressComponents – An array of `AddressComponent` objects representing components of the address for a place. These components are provided for the purpose of extracting structured information about a place's address, for example finding the city in which a place is located. Do not use these components for address formatting; instead, use the `FormattedAddress` property, which provides a localized formatted address. + + Note the following facts about the `AddressComponents` array: + + * The array of address components may contain more components than the `FormattedAddress`. + * The array does not necessarily include all the political entities that contain an address, apart from those included in the `FormattedAddress`. + * The format of the response is not guaranteed to remain the same between requests. In particular, the number of `AddressComponents` varies based on the address requested and can change over time for the same address. A component can change position in the array. The type of the component can change. A particular component may be missing in a later response. + +## Get a place by ID + +> _**Note:**_ _Use of the `PlacesClient` `LookUpPlaceId` instance method is subject to tiered query limits. See the documentation on [usage limits][1]._ + +A place ID is a textual identifier that uniquely identifies a place. In the Google Places API for iOS, you can retrieve the ID of a place from a `Place` object. You can store the place ID and use it to retrieve the `Place` object again later. + +To get a place by ID, call `PlacesClient` `LookUpPlaceId` instance method, passing a place ID and a callback method. + +The API invokes the specified callback method, passing in a `Place` object. If the place is not found, the place object is `null`. + +```csharp +// A hotel in Saigon with an attribution. +var placeId = "ChIJV4k8_9UodTERU5KXbkYpSYs"; +PlacesClient.SharedInstance.LookUpPlaceId (placeId, PlaceResultHandler); + +void PlaceResultHandler (Place place, NSError error) +{ + if (error != null) { + Console.WriteLine ($"Look up place id query error: {error.LocalizedDescription}"); + return; + } + + if (place == null) { + Console.WriteLine ($"No place details for {placeId}"); + return; + } + + Console.WriteLine ($"Place name: {place.Name}"); + Console.WriteLine ($"Place address: {place.FormattedAddress}"); + Console.WriteLine ($"Place id: {place.Id}"); + Console.WriteLine ($"Place attributions: {place.Attributions}"); +} +``` + +## Display attributions in your app + +When your app displays information obtained from `PlacesClient` `LookUpPlaceId`, the app must also display attributions. See this [Attribution's documentation][8] to learn more about this. + +## More about place IDs + +The place ID used in the Google Places API for iOS is the same identifier as used in the [Google Places API Web Service, Google Places API for Android and other Google APIs][10]. + +There are some circumstances that may cause a place to get a new place ID. For example, this may happen if a business moves to a new location. + +When you request a place by specifying a place ID, you can be confident that you will always receive the same place in the response (if the place still exists). Note, however, that the response may contain a place ID that is different from the one in your request. + +For more information, see the [place ID overview][6]. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://developers.google.com/places/ios-api/start) to see original Google documentation._ + +[1]: https://developers.google.com/places/ios-api/usage +[2]: https://console.developers.google.com/flows/enableapi?apiid=placesios&reusekey=true +[3]: https://console.developers.google.com/project/_/apiui/credential +[4]: https://support.google.com/googleapi +[5]: https://developers.google.com/places/ios-api/supported_types#table3 +[6]: https://developers.google.com/places/ios-api/place-id +[7]: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html +[8]: https://developers.google.com/places/ios-api/attributions +[9]: https://developers.google.com/places/ios-api/supported_types +[10]: https://developers.google.com/places/ diff --git a/docs/Google/PlayGames/Details.md b/docs/Google/PlayGames/Details.md new file mode 100755 index 00000000..644e5e4d --- /dev/null +++ b/docs/Google/PlayGames/Details.md @@ -0,0 +1,3 @@ +Welcome to iOS game development with Google Play games services! + +The Play Games SDK provides cross-platform Google Play games services that lets you easily integrate popular gaming features such as achievements, leaderboards, and multiplayer to your tablet and mobile games. \ No newline at end of file diff --git a/docs/Google/PlayGames/GettingStarted.md b/docs/Google/PlayGames/GettingStarted.md new file mode 100755 index 00000000..8844d466 --- /dev/null +++ b/docs/Google/PlayGames/GettingStarted.md @@ -0,0 +1,239 @@ +## Add your game to the Google Play Developer Console + +Create an entry for your game in the Google Play Developer Console. This enables Google Play games services for your application, and creates an OAuth 2.0 client ID, if you don't already have one. + +1. Add an entry for your iOS game by following the steps described in [Setting Up Google Play Games Services][1]. +2. Take note of your game's OAuth 2.0 [client ID][2]; you will need this later. +3. (Optional) Add achievements and leaderboards to your game by following the steps described in [Configuring Achievements and Leaderboards][3]. +4. Add accounts for other members of your team to test your game by following the steps described in [Publishing Your Game Changes][4]. + +## Add a sign-in and sign-out button + +In your view controller, add a sign-in button and a sign-out button. Make sure your sign-in button conforms to the [Google+ branding guidelines][5]. To reduce your development effort, many of the built-in user-interfaces provided by Google Play games services already include a sign-out option so you don't need to add this manually. + +```csharp +using Google.Play.GameServices; +... + +const string CLIENT_ID = "123456789012.apps.googleusercontent.com"; + +UIButton signinButton; +UIButton signoutButton; + +public override void ViewDidLoad () +{ + base.ViewDidLoad (); + + signinButton = new UIButton (new CGRect (93, 99, 125, 44)); + signinButton.SetTitle ("Sign In", UIControlState.Normal); + signinButton.TouchUpInside += delegate { + Manager.SharedInstance.SignIn (CLIENT_ID, false); + }; + + signoutButton = new UIButton (new CGRect (93, 299, 125, 44)); + signoutButton.SetTitle ("Sign Out", UIControlState.Normal); + signoutButton.TouchUpInside += delegate { + Manager.SharedInstance.Signout (); + }; + + Add (signinButton); + Add (signoutButton); + +} +``` + +When a player signs in to Google, the sign-in process sends them to the Google+ app, Chrome for iOS, or Mobile Safari (in that sequence). After the player signs in, the app opens a URL that points back to your game and contains the information necessary to complete the sign-in process. Make sure your application can handle the URL that redirects back to your game. Using the options provided in the Google Sign-In SDK, it is possible to selectively enable or disable redirection to Google first-party sign-in apps, Chrome/Safari, or in-app webviews. + +Your app must be setup to accept a URL scheme. You can do this by opening your app's `Info.plist` file, and navigating to the *Advanced* tab. + +In the *URL Types* section, add two *URL Type*s: + +- In one *URL type*, specify a unique string in the Identifier field, and specify your client ID in reversed order in the *URL Schemas* field. For example, if your client ID for iOS is `CLIENT_ID_CODE.apps.googleusercontent.com`, then specify `com.googleusercontent.apps.CLIENT_ID_CODE` in the *URL Schemas* field. +- In the other *URL type*, specify a unique string in the Identifier field, and specify your app's bundle identifier (com.company.appname) in the *URL Schemas* field. + +In your AppDelegate file, add `Google.SignIn` to libraries and override the `OpenUrl` method. This method handles the URL that your application receives at the end of the authentication process: + +```csharp +using Google.SignIn; +... + +public override bool OpenUrl (UIApplication application, NSUrl url, string sourceApplication, NSObject annotation) +{ + return SignIn.SharedInstance.HandleUrl (url, sourceApplication, annotation); +} +``` + +In some class of your app (where signin and singout buttons live for example), set `SignIn.SharedInstance.UIDelegate` property: + +```csharp +SignIn.SharedInstance.UIDelegate = this; +``` + +The class assigned to `UIDelegate` property must implement the `ISignInUIDelegate` interface. + +You can now test your application and be able to sign in and out. When testers sign in, they will be redirected to Google+, Chrome, or Safari to complete the sign-in process, and then redirected back to your application. + +## Add a GPGStatusDelegate + +Next, add the code to let your app know that when sign-in process is completed. + +Make a class implements `IStatusDelegate` interface and assign it to `Manager.SharedInstance.StatusDelegate` property. Also, Implement these `IStatusDelegate` methods: `GamesSignInFinished` (to handle completion of player sign-in) and `GamesSignOutFinished` (to handle completion of player sign-out): + +```csharp +public override void ViewDidLoad () +{ + base.ViewDidLoad (); + + Manager.SharedInstance.StatusDelegate = this; +} + +[Export ("didFinishGamesSignInWithError:")] +public void GamesSignInFinished (NSError error) +{ + if (error != null) + Console.WriteLine ("ERROR signing in: {0}", error.LocalizedDescription); + else + Console.WriteLine ("Finished with games sign in!"); + + RefreshYourUI (); +} + + +[Export ("didFinishGamesSignOutWithError:")] +public virtual void GamesSignOutFinished (NSError error) +{ + if (error != null) + Console.WriteLine ("ERROR signing out: {0}", error.LocalizedDescription); + else + Console.WriteLine ("Signed out!"); + + RefreshYourUI (); +} +``` + +After you sign in or you sign out, refresh your UI to determine what button you should show: + +```csharp +void RefreshYourUI () +{ + var signedIn = Manager.SharedInstance.SignedIn; + signinButton.Hidden = signedIn; + signoutButton.Hidden = !signedIn; +} +``` + +Now, when testers finish signing in, the sign-in button will be hidden. When they sign out, the sign-out button will be hidden and the sign-in button should re-appear. + +## Automatically sign in returning players + +You can also sign players in automatically, to avoid having them sign in every time they launch your game. The `Google.Play.GameServices.Manager` will automatically sign the player in when you specify **true** in `Manager.SharedInstance.SignIn` method. This call succeeds if all the following conditions are met: + +- The player has authorized your application in the past; +- The player has not revoked access to your application; and +- The app is not requesting new scopes since the player last signed in. + +Using this behavior, you can sign the player in automatically to your game by adding the `Manager.SharedInstance.SignIn` method to the end of your `ViewDidLoad` method, with silently param (second param) set to **true**. + +```csharp +Manager.SharedInstance.SignIn (CLIENT_ID, true); +``` + +Run your application and notice that, unless you signed out when you last used your application, you are now signed in automatically. + +## Add some interface refinements + +When the application starts player sign-in automatically, there is a small delay between the time sign-in starts and completes. Your game should disable the UI during this time. To do this, use the fact that the `Manager.SharedInstance.SignIn` method returns **true** if it is attempting to sign the player in automatically. You could do something like this: + +``` +using Google.Play.GameServices; +... + +const string CLIENT_ID = "123456789012.apps.googleusercontent.com"; + +UIButton signinButton; +UIButton signoutButton; +bool currentlySigningIn; + +public override void ViewDidLoad () +{ + base.ViewDidLoad (); + + Manager.SharedInstance.StatusDelegate = this; + + signinButton = new UIButton (new CGRect (93, 99, 125, 44)); + signinButton.SetTitle ("Sign In", UIControlState.Normal); + signinButton.TouchUpInside += delegate { + Manager.SharedInstance.SignIn (CLIENT_ID, false); + }; + + signoutButton = new UIButton (new CGRect (93, 299, 125, 44)); + signoutButton.SetTitle ("Sign Out", UIControlState.Normal); + signoutButton.TouchUpInside += delegate { + Manager.SharedInstance.Signout (); + }; + + Add (signinButton); + Add (signoutButton); + + currentlySigningIn = Manager.SharedInstance.SignIn (CLIENT_ID, true); + RefreshYourUI (); +} + +[Export ("didFinishGamesSignInWithError:")] +public void GamesSignInFinished (NSError error) +{ + if (error != null) + Console.WriteLine ("ERROR signing in: {0}", error.LocalizedDescription); + else + Console.WriteLine ("Finished with games sign in!"); + + currentlySigningIn = false; + RefreshYourUI (); +} + + +[Export ("didFinishGamesSignOutWithError:")] +public virtual void GamesSignOutFinished (NSError error) +{ + if (error != null) + Console.WriteLine ("ERROR signing out: {0}", error.LocalizedDescription); + else + Console.WriteLine ("Signed out!"); + + currentlySigningIn = false; + RefreshYourUI (); +} + +void RefreshYourUI () +{ + var signedIn = Manager.SharedInstance.SignedIn; + signinButton.Hidden = signedIn; + signoutButton.Hidden = !signedIn; + + signinButton.Enabled = !currentlySigningIn; + signoutButton.Enabled = !currentlySigningIn; +} +``` + +## More with Play Games + +* [Achievements][6] +* [Leaderboards][7] +* [Saved Games][8] +* [Real-time Multiplayer][9] +* [Turn-based Multiplayer][10] +* [Events and Quests][11] +* [Push Notifications][12] + +[1]: https://developers.google.com/games/services/console/enabling +[2]: https://developers.google.com/games/services/console/enabling#client_id +[3]: https://developers.google.com/games/services/console/configuring#configuring_achievements_and_leaderboards +[4]: https://developers.google.com/games/services/console/testpub#enabling_accounts_for_testing +[5]: https://developers.google.com/+/branding-guidelines +[6]: https://developers.google.com/games/services/ios/achievements +[7]: https://developers.google.com/games/services/ios/leaderboards +[8]: https://developers.google.com/games/services/ios/savedgames +[9]: https://developers.google.com/games/services/ios/realtimeMultiplayer +[10]: https://developers.google.com/games/services/ios/turnbasedMultiplayer +[11]: https://developers.google.com/games/services/ios/quests +[12]: https://developers.google.com/games/services/ios/notifications \ No newline at end of file diff --git a/docs/Google/SignIn/Details.md b/docs/Google/SignIn/Details.md new file mode 100755 index 00000000..cc8be524 --- /dev/null +++ b/docs/Google/SignIn/Details.md @@ -0,0 +1,4 @@ + +Google Sign-In is a secure authentication system that reduces the burden of login for your users, by enabling them to sign in with their Google account—the same account they already use with Gmail, Play, Google+, and other Google services. + +Google Sign-In is also your gateway to connecting with Google’s users and services in a secure manner. You can give your users the opportunity to pay with Android Pay, share with their Google-wide contacts, save a file to Drive, add an event to Calendar, and more. Integrate Google’s user-centric APIs and services inside your app to help your users take action and convert. \ No newline at end of file diff --git a/docs/Google/SignIn/GettingStarted.md b/docs/Google/SignIn/GettingStarted.md new file mode 100755 index 00000000..c1daeade --- /dev/null +++ b/docs/Google/SignIn/GettingStarted.md @@ -0,0 +1,143 @@ +Configuring your App +-------------------- + +Google provides an easy to use configuration web tool to generate a config file for your app: + +1. Open [Google's configuration tool][1] to create a config file for your app. +2. Enter your app's name and iOS Bundle ID and click continue +3. Click *Enable Sign-In* +4. Click *continue* to generate the configuration files +5. Click *Download Google-Service-Info.plist* +6. Add `GoogleService-Info.plist` to your Xamarin.iOS app project and set the *Build Action* to `BundleResource` +7. In your Xamarin.iOS app project's `Info.plist` file, add the following URL Types: + - Role: `Editor` URL Schemes: `your.app.bundle.id` + - Role: `Editor` URL Schemes: `value of REVERSED_CLIENT_ID from GoogleService-Info.plist` + +Setup your AppDelegate +---------------------- + +In order for Sign-In to work properly, you must tell the SDK about some of your application lifecycle events. + +In your `AppDelegate.cs`, in the `FinishedLaunching (..)` override, you should add the following code to the start of the method: + +``` csharp +public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) +{ + ... + + // You can get the GoogleService-Info.plist file at https://developers.google.com/mobile/add + var googleServiceDictionary = NSDictionary.FromFile ("GoogleService-Info.plist"); + SignIn.SharedInstance.ClientID = googleServiceDictionary ["CLIENT_ID"].ToString (); + + ... + + return true; +} +``` + +Next, you will need to override the `OpenUrl` method in your `AppDelegate` class or, if it already exists, add the code inside the method to the existing implementation: + +``` csharp +// For iOS 9 or newer +public override bool OpenUrl (UIApplication app, NSUrl url, NSDictionary options) +{ + var openUrlOptions = new UIApplicationOpenUrlOptions (options); + return SignIn.SharedInstance.HandleUrl (url, openUrlOptions.SourceApplication, openUrlOptions.Annotation); +} + +// For iOS 8 and older +public override bool OpenUrl (UIApplication application, NSUrl url, string sourceApplication, NSObject annotation) +{ + return SignIn.SharedInstance.HandleUrl (url, sourceApplication, annotation); +} +``` + +Signing In +---------- + +Google Sign-In provides a `SignInButton` to add to your views and handles starting the sign in process. You can add the button to your app in code or by using storyboards: + +``` csharp +SignInButton = new SignInButton (); +SignInButton.Frame = new CGRect (20, 100, 150, 44); +View.AddSubview (SignInButton); + +// Assign the SignIn Delegates to receive callbacks +SignIn.SharedInstance.UIDelegate = this; +SignIn.SharedInstance.Delegate = this; +``` + +You also must implement `ISignInDelegate` as well as `ISignInUIDelegate` and provide a `DidSignIn` method to know when the sign-in completed and if it was successful: + +``` csharp +public void DidSignIn (SignIn signIn, GoogleUser user, NSError error) +{ + if (user != null && error == null) + // Disable the SignInButton +} +``` + +> ***Note:*** *The Sign-In SDK automatically acquires access tokens, but the access tokens will be refreshed only when you call `SignIn` or `SignInSilently` methods. To explicitly refresh the access token, call the `RefreshTokens` method. If you need the access token and want the SDK to automatically handle refreshing it, you can use the `GetAccessToken` method.* + +The `SignInUserSilently` method attempts to sign in a previously authenticated user without interaction. This can be done in a `ViewDidLoad` method or `ViewDidAppear` of your `UIViewController`: + +``` csharp +// Assign the SignIn Delegates to receive callbacks +SignIn.SharedInstance.UIDelegate = this; +SignIn.SharedInstance.Delegate = this; + +// Sign the user in automatically +SignIn.SharedInstance.SignInUserSilently (); +``` + +> ***Note:*** *When users silently sign in, the Sign-In SDK automatically acquires access tokens and automatically refreshes them when necessary. If you need the access token and want the SDK to automatically handle refreshing it, you can use the `RefreshTokens` method. To explicitly refresh the access token, call the `RefreshAccessToken` method.* + +If, in your project, the class that implements `ISignInUIDelegate` interface is not a subclass of `UIViewController`, you will need to implement the `WillDispatch`, `PresentViewController`, and `DismissViewController` methods of the `ISignInUIDelegate` interface. For example: + +```csharp +[Export ("signInWillDispatch:error:")] +public void WillDispatch (SignIn signIn, NSError error) +{ + myActivityIndicator.StopAnimating (); +} + +[Export ("signIn:presentViewController:")] +public void PresentViewController (SignIn signIn, UIViewController viewController) +{ + PresentViewController (viewController, true, null); +} + +[Export ("signIn:dismissViewController:")] +public void DismissViewController (SignIn signIn, UIViewController viewController) +{ + DismissViewController (true, null); +} +``` + +Signing Out and Disconnecting +---------- + +To sign out a user simply call the `SignOutUser` method on the `SignIn` object: + +``` csharp +SignOutButton.TouchUpInside += (sender, e) => { + SignIn.SharedInstance.SignOutUser (); + + SignInButton.Enabled = true; + SignOutButton.Enabled = false; +}; +``` + +To completely disconnect the current user from the app and revoke previous authentication call the `DisconnectUser` method on the `SignIn` object. + +Optionally, you can provide a `DidDisconnect` method to know when the sign out was completed and if it was successful: + +```csharp +[Export ("signIn:didDisconnectWithUser:withError:")] +public void DidDisconnect (SignIn signIn, GoogleUser user, NSError error) +{ + // Perform any operations when the user disconnects from app here. +} +``` + +[1]: https://developers.google.com/mobile/add?platform=ios&cntapi=gcm diff --git a/docs/Google/TagManager/Details.md b/docs/Google/TagManager/Details.md new file mode 100755 index 00000000..b14ab6cb --- /dev/null +++ b/docs/Google/TagManager/Details.md @@ -0,0 +1,16 @@ +Developers can use the Google Tag Manager interface to implement and manage measurement tags and pixels in their mobile applications, without having to rebuild and resubmit application binaries to app marketplaces. Developers who are working with Firebase Analytics can easily add Google Tag Manager to help manage and make changes to the implementation, even after the app has shipped. + +Developers can log important events, and decide later which tracking tags or pixels should be fired. Tag Manager currently supports tags for the following products: + +* Firebase Analytics +* Google Analytics +* DoubleClick +* AdWords +* adjust +* AppsFlyer +* Apsalar +* Kochava +* Tune +* Custom Function Calls (for other products) + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://developers.google.com/tag-manager/ios/v5/) to see original Google documentation._ \ No newline at end of file diff --git a/docs/Google/TagManager/GettingStarted.md b/docs/Google/TagManager/GettingStarted.md new file mode 100755 index 00000000..b4f17905 --- /dev/null +++ b/docs/Google/TagManager/GettingStarted.md @@ -0,0 +1,168 @@ +## Prerequisites + +* Create a [Google Tag Manager account][3]. +* [Configure a Google Tag Manager container][4]. + +## Add Firebase to your app + +1. Create a Firebase project in the [Firebase console][1], if you don't already have one. If you already have an existing Google project associated with your mobile app, click **Import Google Project**. Otherwise, click **Create New Project**. +2. Click **Add Firebase to your iOS app** and follow the setup steps. If you're importing an existing Google project, this may happen automatically and you can just [download the config file][2]. +3. When prompted, enter your app's bundle ID. It's important to enter the bundle ID your app is using; this can only be set when you add an app to your Firebase project. +4. At the end, you'll download a `GoogleService-Info.plist` file. You can [download this file][2] again at any time. + +## Configure Tag Manager in your app + +Once you have your `GoogleService-Info.plist` file downloaded in your computer, do the following steps in Xamarin Studio: + +1. Add `GoogleService-Info.plist` file to your app project. +2. Set `GoogleService-Info.plist` **build action** behaviour to `Bundle Resource` by Right clicking/Build Action. +3. Open `GoogleService-Info.plist` file and change `IS_ANALYTICS_ENABLED` value to `Yes`. +4. Add the following lines of code somewhere in your app, typically in your AppDelegate's `FinishedLaunching` method (don't forget to import `Firebase.Core` and `Google.TagManager` namespaces): + +```csharp +TagManager.Configure (); +App.Configure (); +``` + +## Download your container and add it to your project + +1. Sign in to your [Google Tag Manager][5] account. +2. Select a mobile container. +3. Click Versions in the top navigation bar. +4. Click Actions > Download on the selected container version. +5. The name of the downloaded file is the container ID with a .json extension. +6. In Xamarin Studio, create a folder named **container** inside of your **Resources** folder. +7. Add **GTM-XXXXXX.json** file into your **container** folder in your project. +8. Verify that **GTM-XXXXXX.json** build action behaviour is `Bundle Resource` by Right clicking/Build Action. + +## Log events and variables + +Google Tag Manager uses Firebase Analytics' events, parameters, and user properties to trigger and build tags you've configured in the Google Tag Manager web interface. In this sense, your Firebase Analytics implementation acts as your data layer. + +To learn how to log events and properties, see [Firebase Analytics for iOS Getting Started][6] guide. + +### Configure variables in Tag Manager + +To capture the value of Firebase event parameters and user properties for use in Google Tag Manager, you can [configure variables][7] in the Tag Manager interface. + +For example, if you log the following custom event: + +```csharp +NSString [] keys = { new NSString ("image_name"), new NSString ("full_text") }; +NSObject [] values = { name, text }; +var parameters = NSDictionary.FromObjectsAndKeys (keys, values, keys.Length); +Analytics.LogEvent ("share_image", parameters); +``` + +You could configure new **Event Parameter** variables in Google Tag Manager to capture the `image_name` and `full_text` parameter values: + +* **Variable Name**: Image Name +* **Variable Type**: Event Parameter +* **Event Parameter Key Name**: image_name + +and: + +* **Variable Name**: Full Text +* **Variable Type**: Event Parameter +* **Event Parameter Key Name**: full_text + +It is similar for user properties, for example: + +```csharp +Analytics.SetUserProperty (food, "favorite_food"); +``` + +You could configure a new **Firebase User Property** variable in Google Tag Manager to capture the `favorite_food` value: + +* **Variable Name**: Favorite Food +* **Variable Type**: Firebase User Property +* **Event Parameter Key Name**: favorite_food + +### Modify and block Firebase Analytics events + +Google Tag Manager enables you to modify and block events before they are logged in Firebase Analytics. Modifying events can help you—without app updates—add, remove, or change the values of event parameters or adjust event names. Events that are not blocked will be logged in Firebase Analytics. + +Firebase Analytics also automatically logs some [events][8] and [user properties][9]; you don't need to add any code to enable them. These automatically collected events and properties can be used in Google Tag Manager, but cannot be blocked. + +## Fire tags + +Firebase event name variables, Firebase event parameter variables, and other variables are used to set up [triggers][10]. Trigger conditions are evaluated whenever you log a Firebase event. By default, Firebase Analytics events automatically fire. It is possible to add a Firebase Analytics tag in Tag Manager to block events from being sent to Firebase Analytics. + +## Preview, debug, and publish your container + +Before publishing a version of your container, you'll want to preview it to make sure it works as intended. Google Tag Manager enables you to preview versions of your container by generating links and QR codes in the Google Tag Manager web interface and using them to open your application. + +### Preview container + +To preview a container, generate a preview URL in the Google Tag Manager web interface: + +1. Sign in to your [Google Tag Manager][5] account. +2. Select a mobile container. +3. Click **Versions** in the top navigation bar. +4. Click **Actions > Preview** on the container version you'd like to preview. +5. Enter your application's Bundle ID. +6. Click **Generate begin preview link**. +7. Save the preview URL. + +To enable container previews, you must define the Google Tag Manager preview URL scheme in your **Info.plist** file: + +1. In Xamarin Studio, open your Info.plist file and go to **Advance** tab. +2. Click on **Add URL Type**. +3. As Identifer set your Bundle ID. +4. As URL Schemes set `tagmanager.c.`. +5. Open the preview URL on an emulator or physical device to preview the draft container in your app. + +### Debug container + +When you run your app in a simulator or in preview mode, Tag Manager automatically turns logging to verbose. + +### Publish container + +After previewing your container and verifying that it is working, you can [publish it][11]. After you have published your container, your tag configurations are available to mobile app users. When user devices are online, they typically will receive the new configurations within a day. + +## Advanced Configuration + +To extend the functionality of Google Tag Manager, you can add Function Call variables and Function Call tags. Function Call variables let you capture the values returned by calls to pre-registered functions. Function Call tags let you execute pre-registered functions (e.g. to trigger hits for additional measurement and remarketing tools that are not currently supported with tag templates in Google Tag Manager). + +### Add custom tags and variables + +To create a custom tag, create a class that implements the `ICustomFunction` interface (don't forget to import `Google.TagManager` namespace): + +```csharp +public class MyCustomTag : NSObject, ICustomFunction +{ + public NSObject Execute (NSDictionary parameters) + { + // Add custom tag implementation here + } +} +``` + +To create a custom variable, create a class that implements the `ICustomFunction` interface: + +```csharp +public class MyCustomVariable : NSObject, ICustomFunction +{ + public NSObject Execute (NSDictionary parameters) + { + // Return the value of the custom variable. + return NSNumber.FromNInt (42); + } +} +``` + +After you finished creating your custom classes, go to Google Tag Manager's web interface and create **Tags** or **Variables** with **Function Call** as type. + +_Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/). Click [here](https://developers.google.com/tag-manager/ios/v5/) to see original Google documentation._ + +[1]: https://firebase.google.com/console/ +[2]: http://support.google.com/firebase/answer/7015592 +[3]: https://www.google.com/analytics/tag-manager/ +[4]: https://support.google.com/tagmanager/answer/6103696#CreatingAnAccount +[5]: https://tagmanager.google.com/ +[6]: https://components.xamarin.com/gettingstarted/firebaseiosanalytics +[7]: https://support.google.com/tagmanager/answer/6106899 +[8]: https://support.google.com/firebase/answer/6317485 +[9]: https://support.google.com/firebase/answer/6317486 +[10]: https://support.google.com/tagmanager/answer/6106961 +[11]: https://support.google.com/tagmanager/answer/6107163