Google Play Services Rock
-
Upload
peter-friese -
Category
Software
-
view
929 -
download
5
description
Transcript of Google Play Services Rock
Google Play Services
with Xamarin
Building Apps that rock
+Peter Friese
@peterfriese
What is Google Play
Services?
• Set of APIs by Google
Google Play Services
• Set of APIs by Google
• Available for Devices running
Gingerbread and higher
Google Play Services
• Set of APIs by Google
• Available for Devices running
Gingerbread and higher
• Access the latest in Google
technology
Google Play Services
• Set of APIs by Google
• Available for Devices running
Gingerbread and higher
• Access the latest in Google
technology
• Frequent updates
Google Play Services
• Set of APIs by Google
• Available for Devices running
Gingerbread and higher
• Access the latest in Google
technology
• Frequent updates
• One standard way to connect
and authorise
Google Play Services
Device
Google Play Services Library
Google Play Services
Drive Service
Your App
Google API Client
Maps
Google+
Wallet
Games Services
How to Integrate Google
Play Services
Three simple steps to success
Add Google
Play Services
to your project
Start using our
APIsProfit!
it's a little
bit morecomplicated.
Actually,
Image: http://en.wikipedia.org/wiki/Turf_maze
• Create new Android project
Xamarin StudioProject setup
Xamarin Studio
• Create new Android project
Project setup
• Create new Android project
• Download Google Play
Services
Xamarin StudioProject setup
Xamarin Studio
• Create new Android project
• Download Google Play
Services
Project setup
• Create new Android project
• Download Google Play
Services
• Add the NuGet component
Xamarin StudioProject setup
Xamarin Studio
• Create new Android project
• Download Google Play
Services
• Add the NuGet component
Project setup
• Create a new project
Google Developers ConsoleProject setup
Google Developers Console
• Create a new project
Project setup
• Create a new project
• Configure the Consent Screen
Google Developers ConsoleProject setup
Google Developers Console
• Create a new project
• Configure the Consent Screen
Project setup
• Create a new project
• Configure the Consent Screen
• Create a Client Configuration
Google Developers ConsoleProject setup
Google Developers Console
• Create a new project
• Configure the Consent Screen
• Create a Client Configuration
Project setup$ keytool -list -storepass android -keystore ~/.local/share/Xamarin/Mono\ for\ Android/debug.keystore !Keystore type: JKS Keystore provider: SUN Your keystore contains 1 entry androiddebugkey, 14-May-2014, PrivateKeyEntry, Certificate fingerprint (SHA1): CA:FE:BA:BE:DE:AD:BE:EF:DE:ED:BE:AD:FE:ED:DE:AF:CA:FE:BA:BE
• Create a new project
• Configure the Consent Screen
• Create a Client Configuration
Google Developers ConsoleProject setup
• Create a new project
• Configure the Consent Screen
• Create a Client Configuration
• Activate APIs
Google Developers ConsoleProject setup
Connecting Your App
with Google
protected bool ServicesConnected(){ var resultCode = GooglePlayServicesUtil.IsGooglePlayServicesAvailable (this); if (resultCode == ConnectionResult.Success) { Log.Debug (logTag, "Google Play Services are available"); return true; } else { Log.Debug (logTag, "Connection failed. Attempting resolution."); GooglePlayServicesUtil.GetErrorDialog (resultCode, this, ConnectionFailureResolutionRequest).Show (); return false; }}
BaseActivity.cs
Checking for a compatible GMS version
protected override void OnCreate (Bundle savedInstanceState){ base.OnCreate (savedInstanceState); if (googleApiClient == null) { googleApiClient = new GoogleApiClientBuilder (this) .AddApi (DriveClass.Api) .AddScope (DriveClass.ScopeFile) .AddApi(PlusClass.Api) .AddScope(PlusClass.ScopePlusLogin) .AddConnectionCallbacks (this) .AddOnConnectionFailedListener (this) .Build (); }}
BaseActivity.cs
Configuring GoogleApiClient
protected override void OnCreate (Bundle savedInstanceState){ base.OnCreate (savedInstanceState); if (googleApiClient == null) { googleApiClient = new GoogleApiClientBuilder (this) .AddApi (DriveClass.Api) .AddScope (DriveClass.ScopeFile) .AddApi(PlusClass.Api) .AddScope(PlusClass.ScopePlusLogin) .AddConnectionCallbacks (this) .AddOnConnectionFailedListener (this) .Build (); }}
BaseActivity.cs
Configuring GoogleApiClient
Add multiple APIs and Scopes
protected override void OnCreate (Bundle savedInstanceState){ base.OnCreate (savedInstanceState); if (googleApiClient == null) { googleApiClient = new GoogleApiClientBuilder (this) .AddApi (DriveClass.Api) .AddScope (DriveClass.ScopeFile) .AddApi(PlusClass.Api) .AddScope(PlusClass.ScopePlusLogin) .AddConnectionCallbacks (this) .AddOnConnectionFailedListener (this) .Build (); }}
BaseActivity.cs
Configuring GoogleApiClient
Set up callbacks
protected override void OnStart (){ base.OnStart (); googleApiClient.Connect();} !protected override void OnStop (){ if (googleApiClient != null) { googleApiClient.Disconnect (); } base.OnStop ();}
BaseActivity.cs
Connecting GoogleApiClient
BaseActivity.cs
Callbacks - The Good
public class BaseDemoActivity : Activity, IGoogleApiClientConnectionCallbacks{ public virtual void OnConnected (Bundle connectionHint) { Log.Debug (logTag, "Google API client connected.”); // let the good stuff happen here } public void OnConnectionSuspended (int cause) { Log.Debug (logTag, "Google API client connection suspended."); // deactivate UI components, etc. }}
BaseActivity.cs
Callbacks - The Bad
public class BaseDemoActivity : Activity, IGoogleApiClientOnConnectionFailedListener{ public void OnConnectionFailed (ConnectionResult result) { if (result.HasResolution) { try { result.StartResolutionForResult (this, RequestCodeResolution); } catch (IntentSender.SendIntentException ex) { Log.Error (logTag, "Exception while starting resolution activity", ex); } } else { GooglePlayServicesUtil.GetErrorDialog (result.ErrorCode, this, 0).Show (); return; } }}
BaseActivity.cs
Callbacks - The Bad
public class BaseDemoActivity : Activity, IGoogleApiClientOnConnectionFailedListener{ public void OnConnectionFailed (ConnectionResult result) { if (result.HasResolution) { try { result.StartResolutionForResult (this, RequestCodeResolution); } catch (IntentSender.SendIntentException ex) { Log.Error (logTag, "Exception while starting resolution activity", ex); } } else { GooglePlayServicesUtil.GetErrorDialog (result.ErrorCode, this, 0).Show (); return; } }}
Try to resolve this error by asking for the user’s consent
A Closer Look at Some
of the Services
?
Google Drive
Record demos
Reading / writing files
App folders
• Cloud storage powered by
Google’s infrastructure
Google Drive
• Cloud storage powered by
Google’s infrastructure
• 15 GB for free, upgrades at
competitive prices
Google Drive
• Cloud storage powered by
Google’s infrastructure
• 15 GB for free, upgrades at
competitive prices
• Automatic synchronisation
Google Drive
• Cloud storage powered by
Google’s infrastructure
• 15 GB for free, upgrades at
competitive prices
• Automatic synchronisation
• Android, iOS, Mac, Windows,
Web
Google Drive
• Cloud storage powered by
Google’s infrastructure
• 15 GB for free, upgrades at
competitive prices
• Automatic synchronisation
• Android, iOS, Mac, Windows,
Web
• UI controls (create / pick files)
Google Drive
protected override void OnCreate (Bundle savedInstanceState){ base.OnCreate (savedInstanceState); if (googleApiClient == null) { googleApiClient = new GoogleApiClientBuilder (this) .AddApi (DriveClass.Api) .AddScope (DriveClass.ScopeFile) .AddConnectionCallbacks (this) .AddOnConnectionFailedListener (this) .Build (); }}
BaseActivity.cs
Google Drive - Connecting
protected override void OnCreate (Bundle savedInstanceState){ base.OnCreate (savedInstanceState); if (googleApiClient == null) { googleApiClient = new GoogleApiClientBuilder (this) .AddApi (DriveClass.Api) .AddScope (DriveClass.ScopeFile) .AddConnectionCallbacks (this) .AddOnConnectionFailedListener (this) .Build (); }}
BaseActivity.cs
Configuring GoogleApiClient
Supported scopes:
• DriveClass.ScopeFile (drive.file)
• DriveClass.ScopeAppFolder (drive.appfolder)
More about scopes: https://developers.google.com/drive/web/scopes
Google DriveList Files
public class ListFilesActivity : BaseDemoActivity, IResultCallback{ private bool hasMore; private string nextPageToken; private ListView listView; protected ResultsAdapter resultsAdapter;
protected override void OnCreate (Bundle bundle) { base.OnCreate (bundle); SetContentView (Resource.Layout.ListFiles); hasMore = true; listView = (ListView) FindViewById (Resource.Id.ListViewResults); resultsAdapter = new ResultsAdapter (this); listView.Adapter = resultsAdapter; listView.Scroll += (object sender, AbsListView.ScrollEventArgs e) => { if (nextPageToken != null && e.FirstVisibleItem + e.VisibleItemCount + 5 < e.TotalItemCount) {
ListFilesActivity.cs
Called for paging list of files.
public class ListFilesActivity : BaseDemoActivity, IResultCallback{ private bool hasMore; private string nextPageToken; private ListView listView; protected ResultsAdapter resultsAdapter;
protected override void OnCreate (Bundle bundle) { base.OnCreate (bundle); SetContentView (Resource.Layout.ListFiles); hasMore = true; listView = (ListView) FindViewById (Resource.Id.ListViewResults); resultsAdapter = new ResultsAdapter (this); listView.Adapter = resultsAdapter; listView.Scroll += (object sender, AbsListView.ScrollEventArgs e) => { if (nextPageToken != null && e.FirstVisibleItem + e.VisibleItemCount + 5 < e.TotalItemCount) { RetrieveNextPage(); } } ; }
protected override void OnStop() {
ListFilesActivity.cs
General view setup
base.OnCreate (bundle); SetContentView (Resource.Layout.ListFiles); hasMore = true; listView = (ListView) FindViewById (Resource.Id.ListViewResults); resultsAdapter = new ResultsAdapter (this); listView.Adapter = resultsAdapter; listView.Scroll += (object sender, AbsListView.ScrollEventArgs e) => { if (nextPageToken != null && e.FirstVisibleItem + e.VisibleItemCount + 5 < e.TotalItemCount) { RetrieveNextPage(); } } ; }
protected override void OnStop() { base.OnStop(); resultsAdapter.Clear(); } public override void OnConnected (Bundle connectionHint) { base.OnConnected (connectionHint); RetrieveNextPage(); }
ListFilesActivity.cs
Fetch next page when scrolling
listView.Scroll += (object sender, AbsListView.ScrollEventArgs e) => { if (nextPageToken != null && e.FirstVisibleItem + e.VisibleItemCount + 5 < e.TotalItemCount) { RetrieveNextPage(); } } ; }
protected override void OnStop() { base.OnStop(); resultsAdapter.Clear(); } public override void OnConnected (Bundle connectionHint) { base.OnConnected (connectionHint); RetrieveNextPage(); } private void RetrieveNextPage () { if (!hasMore) { return; } var query = new QueryClass.Builder () .SetPageToken (nextPageToken)
ListFilesActivity.cs
Clear adapter on stop.
}
protected override void OnStop() { base.OnStop(); resultsAdapter.Clear(); } public override void OnConnected (Bundle connectionHint) { base.OnConnected (connectionHint); RetrieveNextPage(); } private void RetrieveNextPage () { if (!hasMore) { return; } var query = new QueryClass.Builder () .SetPageToken (nextPageToken) .Build (); DriveClass.DriveApi.Query (GoogleApiClient, query).SetResultCallback (this); } public void OnResult (Java.Lang.Object result) {
ListFilesActivity.cs
Fetch data as soon as we’re connected
} public override void OnConnected (Bundle connectionHint) { base.OnConnected (connectionHint); RetrieveNextPage(); } private void RetrieveNextPage () { if (!hasMore) { return; } var query = new QueryClass.Builder () .SetPageToken (nextPageToken) .Build (); DriveClass.DriveApi.Query (GoogleApiClient, query).SetResultCallback (this); } public void OnResult (Java.Lang.Object result) { var metadataBufferResult = result.JavaCast<IDriveApiMetadataBufferResult> (); if (metadataBufferResult != null) { if (!metadataBufferResult.Status.IsSuccess) { ShowMessage ("Problems while retrieving files."); }
ListFilesActivity.cs
Use token to keep track of position
return; } var query = new QueryClass.Builder () .SetPageToken (nextPageToken) .Build (); DriveClass.DriveApi.Query (GoogleApiClient, query).SetResultCallback (this); } public void OnResult (Java.Lang.Object result) { var metadataBufferResult = result.JavaCast<IDriveApiMetadataBufferResult> (); if (metadataBufferResult != null) { if (!metadataBufferResult.Status.IsSuccess) { ShowMessage ("Problems while retrieving files."); } resultsAdapter.Append (metadataBufferResult.MetadataBuffer); nextPageToken = metadataBufferResult.MetadataBuffer.NextPageToken; hasMore = nextPageToken != null; } }}
ListFilesActivity.cs
Fetch results from metadata buffer
Google DrivePick Files & Folders
public override void OnConnected (Bundle connectionHint){ base.OnConnected (connectionHint); var intentSender = DriveClass.DriveApi .NewOpenFileActivityBuilder () .SetMimeType (new string[]{ "text/plain", "text/html" } ) .Build (GoogleApiClient); try { StartIntentSenderForResult (intentSender, RequestCodeOpener, null, 0, 0, 0); } catch (IntentSender.SendIntentException ex) { Log.Warn (logTag, "Unable to send intent", ex); }}protected override void OnActivityResult (int requestCode, Result resultCode, Intent data){ base.OnActivityResult (requestCode, resultCode, data);
PickFileWithOpenerActivity.cs
public override void OnConnected (Bundle connectionHint){ base.OnConnected (connectionHint); var intentSender = DriveClass.DriveApi .NewOpenFileActivityBuilder () .SetMimeType (new string[]{ "text/plain", "text/html" } ) .Build (GoogleApiClient); try { StartIntentSenderForResult (intentSender, RequestCodeOpener, null, 0, 0, 0); } catch (IntentSender.SendIntentException ex) { Log.Warn (logTag, "Unable to send intent", ex); }}protected override void OnActivityResult (int requestCode, Result resultCode, Intent data){ base.OnActivityResult (requestCode, resultCode, data);
PickFileWithOpenerActivity.cs
Create new file picker
public override void OnConnected (Bundle connectionHint){ base.OnConnected (connectionHint); var intentSender = DriveClass.DriveApi .NewOpenFileActivityBuilder () .SetMimeType (new string[]{ "text/plain", "text/html" } ) .Build (GoogleApiClient); try { StartIntentSenderForResult (intentSender, RequestCodeOpener, null, 0, 0, 0); } catch (IntentSender.SendIntentException ex) { Log.Warn (logTag, "Unable to send intent", ex); }}protected override void OnActivityResult (int requestCode, Result resultCode, Intent data){ base.OnActivityResult (requestCode, resultCode, data);
PickFileWithOpenerActivity.cs
Mime types to show in picker
public override void OnConnected (Bundle connectionHint){ base.OnConnected (connectionHint); var intentSender = DriveClass.DriveApi .NewOpenFileActivityBuilder () .SetMimeType (new string[]{ "text/plain", "text/html" } ) .Build (GoogleApiClient); try { StartIntentSenderForResult (intentSender, RequestCodeOpener, null, 0, 0, 0); } catch (IntentSender.SendIntentException ex) { Log.Warn (logTag, "Unable to send intent", ex); }}protected override void OnActivityResult (int requestCode, Result resultCode, Intent data){ base.OnActivityResult (requestCode, resultCode, data);
PickFileWithOpenerActivity.cs
Start picker intent
.Build (GoogleApiClient); try { StartIntentSenderForResult (intentSender, RequestCodeOpener, null, 0, 0, 0); } catch (IntentSender.SendIntentException ex) { Log.Warn (logTag, "Unable to send intent", ex); }}protected override void OnActivityResult (int requestCode, Result resultCode, Intent data){ base.OnActivityResult (requestCode, resultCode, data); if (requestCode == RequestCodeOpener) { if (resultCode == Result.Ok) { var driveId = data.GetParcelableExtra (OpenFileActivityBuilder.ExtraResponseDriveId); ShowMessage ("Selected folder with ID: " + driveId); } Finish (); } else { base.OnActivityResult (requestCode, resultCode, data); }}
PickFileWithOpenerActivity.cs
.Build (GoogleApiClient); try { StartIntentSenderForResult (intentSender, RequestCodeOpener, null, 0, 0, 0); } catch (IntentSender.SendIntentException ex) { Log.Warn (logTag, "Unable to send intent", ex); }}protected override void OnActivityResult (int requestCode, Result resultCode, Intent data){ base.OnActivityResult (requestCode, resultCode, data); if (requestCode == RequestCodeOpener) { if (resultCode == Result.Ok) { var driveId = data.GetParcelableExtra (OpenFileActivityBuilder.ExtraResponseDriveId); ShowMessage ("Selected folder with ID: " + driveId); } Finish (); } else { base.OnActivityResult (requestCode, resultCode, data); }}
PickFileWithOpenerActivity.cs
Returning from picker?
.Build (GoogleApiClient); try { StartIntentSenderForResult (intentSender, RequestCodeOpener, null, 0, 0, 0); } catch (IntentSender.SendIntentException ex) { Log.Warn (logTag, "Unable to send intent", ex); }}protected override void OnActivityResult (int requestCode, Result resultCode, Intent data){ base.OnActivityResult (requestCode, resultCode, data); if (requestCode == RequestCodeOpener) { if (resultCode == Result.Ok) { var driveId = data.GetParcelableExtra (OpenFileActivityBuilder.ExtraResponseDriveId); ShowMessage ("Selected folder with ID: " + driveId); } Finish (); } else { base.OnActivityResult (requestCode, resultCode, data); }}
PickFileWithOpenerActivity.cs
Get metadata from extras
Activity Recogniton
Image by Martijn van Dalen
https://www.flickr.com/photos/martijnvandalen/4591360652
Activity RecognitionDemo
protected override void OnCreate (Bundle savedInstanceState){ base.OnCreate (savedInstanceState); if (googleApiClient == null) { googleApiClient = new GoogleApiClientBuilder (this) .AddApi (ActivityRecognition.Api) .AddConnectionCallbacks (this) .AddOnConnectionFailedListener (this) .Build (); }}
BaseActivity.cs
Activity Recognition - Connecting
protected override void OnCreate (Bundle savedInstanceState){ base.OnCreate (savedInstanceState); if (googleApiClient == null) { googleApiClient = new GoogleApiClientBuilder (this) .AddApi (ActivityRecognition.Api) .AddConnectionCallbacks (this) .AddOnConnectionFailedListener (this) .Build (); }}
BaseActivity.cs
Activity Recognition - Connecting
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="ActivityRecognitionDemos.ActivityRecognitionDemos"> <uses-sdk /> <application android:label="ActivityRecognitionDemos"> </application> <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" /></manifest>
AndroidManifest.xml
Activity Recognition - Permissions
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="ActivityRecognitionDemos.ActivityRecognitionDemos"> <uses-sdk /> <application android:label="ActivityRecognitionDemos"> </application> <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" /></manifest>
AndroidManifest.xml
Activity Recognition - Permissions
public override void OnConnected (Bundle connectionHint){ var intent = new Intent (this, typeof(ActivityRecognitionIntentService)); activityRecognitionPendingIntent = PendingIntent.GetService ( this, 0, intent, PendingIntentFlags.UpdateCurrent); ActivityRecognition.ActivityRecognitionApi.RequestActivityUpdates (GoogleApiClient, DetectionInterval, activityRecognitionPendingIntent);}
MainActivity.cs
Starting Activity Recognition
public override void OnConnected (Bundle connectionHint){ var intent = new Intent (this, typeof(ActivityRecognitionIntentService)); activityRecognitionPendingIntent = PendingIntent.GetService ( this, 0, intent, PendingIntentFlags.UpdateCurrent); ActivityRecognition.ActivityRecognitionApi.RequestActivityUpdates (GoogleApiClient, DetectionInterval, activityRecognitionPendingIntent);}
MainActivity.cs
Starting Activity Recognition
public override void OnConnected (Bundle connectionHint){ var intent = new Intent (this, typeof(ActivityRecognitionIntentService)); activityRecognitionPendingIntent = PendingIntent.GetService ( this, 0, intent, PendingIntentFlags.UpdateCurrent); ActivityRecognition.ActivityRecognitionApi.RequestActivityUpdates (GoogleApiClient, DetectionInterval, activityRecognitionPendingIntent);}
MainActivity.cs
Starting Activity Recognition
[Service][IntentFilter(new String[]{"ActivityRecognitionIntentService"})]public class ActivityRecognitionIntentService : IntentService{ protected override void OnHandleIntent (Android.Content.Intent intent) { if (ActivityRecognitionResult.HasResult (intent)) { var result = ActivityRecognitionResult.ExtractResult (intent); var mostProbableActivity = result.MostProbableActivity; var confidence = mostProbableActivity.Confidence; var activityType = mostProbableActivity.Type; var name = GetActivityName (activityType); } } ! protected string GetActivityName(int activityType) { switch (activityType) { } } }
ActivityRecognitionIntentService.cs
Receiving Activity Updates
[Service][IntentFilter(new String[]{"ActivityRecognitionIntentService"})]public class ActivityRecognitionIntentService : IntentService{ protected override void OnHandleIntent (Android.Content.Intent intent) { if (ActivityRecognitionResult.HasResult (intent)) { var result = ActivityRecognitionResult.ExtractResult (intent); var mostProbableActivity = result.MostProbableActivity; var confidence = mostProbableActivity.Confidence; var activityType = mostProbableActivity.Type; var name = GetActivityName (activityType); } } ! protected string GetActivityName(int activityType) { switch (activityType) { } } }
ActivityRecognitionIntentService.cs
Receiving Activity Updates
[Service][IntentFilter(new String[]{"ActivityRecognitionIntentService"})]public class ActivityRecognitionIntentService : IntentService{ protected override void OnHandleIntent (Android.Content.Intent intent) { if (ActivityRecognitionResult.HasResult (intent)) { var result = ActivityRecognitionResult.ExtractResult (intent); var mostProbableActivity = result.MostProbableActivity; var confidence = mostProbableActivity.Confidence; var activityType = mostProbableActivity.Type; var name = GetActivityName (activityType); } } ! protected string GetActivityName(int activityType) { switch (activityType) { } } }
ActivityRecognitionIntentService.cs
Receiving Activity Updates
[Service][IntentFilter(new String[]{"ActivityRecognitionIntentService"})]public class ActivityRecognitionIntentService : IntentService{ protected override void OnHandleIntent (Android.Content.Intent intent) { if (ActivityRecognitionResult.HasResult (intent)) { var result = ActivityRecognitionResult.ExtractResult (intent); var mostProbableActivity = result.MostProbableActivity; var confidence = mostProbableActivity.Confidence; var activityType = mostProbableActivity.Type; var name = GetActivityName (activityType); } } ! protected string GetActivityName(int activityType) { switch (activityType) { } } }
ActivityRecognitionIntentService.cs
Receiving Activity Updates
[Service][IntentFilter(new String[]{"ActivityRecognitionIntentService"})]public class ActivityRecognitionIntentService : IntentService{ protected override void OnHandleIntent (Android.Content.Intent intent) { if (ActivityRecognitionResult.HasResult (intent)) { var result = ActivityRecognitionResult.ExtractResult (intent); var mostProbableActivity = result.MostProbableActivity; var confidence = mostProbableActivity.Confidence; var activityType = mostProbableActivity.Type; var name = GetActivityName (activityType); } } ! protected string GetActivityName(int activityType) { switch (activityType) { } } }
ActivityRecognitionIntentService.cs
Receiving Activity Updates
• Obtaining a Maps Key
Google Developers ConsoleMaps Setup
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android=“http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName=“1.0" package="com.google.xamarin.sample.GoogleMapsAndroidDemos">
<uses-sdk /> <application android:label="GoogleMapsAndroidDemos"> <meta-data android:name=“com.google.android.maps.v2.API_KEY" android:value="AAzaXXXv3LKXXX0yJb-dXX0xxxWo0XxX_XXXxAg" /> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> </application> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission
AndroidManifest.xml
Maps Key and Permissions
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android=“http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName=“1.0" package="com.google.xamarin.sample.GoogleMapsAndroidDemos">
<uses-sdk /> <application android:label="GoogleMapsAndroidDemos"> <meta-data android:name=“com.google.android.maps.v2.API_KEY" android:value="AAzaXXXv3LKXXX0yJb-dXX0xxxWo0XxX_XXXxAg" /> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> </application> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission
AndroidManifest.xml
Maps Key and Permissions
Maps API Key
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android=“http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName=“1.0" package="com.google.xamarin.sample.GoogleMapsAndroidDemos">
<uses-sdk /> <application android:label="GoogleMapsAndroidDemos"> <meta-data android:name=“com.google.android.maps.v2.API_KEY" android:value="AAzaXXXv3LKXXX0yJb-dXX0xxxWo0XxX_XXXxAg" /> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> </application> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission
AndroidManifest.xml
Maps Key and Permissions
GMS Version
android:versionName=“1.0" package="com.google.xamarin.sample.GoogleMapsAndroidDemos">
<uses-sdk /> <application android:label="GoogleMapsAndroidDemos"> <meta-data android:name=“com.google.android.maps.v2.API_KEY" android:value="AAzaXXXv3LKXXX0yJb-dXX0xxxWo0XxX_XXXxAg" /> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> </application> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /></manifest>
Maps Key and Permissions
AndroidManifest.xml
MapsStreet View
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/StreetViewActivity" android:layout_width="match_parent" android:layout_height="match_parent" class="com.google.android.gms.maps.StreetViewPanoramaFragment" /></FrameLayout>
StreetViewActivity.axml
Layout
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/StreetViewActivity" android:layout_width="match_parent" android:layout_height="match_parent" class="com.google.android.gms.maps.StreetViewPanoramaFragment" /></FrameLayout>
StreetViewActivity.axml
Layout
protected override void OnCreate (Bundle bundle){ base.OnCreate (bundle); SetContentView (Resource.Layout.StreetViewActivity);
svp = (FragmentManager.FindFragmentById<StreetViewPanoramaFragment> (Resource.Id.StreetViewActivity)).StreetViewPanorama; svp.SetPosition (new LatLng(51.493896, -0.146866));}
StreetViewActivity.cs
StreetView - Set Position
protected override void OnCreate (Bundle bundle){ base.OnCreate (bundle); SetContentView (Resource.Layout.StreetViewActivity);
svp = (FragmentManager.FindFragmentById<StreetViewPanoramaFragment> (Resource.Id.StreetViewActivity)).StreetViewPanorama; svp.SetPosition (new LatLng(51.493896, -0.146866));}
StreetViewActivity.cs
StreetView - Set Position
protected override void OnCreate (Bundle bundle){ base.OnCreate (bundle); SetContentView (Resource.Layout.StreetViewActivity);
svp = (FragmentManager.FindFragmentById<StreetViewPanoramaFragment> (Resource.Id.StreetViewActivity)).StreetViewPanorama; svp.SetPosition (new LatLng(51.493896, -0.146866));}
StreetViewActivity.cs
StreetView - Set Position
protected void walkToOffice() { long duration = 500; float tilt = 0; var camera = new StreetViewPanoramaCamera.Builder () .Zoom (svp.PanoramaCamera.Zoom) .Bearing (svp.PanoramaCamera.Bearing) .Tilt (tilt) .Build (); svp.AnimateTo (camera, duration); svp.SetPosition (new LatLng(51.493896, -0.146866));}
StreetViewActivity.cs
StreetView - Move Camera
protected void walkToOffice() { long duration = 500; float tilt = 0; var camera = new StreetViewPanoramaCamera.Builder () .Zoom (svp.PanoramaCamera.Zoom) .Bearing (svp.PanoramaCamera.Bearing) .Tilt (tilt) .Build (); svp.AnimateTo (camera, duration); svp.SetPosition (new LatLng(51.493896, -0.146866));}
StreetViewActivity.cs
StreetView - Move Camera
protected void walkToOffice() { long duration = 500; float tilt = 0; var camera = new StreetViewPanoramaCamera.Builder () .Zoom (svp.PanoramaCamera.Zoom) .Bearing (svp.PanoramaCamera.Bearing) .Tilt (tilt) .Build (); svp.AnimateTo (camera, duration); svp.SetPosition (new LatLng(51.493896, -0.146866));}
StreetViewActivity.cs
StreetView - Move Camera
Customizing user-controlled functionality:
• PanningGesturesEnabled
• UserNavigationEnabled
• ZoomGesturesEnabled
• StreetNamesEnabled
MapsIndoor Maps
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/IndoorMaps" android:layout_width="match_parent" android:layout_height="match_parent" class="com.google.android.gms.maps.MapFragment" /></FrameLayout>
StreetViewActivity.axml
Layout
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/IndoorMaps" android:layout_width="match_parent" android:layout_height="match_parent" class="com.google.android.gms.maps.MapFragment" /></FrameLayout>
StreetViewActivity.axml
Layout
protected override void OnCreate (Bundle bundle){ base.OnCreate (bundle); SetContentView (Resource.Layout.IndoorMapsActivity);
if (map == null) { map = (FragmentManager.FindFragmentById<MapFragment> (Resource.Id.IndoorMaps)).Map; if (map != null) { var camera = CameraUpdateFactory.NewLatLngZoom ( new LatLng(51.493896, -0.146866), 18); map.MoveCamera (camera); } }}
IndoorMapsViewActivity.cs
Indoor Maps
protected override void OnCreate (Bundle bundle){ base.OnCreate (bundle); SetContentView (Resource.Layout.IndoorMapsActivity);
if (map == null) { map = (FragmentManager.FindFragmentById<MapFragment> (Resource.Id.IndoorMaps)).Map; if (map != null) { var camera = CameraUpdateFactory.NewLatLngZoom ( new LatLng(51.493896, -0.146866), 18); map.MoveCamera (camera); } }}
IndoorMapsViewActivity.cs
Indoor Maps
protected override void OnCreate (Bundle bundle){ base.OnCreate (bundle); SetContentView (Resource.Layout.IndoorMapsActivity);
if (map == null) { map = (FragmentManager.FindFragmentById<MapFragment> (Resource.Id.IndoorMaps)).Map; if (map != null) { var camera = CameraUpdateFactory.NewLatLngZoom ( new LatLng(51.493896, -0.146866), 18); map.MoveCamera (camera); } }}
IndoorMapsViewActivity.cs
Indoor Maps
Google+
• Powerful identity provider
Google+
• Powerful identity provider
• Over the Air Installs (OTA)
Google+
• Powerful identity provider
• Over the Air Installs (OTA)
• Drive engagement via
interactive posts
Google+
No tap required, log-in will happen automatically!
• Use Google+ Sign-in button
Google+ OTA
• Use Google+ Sign-in button
• Set App package name on the
button
Google+ OTA
• Use Google+ Sign-in button
• Set App package name on the
button
• Use the same scopes on web
and in the app
Google+ OTA
• Use Google+ Sign-in button
• Set App package name on the
button
• Use the same scopes on web
and in the app
• Configure consent screen
Google+ OTA
• Use Google+ Sign-in button
• Set App package name on the
button
• Use the same scopes on web
and in the app
• Configure consent screen
• Meet quality thresholds
Google+ OTA
Google+Interactive Posts
Google+Interactive Posts
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android=“http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.google.xamarin.GooglePlusAndroidDemos"> <uses-sdk /> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <application android:label="GooglePlusAndroidDemos"> </application> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" /></manifest>
AndroidManifest.xml
Permissions
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android=“http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.google.xamarin.GooglePlusAndroidDemos"> <uses-sdk /> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <application android:label="GooglePlusAndroidDemos"> </application> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" /></manifest>
AndroidManifest.xml
Permissions
const int RequestCodeInterActivePost = 1;
var callToActionUrl = Android.Net.Uri.Parse ( GetString (Resource.String.plus_example_deep_link_url) + action);var callToActionDeepLinkId = GetString (Resource.String.plus_example_deep_link_id) + action;var intent = new PlusShare.Builder (this) .AddCallToAction ( LabelViewItem, callToActionUrl, callToActionDeepLinkId) .SetContentUrl (Android.Net.Uri.Parse ( GetString (Resource.String.plus_example_deep_link_url))) .SetContentDeepLinkId ( GetString (Resource.String.plus_example_deep_link_id), null, null, null) .SetText (sendEditText.Text.ToString ()) .Intent;
MainActivity.cs
Create Interactive Posts
const int RequestCodeInterActivePost = 1;
var callToActionUrl = Android.Net.Uri.Parse ( GetString (Resource.String.plus_example_deep_link_url) + action);var callToActionDeepLinkId = GetString (Resource.String.plus_example_deep_link_id) + action;var intent = new PlusShare.Builder (this) .AddCallToAction ( LabelViewItem, callToActionUrl, callToActionDeepLinkId) .SetContentUrl (Android.Net.Uri.Parse ( GetString (Resource.String.plus_example_deep_link_url))) .SetContentDeepLinkId ( GetString (Resource.String.plus_example_deep_link_id), null, null, null) .SetText (sendEditText.Text.ToString ()) .Intent;
MainActivity.cs
Create Interactive Posts
const int RequestCodeInterActivePost = 1;
var callToActionUrl = Android.Net.Uri.Parse ( GetString (Resource.String.plus_example_deep_link_url) + action);var callToActionDeepLinkId = GetString (Resource.String.plus_example_deep_link_id) + action;var intent = new PlusShare.Builder (this) .AddCallToAction ( LabelViewItem, callToActionUrl, callToActionDeepLinkId) .SetContentUrl (Android.Net.Uri.Parse ( GetString (Resource.String.plus_example_deep_link_url))) .SetContentDeepLinkId ( GetString (Resource.String.plus_example_deep_link_id), null, null, null) .SetText (sendEditText.Text.ToString ()) .Intent; !StartActivityForResult(intent, RequestCodeInterActivePost);
MainActivity.cs
Create Interactive Posts
var callToActionDeepLinkId = GetString (Resource.String.plus_example_deep_link_id) + action;var intent = new PlusShare.Builder (this) .AddCallToAction ( LabelViewItem, callToActionUrl, callToActionDeepLinkId) .SetContentUrl (Android.Net.Uri.Parse ( GetString (Resource.String.plus_example_deep_link_url))) .SetContentDeepLinkId ( GetString (Resource.String.plus_example_deep_link_id), null, null, null) .SetText (sendEditText.Text.ToString ()) .Intent; !StartActivityForResult(intent, RequestCodeInterActivePost);
MainActivity.cs
Create Interactive Posts
[Activity (Label = "ParseDeepLinkActivity")] [IntentFilter (new[]{"com.google.android.apps.plus.VIEW_DEEP_LINK"}, DataScheme="vnd.google.deeplink", Categories=new[]{Intent.CategoryDefault, Intent.CategoryBrowsable} )]public class ParseDeepLinkActivity : BaseActivity{ const string logTag = "ParseDeepLinkActivity"; protected override void OnCreate (Bundle bundle) { base.OnCreate (bundle); var deepLinkId = PlusShare.GetDeepLinkId (Intent); var target = ProcessDeepLinkId (deepLinkId); if (target != null) { StartActivity (target); } }
ParseDeepLinkActivity.cs
Parse Deep Links
[Activity (Label = "ParseDeepLinkActivity")] [IntentFilter (new[]{"com.google.android.apps.plus.VIEW_DEEP_LINK"}, DataScheme="vnd.google.deeplink", Categories=new[]{Intent.CategoryDefault, Intent.CategoryBrowsable} )]public class ParseDeepLinkActivity : BaseActivity{ const string logTag = "ParseDeepLinkActivity"; protected override void OnCreate (Bundle bundle) { base.OnCreate (bundle); var deepLinkId = PlusShare.GetDeepLinkId (Intent); var target = ProcessDeepLinkId (deepLinkId); if (target != null) { StartActivity (target); } }
ParseDeepLinkActivity.cs
Parse Deep Links
[Activity (Label = "ParseDeepLinkActivity")] [IntentFilter (new[]{"com.google.android.apps.plus.VIEW_DEEP_LINK"}, DataScheme="vnd.google.deeplink", Categories=new[]{Intent.CategoryDefault, Intent.CategoryBrowsable} )]public class ParseDeepLinkActivity : BaseActivity{ const string logTag = "ParseDeepLinkActivity"; protected override void OnCreate (Bundle bundle) { base.OnCreate (bundle); var deepLinkId = PlusShare.GetDeepLinkId (Intent); var target = ProcessDeepLinkId (deepLinkId); if (target != null) { StartActivity (target); } } protected Intent ProcessDeepLinkId(string deepLinkId) { Intent route = null; var uri = Android.Net.Uri.Parse (deepLinkId);
Parse Deep Links
ParseDeepLinkActivity.cs
base.OnCreate (bundle); var deepLinkId = PlusShare.GetDeepLinkId (Intent); var target = ProcessDeepLinkId (deepLinkId); if (target != null) { StartActivity (target); } } protected Intent ProcessDeepLinkId(string deepLinkId) { Intent route = null; var uri = Android.Net.Uri.Parse (deepLinkId); if (uri.Path.StartsWith (GetString (Resource.String.plus_example_deep_link_id))) { Toast.MakeText (this, string.Format (“Deep link was { 0}", uri.Path.ToString ()), ToastLength.Long) .Show (); } else { Log.Debug (TAG, "We cannot handle this"); } return route; }}
ParseDeepLinkActivity.cs
Parse Deep Links
Recap
?
Google Play Services https://developer.android.com/google/play-services/
Q & A
What’s next?Material Android Design from Concept to Implementation (I + II) Thursday, 1 pm (Franklin Salon)
C# is in My Ears and in My Eyes Thursday, 4:15 pm (Linnaeus Salon)
youtube.com/GoogleDevelopers
+PeterFriese
@
Thank you!
#Xamarin+Google