Deep Linking for xamarin app without a website - c#

I want to redirect my user from email link to a particular app page.
I do not have a subsequent website, my app is independent.
I have tried intent filters and it does take me to the apps main activity but how do i navigate the user to particular activity is my main roadblock.
Am not interested in app linking I just require deep linking.
I want to know how to to navigate to the particular activity from the link itself directly.
I have tried intent filters in mainactivity.cs along with datascheme.
In my implementation when i send an link inside the email and i click OS asks me how should I proceed
1.By app or 2. Chrome This is fine.
But when i click on app it opens from the main activity.
[IntentFilter(new[] { Android.Content.Intent.ActionView },
AutoVerify = true,
Categories = new[]
{
Android.Content.Intent.CategoryDefault,
Android.Content.Intent.CategoryBrowsable
},
DataScheme = "http",
DataPathPrefix = "",
DataHost = "MyAppName")]

Suppose the url you click is http://myappname?destination=a, you can get the data in activity through:
if (Intent.Data != null)
{
var host = Intent.Data.EncodedAuthority;
var parameter = Intent.Data.GetQueryParameter("destination");
}
As you are using Xamarin.Forms, you should navigate to the specify page on Forms. MessagingCenter is a good choice.
Firstly, register it in App on Forms project:
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
MessagingCenter.Subscribe<object, object>(this, "Navigate", (sender, args) =>
{
if ((string)args == "a")
{
MainPage = new SecondPage();
// or (MainPage as NavigationPage).PushAsync(new SecondPage());
}
});
}
Fire this messaging center when you recieve the data:
if (host == "myappname")
{
MessagingCenter.Send<object, object>(this, "Navigate", parameter);
}
Update
If you don't want to use MessagingCenter. Define a public method in App like:
public void MoveToPage(string pageName)
{
if (pageName == "a")
{
MainPage = new SecondPage();
// or (MainPage as NavigationPage).PushAsync(new SecondPage());
}
}
Then call this when Intent.Data != null in MainActivity:
var formsApp = new App();
LoadApplication(formsApp);
if (Intent.Data != null)
{
var host = Intent.Data.EncodedAuthority;
var parameter = Intent.Data.GetQueryParameter("destination");
formsApp.MovePage(parameter);
}

Related

SFSafariViewController notification when token received

Converting the code in the following swift sample to C# (Xamarin) to notify the App when Paypal Token is received inside SFSafariViewController but it does not fire the method.
https://github.com/paypal/paypal-here-sdk-ios-distribution/blob/master/SampleApp/PPHSDKSampleApp/InitializeViewController.swift
Converted the swift to C# as following but after user login to PayPal and receives the token, Safari is not closing to fire SetupMerchant()
UrlSchemes is also set to retailsdksampleapp to match the sample swift app from PayPal.
SafariDelegate safariDelegate = new SafariDelegate(this);
NSNotificationCenter.DefaultCenter.AddObserver(new NSString("kCloseSafariViewControllerNotification"), safariDelegate.SetupMerchant);
void loadBrowser()
{
var url = new NSUrl("https://paypalauth.herokuapp.com/toPayPal/" + Application.paypalEnvironment + "?returnTokenOnQueryString=true");
var svc = new SFSafariViewController(url);
svc.Delegate = safariDelegate;
this.PresentViewController(svc, true, null);
}
public class SafariDelegate : SFSafariViewControllerDelegate
{
ViewController _controller = null;
public SafariDelegate(ViewController controller)
{
_controller = controller;
}
public void SetupMerchant(NSNotification notification)
{
// Dismiss the SFSafariViewController when the notification of token has been received.
this._controller.PresentedViewController?.DismissViewController(true, () => { });
// Grab the token(s) from the notification and pass it into the merchant initialize call to set up
// the merchant. Upon successful initialization, the 'Connect Card Reader' button will be
// enabled for use.
var accessToken = notification.Object.ToString();
}
}
When running the swift sample app from Paypal, it closes the browser (SFSafariViewController) after login and fires the SetupMerchant() but not in the C# code. There is possibly a missing step or invalid code conversion.
If the Safari controller is not closing and you have the proper UrlScheme set (), then you are missing the OpenUrl override in your AppDelegate class that listening for your app's url schemes and posts the notification that your view controller is listening for.
Example:
[Export("application:openURL:sourceApplication:annotation:")]
public bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
{
if (sourceApplication == "com.apple.SafariViewService")
{
var dict = HttpUtility.ParseQueryString(url.Query);
var token = dict["access_token"];
NSNotificationCenter.DefaultCenter.PostNotificationName("kCloseSafariViewControllerNotification", new NSString(token));
};
return true;
}

Open URL in Browser and Change Apps View - Android Xamarin

How can I open a URL in the device's default browser and also change the view of the app so after they are done looking at the opened URL and they go back to the app, they are in a new view?
Below is what I have so far:
Button btnOkay = FindViewById<Button>(Application.Context.Resources.GetIdentifier("btnOkay", "id", Application.Context.PackageName));
btnOkay.Click += (sender, eventArgs) =>
{
// Open Survey Monkey Survey in Browser
var uri = Android.Net.Uri.Parse("https://www.example.com/");
var intent = new Intent(Intent.ActionView, uri);
StartActivity(intent);
// Go to Thank you view in app.
intent = new Intent(this, typeof(PatientPostVisitSurveyThanksActivity));
StartActivity(intent);
};
The issue with this is, it goes to the next view, and will only open the URL in the browser if I click back to go to the original view.
I just want the app view to be changed when the user manually goes back into the app.
You can use the Activity lifecycles for this. Your app's last Activity will execute OnResume after the user press back (or selects it from the active app list). Set a flag before launching the browser and test for that flag in the OnResume
btnOkay.Click += (sender, eventArgs) =>
{
GotoNewActivity = true;
// Open Survey Monkey Survey in Browser
var uri = Android.Net.Uri.Parse("https://www.example.com/");
var intent = new Intent(Intent.ActionView, uri);
StartActivity(intent);
};
bool GotoNewActivity;
protected override void OnResume()
{
base.OnResume();
if (GotoNewActivity)
{
GotoNewActivity = false;
// Go to Thank you view in app.
var intent = new Intent(this, typeof(ThankYouActivity));
StartActivity(intent);
}
}

Using Cognito Federated Identities from Xamarin

I guess my question, Understanding Cognito Identities, wasn't specific enough. I still can't figure out how to use a federated identity from a Xamarin app. Here's what I'm trying, but it's really quite random because I can't find any sample code for this task out there. I tried putting a breakpoint on the AddLogin line, and it never gets hit, even though breakpoint two lines up does get hit. There are too many new-to-me technologies in this code for me to know where to begin on tracking down the problem. (I x'd out the Identity pool ID in the code below, but a real one is there.) At this point I'm just trying to get evidence that I can uniquely identify/validate an Amazon account, and maybe add it to my user pool. But I can't even get the code to entirely execute or report an error.
Login().ContinueWith(t => { if (t.Exception != null)
Toast.MakeText(ApplicationContext, t.Exception.ToString(), ToastLength.Long).Show(); });
public async Task Login()
{
CognitoAWSCredentials credentials = new CognitoAWSCredentials(
"us-east-2:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // Identity pool ID
RegionEndpoint.USEast2 // Region
);
var client = new Amazon.SecurityToken.AmazonSecurityTokenServiceClient(credentials);
var request = new Amazon.SecurityToken.Model.GetFederationTokenRequest("myamazonid#gmail.com");
var response = await client.GetFederationTokenAsync(request);
credentials.AddLogin("www.amazon.com", response.Credentials.SessionToken);
}
It took a good deal of searching, but I think I figured it out. Setting up the services and getting the client ID is not too hard (is well documented) compared to working out the code, so this answer will focus on the code. Google is particularly tricky because of changes made to their OAuth implementation that prevents some forms of authentication from working. In order for Google identities to work with Cognito, APIs need to be up-to-date. Use NuGet to reference the following API versions or later:
Xamarin.Auth 1.5.0.3
Xamarin.Android.Support.v4 25.4.0.2
Xamarin.Android.Support.CustomTabs 25.4.0.2
AWSSDK.CognitoIdentity 3.3.2.14
AWSSDK.Core 3.3.17.8
Validation 2.4.15
Xamarin.Android.Support.Annotations 25.4.0.2
This code is in the main activity:
protected override void OnCreate(Bundle savedInstanceState)
{
// (etc)
credentials = new CognitoAWSCredentials(
"us-east-2:00000000-0000-0000-0000-000000000000", // Identity pool ID
RegionEndpoint.USEast2 // Region
);
// (etc)
}
private void ShowMessage(string message)
{
AlertDialog dlgAlert = new AlertDialog.Builder(this).Create();
dlgAlert.SetMessage(message);
dlgAlert.SetButton("Close", (s, args) => { dlgAlert.Dismiss(); });
dlgAlert.Show();
}
public void Logout()
{
credentials.Clear();
}
public void Login()
{
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
ShowMessage(string.Format("I still remember you're {0} ", credentials.GetIdentityId()));
bDidLogin = true;
return;
}
bDidLogin = true;
auth = new Xamarin.Auth.OAuth2Authenticator(
"my-google-client-id.apps.googleusercontent.com",
string.Empty,
"openid",
new System.Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new System.Uri("com.mynamespace.myapp:/oauth2redirect"),
new System.Uri("https://www.googleapis.com/oauth2/v4/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
}
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var http = new System.Net.Http.HttpClient();
var idToken = e.Account.Properties["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = "us-east-2:00000000-0000-0000-0000-000000000000";
cli.GetIdAsync(req).ContinueWith((task) =>
{
if ((task.Status == TaskStatus.RanToCompletion) && (task.Result != null))
ShowMessage(string.Format("Identity {0} retrieved", task.Result.IdentityId));
else
ShowMessage(task.Exception.InnerException!=null ? task.Exception.InnerException.Message : task.Exception.Message);
});
}
else
ShowMessage("Login cancelled");
}
Then there's another activity to handle the callback from the redirect URL in the Google authentication process:
[Activity(Label = "GoodleAuthInterceptor")]
[IntentFilter(actions: new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataSchemes = new[] { "com.mynamespace.myapp" }, DataPaths = new[] { "/oauth2redirect" })]
public class GoodleAuthInterceptor : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Android.Net.Uri uri_android = Intent.Data;
Uri uri_netfx = new Uri(uri_android.ToString());
MainActivity.auth?.OnPageLoading(uri_netfx);
Finish();
}
}

Xamarin.Forms Google API Authenticating Users with an Identity Provider

I am still getting used to Xamarin.Forms and I am on very basic level. I went through many articles for my issue, but to the end couldn't resolve it. So...
Currently I am trying to add Google authentication inside my Xamarin.Forms application, which use Droid and iOS (no WP).
So far I am following guide from here. I am using Xamarin.Auth to authenticate to Google.
Here is some part from my source code.
private async void GoogleSheetsButton_Tapped()
{
string clientId = null;
string redirectUri = null;
if (Device.RuntimePlatform == Device.iOS)
{
clientId = Constants.iOSClientId;
redirectUri = Constants.iOSRedirectUrl;
}
else if (Device.RuntimePlatform == Device.Android)
{
clientId = Constants.AndroidClientId;
redirectUri = Constants.AndroidRedirectUrl;
}
var authenticator = new OAuth2Authenticator(
clientId,
null,
Constants.Scope,
new Uri(Constants.AuthorizeUrl),
new Uri(redirectUri),
new Uri(Constants.AccessTokenUrl),
null,
true);
authenticator.Completed += OnAuthCompleted;
authenticator.Error += OnAuthError;
AuthenticationState.Authenticator = authenticator;
var presenter = new Xamarin.Auth.Presenters.OAuthLoginPresenter();
presenter.Login(authenticator);
}
The problem is coming after my method complete it's work. So after my last line:
presenter.Login(authenticator);
everything looks alright and debugging I am following that compiler goes out of method without any errors, but then I receive exception, which you can see here. It's "No compatible code running".
Here some more information regarding my source code:
Source of "Constants" class used for client ids and URLs
public static class Constants
{
public static string AppName = "....";
// OAuth
// For Google login, configure at https://console.developers.google.com/
public static string iOSClientId = "6.....apps.googleusercontent.com";
public static string AndroidClientId = "6.....apps.googleusercontent.com";
// These values do not need changing
public static string Scope = "https://www.googleapis.com/auth/userinfo.email";
public static string AuthorizeUrl = "https://accounts.google.com/o/oauth2/auth";
public static string AccessTokenUrl = "https://www.googleapis.com/oauth2/v4/token";
public static string UserInfoUrl = "https://www.googleapis.com/oauth2/v2/userinfo";
// Set these to reversed iOS/Android client ids, with :/oauth2redirect appended
public static string iOSRedirectUrl = "com.googleusercontent.apps.6......h:/oauth2redirect";
public static string AndroidRedirectUrl = "com.googleusercontent.apps.6......l:/oauth2redirect";
}
Source of implemented methods for on authentication complete/error, which in fact still I cannot hit because of my error
async void OnAuthCompleted(object sender, AuthenticatorCompletedEventArgs e)
{
var authenticator = sender as OAuth2Authenticator;
if (authenticator != null)
{
authenticator.Completed -= OnAuthCompleted;
authenticator.Error -= OnAuthError;
}
GoogleLoginUser user = null;
if (e.IsAuthenticated)
{
var request = new OAuth2Request("GET", new Uri(Constants.UserInfoUrl), null, e.Account);
var response = await request.GetResponseAsync();
if (response != null)
{
string userJson = await response.GetResponseTextAsync();
user = JsonConvert.DeserializeObject(userJson);
}
if (_account != null)
{
_store.Delete(_account, Constants.AppName);
}
await _store.SaveAsync(_account = e.Account, Constants.AppName);
await DisplayAlert("Email address", user.Email, "OK");
}
}
void OnAuthError(object sender, AuthenticatorErrorEventArgs e)
{
var authenticator = sender as OAuth2Authenticator;
if (authenticator != null)
{
authenticator.Completed -= OnAuthCompleted;
authenticator.Error -= OnAuthError;
}
var message = e.Message;
}
Source of Android MainActivity where I added
public class MainActivity : FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
Forms.Init(this, bundle);
global::Xamarin.Auth.Presenters.XamarinAndroid.AuthenticationConfiguration.Init(this, bundle);
MobileBarcodeScanner.Initialize(Application);
LoadApplication(new App());
}
}
Source of UrlSchemeInterceptorActivity
[Activity(Label = "CustomUrlSchemeInterceptorActivity", NoHistory = true, LaunchMode = LaunchMode.SingleTop)]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable }, DataSchemes = new[] { "com.googleusercontent.apps.6......l" }, DataPath = "/oauth2redirect")]
public class CustomUrlSchemeInterceptorActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
var uri = new Uri(Intent.Data.ToString());
AuthenticationState.Authenticator.OnPageLoading(uri);
Finish();
}
}
Here are the main articles I went through deeply => Link 1, Link 2 and Link 3, but still couldn't resolve the issue.
I am not sure where the error comes from, or can I can I continue debugging it to resolve issue.
Thanks in advance
Solution
Change android compiler to Android 7.0 inside Android project properties. Screenshot
Make sure that inside Android Manifest your target is SDK Version. Screenshot
Update all "Xamarin.Android.*" nuget packages to minimum version 25.4.0.1. Most probably they're currently to 23.3.0. I found problems with dependencies on updating it, so I make manual upload. I went and download manually each package and move it to packages folder. Then I created my own package source and give for path my folder packages and I used it to install already downloaded NuGet packages. Screenshot
After that my issue was solved.
Please update your Android SDK to API 24 or higher and set it as the compile version for your project. And update your referred assemblies for the custom tabs and its dependencies to v25.x.x.

Facebook Shared Link - Scrape blog page right after published

I'm developing a blog website (using Umbraco) where it also can share its blog page to Facebook automatically after the page has been published. I have created a Facebook app and used Facebook SDK for .NET in order to that and it works but one small problem is that when the link is shared for the first time, it looks ugly. No image, no title and no description, just url. I have to publish the second time to get the proper link. Heres what I've done so far:-
Programmatically use the scrape API using Facebook SDK for .NET. I even tried to loop through the scrape API 10x to test if it work but doesn't.
Supply the necessary Open Graph Tag in the header of the shared page.
og:title
og:description
og:image
og:image:width
og:image:height
og:type
og:fb_id
og:site_name
I'm using Umbraco ContentService Events to invoke the Facebook API functions:-
public class PublishEventHandler : ApplicationEventHandler
{
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
ContentService.Published += ContentServicePublished;
}
private void ContentServicePublished(IPublishingStrategy sender, PublishEventArgs<IContent> args)
{
foreach (var node in args.PublishedEntities)
{
foreach (var item in node.Properties)
{
//check property alias and its value
if (item.Alias == "autoPost" && (int)item.Value == 1)
{
var fbClient = new FacebookAuthClient();
var pageToken = fbClient.GetPageToken();
var url = umbraco.library.NiceUrlWithDomain(node.Id);
fbClient.ScrapePage(url, pageToken);
fbClient.SharePostOnPage(url, pageToken);
}
}
}
}
}
Scrape API function:-
public void ScrapePage(string url, string pageToken)
{
var scrapeResult = new FacebookClient(pageToken)
{
Version = "v2.9"
};
var response = scrapeResult.Post(new { id = url, scrape = "true" });
LogHelper.Info(this.GetType(), "|SCRAPE| " + response.ToString());
}
Share API function:-
public void SharePostOnPage(string url, string pageToken)
{
if (pageToken != null)
{
var result = new FacebookClient(pageToken)
{
Version = "v2.9"
};
var response = result.Post("/" + AuthConfig.facebookPageId + "/feed", new { link = url, scrape = "true" });
LogHelper.Info(this.GetType(), "|SHARE| " + response.ToString());
}
}
One more thing I want to add is when I checked the response for the scrape in the log. I only get the id and url but when I used the Graph API Explorer in https://developers.facebook.com I got the whole information of the page (id,url,title,description,etc..) and yes I've been Googling and researching for a week now for this problem before deciding to post this.

Categories

Resources