Xamarin Android Facebook AppInviteDialog not showing - xamarin

I'm using the Xamarin.FacebookSdk to display the App Invite Dialog. For iOS everything works great but for Android, nothing displays.
Update
It turns out iOS isn't always working. I actually get the following error sometimes:
[0:] Invite Failed Error Domain=com.facebook.sdk.core Code=9 "(null)"
I can step through the following code until it gets to the AppInv.Show. However, Show never shows anything. I tried having the Facebook app installed and without it installed.
public class FacebookService : IFacebookService
{
public void InviteFriends(string appLinkUrl, string previewImageUrl)
{
if (AppInviteDialog.CanShow())
{
var activity = Xamarin.Forms.Forms.Context as Activity;
var content = new AppInviteContent.Builder().SetApplinkUrl(appLinkUrl).SetPreviewImageUrl(previewImageUrl).Build() as AppInviteContent;
//AppInviteDialog.Show(activity, content);
AppInviteDialog AppInv = new AppInviteDialog(activity);
var callbackManager = CallbackManagerFactory.Create();
var invitecallback = new CCallback();
AppInv.RegisterCallback(callbackManager, invitecallback);
AppInv.Show(content);
}
}
}
public class CCallback : Java.Lang.Object, IFacebookCallback
{
public void OnCancel()
{
System.Diagnostics.Debug.WriteLine($"Invite was cancelled");
}
public void OnError(FacebookException error)
{
System.Diagnostics.Debug.WriteLine($"Invite failed {error.Message}");
}
public void OnSuccess(Java.Lang.Object result)
{
System.Diagnostics.Debug.WriteLine($"Invite was a success: {result}");
}
}
I added the following to the AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
........
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="#string/app_id" />
<activity android:name="com.facebook.FacebookActivity" android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation" android:theme="#android:style/Theme.Translucent.NoTitleBar" android:label="#string/app_name" />
<provider android:authorities="com.facebook.app.FacebookContentProviderXXXXXMy App IdXXXX" android:name="com.facebook.FacebookContentProvider" android:exported="true" />

It turns out that AppInviteDialog was deprecated by Facebook.
It sure would have been nice for a more informative error to be displayed.

Related

Get reminders based on data in a database

I want to get reminders for some Appointments that I have saved in database and they have a notificationTime property witch is the time when a notification needs to be displayed.
My approach so far is to write some kind of job that runs 1 or 2 times a day to pull the notifications that need to be registered in the next 24h and ofc register them (if you guys have a better ideea lmk :D )
This works BUT:
Only if the app is in foreground / background; then I get notification every 15min or so;
If I KILL the app I don't receive notification on my physical device (Xiaomi Redmi Note 9 Pro with Android version 12 SKQ),
only on the virtual one (Pixel 5 Android 13)
Right now I have a class that extends JobService and I use JobScheduler to schedule the Job to run every 15 min (for testing so I don't need to w8 12h xD )
Here is the JobScheduler witch I call in MainActivity file in OnCreate method
Console.WriteLine("Schedualing job");
TimeSpan interval = TimeSpan.FromMinutes(15);
var javaClass = Java.Lang.Class.FromType(typeof(NotificationService));
var componentName = new ComponentName(Application.Context, javaClass);
var jobInfo = new JobInfo.Builder(1, componentName)
.SetPeriodic(15 * 60 * 1000, 30 * 60 * 1000)
.SetRequiredNetworkType(NetworkType.Any)
.SetPersisted(true)
.Build();
var jobScheduler = (JobScheduler)GetSystemService(JobSchedulerService);
var resultCode = jobScheduler.Schedule(jobInfo);
and here is the NotificationService.cs
[Service(Name = "com.companyname.deratizare_mobile.NotificationService",
Permission = "android.permission.BIND_JOB_SERVICE")]
public class NotificationService : JobService
{
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
return StartCommandResult.StickyCompatibility;
}
public override bool OnStartJob(JobParameters #params)
{
Console.WriteLine("Job started");
Task.Run(async () =>
{
//var hasSuccessful = await ProccessNotificationToRegister();
var notification = new NotificationRequest
{
Title = "Job",
Description = $"Description",
Schedule = new NotificationRequestSchedule
{
NotifyTime = DateTime.Now,
}
};
LocalNotificationCenter.Current.Show(notification);
JobFinished(#params, false);
Console.WriteLine("Job finished");
});
return true;
}
public override bool OnStopJob(JobParameters #params)
{
Console.WriteLine("Job stopped");
return true;
}
}
AndroidManifest
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.BIND_JOB_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
UPDATE
I have given permission to auto start to my app from the device and it works
SOLUTION
I ended up using FCM and a hosted service on the server that checks the cache every 5 minutes where I have stored the next notification that needs to be displaied
You can try to use Firebase push notifications.
With push notifications, you can update your users without requiring the app to run at all times or having it poll a server, potentially running down the battery.
For more information, you can check Implementing Push Notifications in Your Android Apps and Firebase Cloud Messaging.

Xamarin.Auth OAuth Google API for user authentication for Android

I have been trying to authenticate via Google OAuth 2.0. Following this link.
Have been able to open the Google Auth page and login successfully.
The problem is after logging in i am redirected to the google search page.
If i close the application then in the OnAuthCompleted method of the OAuth2Authenticator i get the e.IsAuthenticated to false and not able to get any information of the user.
Xamarin Share Library code:
var authenticator = new OAuth2Authenticator(
"somekey-somekey1.apps.googleusercontent.com",
null,
"email",
new Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new Uri("com.companyname.somenameofapp:/oauth2redirect"),
new Uri("https://www.googleapis.com/oauth2/v4/"),
null,
true);
var presenter = new Xamarin.Auth.Presenters.OAuthLoginPresenter();
presenter.Login(authenticator);
authenticator.Completed += OnAuthCompleted;
AuthenticationState.Authenticator = authenticator;
AuthenticationState Class
public class AuthenticationState
{
public static OAuth2Authenticator Authenticator;
}
The OnAuthCompleted method
private async void OnAuthCompleted(object sender, AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
await Navigation.PushAsync(new GoogleLoginSuccess());
}
}
Main Activity Code
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
global::Xamarin.Auth.Presenters.XamarinAndroid.AuthenticationConfiguration.Init(this, savedInstanceState);
LoadApplication(new App());
}
CustomUrlSchemeInterceptorActivity
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
namespace XamAppGoogleAuth.Droid
{
[Activity(Label = "CustomUrlSchemeInterceptorActivity")]
[IntentFilter(
new[] { Intent.ActionView },
Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataSchemes = new[] { "com.companyname.somenameofapp" },
DataPath = ":/oauth2redirect",
DataHost = "com.companyname.somenameofapp")]
public class CustomUrlSchemeInterceptorActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
global::Android.Net.Uri uri_android = Intent.Data;
Uri uri_netfx = new Uri(uri_android.ToString());
// load redirect_url Page
AuthenticationState.Authenticator.OnPageLoading(uri_netfx);
var intent = new Intent(this, typeof(MainActivity));
intent.SetFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop);
StartActivity(intent);
this.Finish();
return;
}
}
}
AndroidManifest
<?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.companyname.somenameofapp">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
<application android:label="somenameofapp.Android"></application>
<activity android:name="somenameofapp.Android.MainActivity" android:label="MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.companyname.somenameofapp" android:host="com.companyname.somenameofapp" />
</intent-filter>
</activity>
</manifest>
When i close the Custom Tab explicitly i am navigated back to the application with the below toast message.
What i would want is after the user authenticates with Google OAuth, they are redirected back to the application and I can get the access token and other information as needed. I have been to a lot of links regarding this but havent been able to find a solution. Kindly help
#kinder-cappy
I noticed you have the URL scheme interceptor activity in your application manifest as well as an activity class. You will have to choose one or else you will have multiple intent on trigger.
To remove the "customtabs login screen ...." message add this to your MainActivity (not interceptor class) after Xamarin.Auth initialization:
global::Xamarin.Auth.CustomTabsConfiguration.CustomTabsClosingMessage = null;
Also use Xamarin.Auth version 1.5.0.3 for now in android. It works.
If the current redirect URL scheme you specified does not works, try to get the correct redirect URL from your provider documentations. In my case, I used Google so I am using the inverted URL (client id).
If the current redirect URL scheme you specified does not work, try to get the correct redirect URL from your provider documentation. In my case, I used Google so I am using the inverted URL (client id).
In your CustomUrlSchemeInterceptorActivity DataSchemes and in Redirect URI you need to put inverted URL (ie. client ID) which you can get from the developer console.
client-id : "somekey-somekey1.apps.googleusercontent.com"
Redirect URI looks like : com.googleusercontent.apps.somekey-somekey1:/oauth2redirect
DataSchemes = new[] { "com.googleusercontent.apps.somekey-somekey1" } at your CustomUrlSchemeInterceptorActivity

How do I open an internally created pdf in Xamarin.Android via a FileProvider in the default system pdf app?

I created a .pdf file in the private local directory I got via (I try to work with the minimum of permissions):
string targetBaseDir = Environment.GetFolderPath(
Environment.SpecialFolder.Personal,
Environment.SpecialFolderOption.Create
);
The official Android documentation suggests that file should be shared via a FileProvider.
I also tried to get some code to start an intent, and I'm currently at:
var fileUri = Android.Net.Uri.Parse(filePath);
var intent = new Intent();
intent.SetFlags(ActivityFlags.ClearTop);
intent.SetFlags(ActivityFlags.NewTask);
intent.SetAction(Intent.ActionView);
intent.SetType("application/pdf");
intent.PutExtra(Intent.ExtraStream, fileUri);
intent.AddFlags(ActivityFlags.GrantReadUriPermission);
Android.App.Application.Context.StartActivity(intent);
This starts the share dialog for a pdf file but while the Adobe Pdf reader gets opened it shows an empty view and not my pdf file.
You need to wrap your URI with FileProvider. Since android uri will give you file:// while FileProvider will give you content://, which you actually need:
public static Android.Net.Uri WrapFileWithUri(Context context,Java.IO.File file)
{
Android.Net.Uri result;
if (Build.VERSION.SdkInt < (BuildVersionCodes)24)
{
result = Android.Net.Uri.FromFile(file);
}
else
{
result = FileProvider.GetUriForFile(context, context.ApplicationContext.PackageName + ".provider", file);
}
return result;
}
File can be createed this way:
var file = new Java.IO.File(filePath);
Then you can open it:
public static void View(Context context, string path, string mimeType)
{
Intent viewIntent = new Intent(Intent.ActionView);
Java.IO.File document = new Java.IO.File(path);
viewIntent.SetDataAndType(UriResolver.WrapFileWithUri(context,document),mimeType);
viewIntent.SetFlags(ActivityFlags.NewTask);
viewIntent.AddFlags(ActivityFlags.GrantReadUriPermission);
context.StartActivity(Intent.CreateChooser(viewIntent, "your text"));
}
Be aware, that this line
viewIntent.SetDataAndType(UriResolver.WrapFileWithUri(context,document),mimeType);
does not equal to SetData and SetType separate commands.
And yes, you need to add FileProvider to your manifest:
<provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="#xml/provider_paths" />
</provider>
Also you need to create Resources\xml folder with provider_paths.xml file:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
<files-path name="internal_files" path="." />
</paths>

Geo location is not working in xamarin android

I am try to get the current geo location of the phone using 'plugin.geolocator' but it works fine in ios in android getting task cancellation exception. Here is the code I have tried. Please suggest any idea why I am getting this exception.
sample code:
var locator = CrossGeolocator.Current;
locator.DesiredAccuracy = -1;
if (locator.IsGeolocationEnabled)
{
var position = await locator.GetPositionAsync(TimeSpan.FromMilliseconds(5000));
}
These are the permissions I have gave in android manifest file.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
I am getting task cancellation exception while I am testing in emulator. I have tried with changing time span but no luck getting task cancellation exception only.
Set a different value for Desired accuracy (big values -> more chance to have a result):
locator.DesiredAccuracy = 100;
Well, you also have to override OnRequestPermissionResult on your MainActivity class:
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
And ensure your Android Target version is API 25+. See https://jamesmontemagno.github.io/GeolocatorPlugin/GettingStarted.html

in-app billing connection issue

I am trying to connect the in-app-billing but the OnConnected event not being hit.
my Android Manifest code is
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="alphaTestApp.alphaTestApp" android:versionCode="1" android:versionName="1.0">
<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="21" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:label="alphaTestApp" android:icon="#drawable/Icon"></application>
</manifest>
And the Main Activity file code is.
[Activity(Label = "alphaTestApp", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : Activity
{
private InAppBillingServiceConnection _serviceConnection;
string publicKey = "REDACTED";
private IList<Product> _products;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
_serviceConnection = new InAppBillingServiceConnection(this, publicKey);
if (_serviceConnection == null)
{
Toast t = Toast.MakeText(this, "Error while connecting", ToastLength.Long);
t.Show();
}
_serviceConnection.Connect();
Toast t1 = Toast.MakeText(this, "Connected app", ToastLength.Long);
t1.Show();
_serviceConnection.OnConnected += () =>
{
Toast t2 = Toast.MakeText(this, "Retrieving Items", ToastLength.Long);
t2.Show();
_products = _serviceConnection.BillingHandler.QueryInventoryAsync(new List<string> {
"goldcoin100"
}, ItemType.Product) as IList<Product>;
if (_products == null)
return;
_serviceConnection.BillingHandler.BuyProduct(_products[0]);
};
Button button = FindViewById<Button>(Resource.Id.MyButton);
button.Click += delegate
{
Toast t3 = Toast.MakeText(this, "Retrieving Items", ToastLength.Long);
t3.Show();
_products = _serviceConnection.BillingHandler.QueryInventoryAsync(new List<string> {
"goldcoin100"
}, ItemType.Product) as IList<Product>;
if (_products == null)
return;
_serviceConnection.BillingHandler.BuyProduct(_products[0]);
};
}
}
It's probably not the reason, but it would be best to set your OnConnected handler before you call Connect just in case it connects in less time than it takes for the Toast code to run.
Also, you should make the OnConnected handler async and then await on the call to QueryInventoryAsync (in a Lambda the async keyword goes before the brackets, e.g. OnConnected += async () => ...). As it is, QueryInventoryAsync will return a Task<IList<Product>> so trying to convert it using as IList<Product> will fail and _products will always be null.
If you're running on an emulator, make sure that you've got Google Play Services installed and up-to-date. There's an article on doing this for the Xamarin Android Player here: Installing Google Play Services in XAP. Although note that BuyProduct shouldn't work on an emulator using real products, you'll need to use the test product IDs (e.g. ReservedTestProductIDs.Purchased, and note that once you've purchased this one you'll need to call ConsumePurchase on it before you can purchase it again).
Also, don't post your API key on an open forum.

Resources