Xamarin forms WebView cookies cleared when app closed - xamarin

I have Xamarin Forms WebView app for android. I'm able to use the app perfectly fine on first login. But if i re-open the app WebView cookies clear and user need to login again.
I want that the user does not need to log in after they have logged in once, even if they close the application.
Here is my code:
var wbURL = "https://www.example.com/login/";
webrowser.Source = wbURL;

On Android, cookies should be stored automatically, unless you delete them manually using a custom renderer.
For iOS, you can save cookies and restore them later on, something like this:
Save cookies (Call this method after the user is logged in, this code is placed into the WebView custom renderer):
public async Task SaveCookies()
{
// For iOS < 10, cookies are saved in NSHTTPCookieStorage.sharedHTTPCookieStorage(), coojies should work withouth this
if (!UIDevice.CurrentDevice.CheckSystemVersion(11, 0))
{
return;
}
try
{
var cookies = await Configuration.WebsiteDataStore.HttpCookieStore.GetAllCookiesAsync();
var cachedCookies = cookies.Select(c => new Cookie(c.Name, c.Value, c.Path, c.Domain)
{
Secure = c.IsSecure,
Version = Convert.ToInt32(c.Version)
}).ToList();
//TODO: Save the cachedCookies into app cache. You can create a service in the shared project
}
catch (Exception e)
{
}
}
Restore cookies (Call this in the WebView custom renderer OnElementChanged method):
var store = WKWebsiteDataStore.NonPersistentDataStore;
await RestoreCookies(store);
private async Task RestoreCookies(WKWebsiteDataStore store)
{
// For iOS < 10, cookies are saved in NSHTTPCookieStorage.sharedHTTPCookieStorage(), coojies should work withouth this
if (!UIDevice.CurrentDevice.CheckSystemVersion(11, 0))
{
return;
}
if (store == null)
{
return;
}
await ClearCookies(store);
var storedCookies = //TODO: Retreive cookies from where you sotred them
foreach (Cookie cookie in storedCookies)
{
await store.HttpCookieStore.SetCookieAsync(new NSHttpCookie(cookie));
}
}
private async Task ClearCookies(WKWebsiteDataStore store)
{
var cookies = await store.HttpCookieStore.GetAllCookiesAsync();
if (cookies?.Any() ?? false)
{
foreach (NSHttpCookie cookie in cookies)
{
await store.HttpCookieStore.DeleteCookieAsync(cookie);
}
}
}
EDIT:
If you have problems on Android as well, try this code in your WebView custom renderer:
var cookieManager = CookieManager.Instance;
cookieManager.SetAcceptCookie(true);
cookieManager.AcceptCookie();
cookieManager.Flush(); // Forces cookie sync

Related

Xam.Plugin.Geolocator never returns on first run iOS 14

I am using the CrossGeolocator.Current.StartListeningAsync method in my xamarin app to be able to listen to location updates in background for iOS.
I am using essentials to request permissions.
On first run we get location permission (in app only) and location using essentials and then we use StartListeningAsync for ios to be able to track location if the app is in background or foreground.
When the callback is hit we get a popup saying this app uses background location and gives you the option to use it or change back to use in app only option. On selection of any option the callback never completes and subsequent code isnt run.
Here is the popup I get after I have permission for when in use and then start listening:
Popup on ios
On subsequent runs once permissions are set manually the callback works.
Xamarin Forms Version: 5.0.0.1931
Xamarin Essential Version: 1.6.1
Geolocator Plugin Version: 4.6.2-beta
Code example:
private async Task StartListening()
{
if (CrossGeolocator.Current.IsListening)
return;
try
{
var settings = new ListenerSettings
{
ActivityType = ActivityType.Other,
DeferLocationUpdates = true,
DeferralDistanceMeters = 15,
DeferralTime = TimeSpan.FromSeconds(10),
ListenForSignificantChanges = false,
PauseLocationUpdatesAutomatically = false,
AllowBackgroundUpdates = true,
};
await CrossGeolocator.Current.StartListeningAsync(TimeSpan.FromSeconds(15), 5, true, settings);
CrossGeolocator.Current.PositionChanged += PositionChanged;
CrossGeolocator.Current.PositionError += PositionError;
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
You need to make sure the permission is ok before starting to listen to location updates. Have a try with following code:
public async Task GetLocationAsync()
{
var status = await CheckAndRequestPermissionAsync(new Permissions.LocationAlways());
if (status != PermissionStatus.Granted)
{
// Notify user permission was denied
return;
}else{
await StartListening();
}
}
public async Task<PermissionStatus> CheckAndRequestPermissionAsync<T>(T permission)
where T : BasePermission
{
var status = await permission.CheckStatusAsync();
if (status != PermissionStatus.Granted)
{
status = await permission.RequestAsync();
}
return status;
}

Xamarin Forms iOS Remote Notifications Not Displaying

Remote Notifications are not displaying on the device.
We are using iOS 11.2 and Twilio.
We have generated the APN in Apple Developer Portal and exported the
certificate and key into Twilio.
Twilio says the message is "sent," but it never displays on the
device.
The goal is to send a message with a simple header and body text, and have that display as a remote push notification on the device.
The Xamarin documentation seems incomplete, and I cannot find clear instructions on how to handle displaying the notification. I have looked at the Xamarin samples, but they mostly cover local notifications.
Questions are below in the comments. What is missing?
using Foundation;
using UserNotifications;
using UIKit;
namespace MyNotifications.iOS
{
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
protected UIWindow window;
protected string deviceToken = string.Empty;
public string DeviceToken { get { return deviceToken; } }
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
// check for a notification while running
if (options != null)
{
if (options.ContainsKey(UIApplication.LaunchOptionsRemoteNotificationKey))
{
NSDictionary remoteNotification = options[UIApplication.LaunchOptionsRemoteNotificationKey] as NSDictionary;
if (remoteNotification != null)
{
//1) is this necessary to handle??? if so, how to display? what are the properties from the remoteNotification object that contain the text?
}
}
}
//this prompts for permissions, which are set
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
var notificationSettings = UIUserNotificationSettings.GetSettingsForTypes(
UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound, null
);
app.RegisterUserNotificationSettings(notificationSettings);
app.RegisterForRemoteNotifications();
}
else
{
UIRemoteNotificationType notificationTypes = UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge;
UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(notificationTypes);
}
UNUserNotificationCenter.Current.GetNotificationSettings((settings) =>
{
var alertsAllowed = (settings.AlertSetting == UNNotificationSetting.Enabled);
});
// Request notification permissions from the user
UNUserNotificationCenter.Current.RequestAuthorization(UNAuthorizationOptions.Alert, (approved, err) =>
{
// 2) how do we handle this??? what comes next?
});
return base.FinishedLaunching(app, options);
}
// 3) does this override need to do anything???
public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
// 4) should all of these trigger a notification? does that have to happen manually?
if (application.ApplicationState == UIApplicationState.Active)
{
ProcessPushNotification(userInfo, true);
}
else if (application.ApplicationState == UIApplicationState.Background)
{
ProcessPushNotification(userInfo, true);
}
else if (application.ApplicationState == UIApplicationState.Inactive)
{
ProcessPushNotification(userInfo, true);
}
}
protected void ProcessPushNotification(NSDictionary userInfo, bool isAppAlreadyRunning)
{
if (userInfo == null) return;
if (isAppAlreadyRunning)
{
// 5) do we need to generate our own view???
}
else
{
// 6) how to handle in the background???
}
}
// APNS background
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
deviceToken = deviceToken.ToString();
}
// Handle errors and offline
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
{
// 7) what to do here???
}
}
}
Microsoft Docs has a few guides on how to deal with push notifications on both platforms, iOS and Android. Most of them are sending notifications from an Azure Notifications Hub but in this case, it shouldn't make any difference, because your question is about displaying push notifications, and not sending them.
The guide on how to send and receive notifications in a Xamarin.Forms app gives you an idea of the complete end-to-end setup. There are also two slightly different guides with a focus on Azure here and here
And for rendering messages on iOS, if the app is backgrounded, the notification is rendered by iOS without any custom code so if the app is properly configured and signed, you should be able to see a push notifications without any additional client-side code, just make sure you test on a device (sims doesn't support pushes) and valid profile with pushes enabled.

Play custom sound in Xamarion IOS local notification

I am trying to play custom mp3 sound file of (10-25 seconds) with local notification. I have placed the custom sound file in iOS project folder (same level as Resources) with BundleResource as BuildAction. I have also placed the sound file in Resources folder with same build action. It seems both doesn't work.
var content = new UNMutableNotificationContent
{
Title = title,
//Subtitle = "Notification Subtitle",
Body = body,
Badge = 1,
Sound = UNNotificationSound.GetSound("music.mp3"), //play custom sound
UserInfo = NSDictionary.FromObjectAndKey(NSObject.FromObject(id), NSObject.FromObject(notificationKey))
};
var trigger = UNTimeIntervalNotificationTrigger.CreateTrigger(10, false);
var requestID = "request_" + id;
var request = UNNotificationRequest.FromIdentifier(requestID, content, trigger);
UNUserNotificationCenter.Current.AddNotificationRequest(request, (err) =>
{
if (err != null)
{
throw new Exception($"Failed to schedule notification: {err}");
}
});
Any suggestion?
The code works fine, my phone sound was mute.

Sharing targeted xamarin forms

I use the following command to share a link, but with this command opens a box with apps for me to share. I want when I share it already go straight to facebook, without going through this box
void OnTapped4(object sender, EventArgs args)
{
CrossShare.Current.ShareLink(link, "teste", titulo);
}
Need to do direct shares to facebook, whatsapp, twitter and email
I have this command plus it works only on xamarin android, in xamarin forms it would no work
Intent sendIntent = new Intent();
sendIntent.SetAction(Intent.ActionSend);
sendIntent.PutExtra(Intent.ExtraText,"titulo");
sendIntent.SetType("text/plain");
sendIntent.SetPackage("com.facebook.orca");
StartActivity(sendIntent);
I found the following example where I did on android and it worked, now I want to do this in IOS how can I get it to go to whatsapp
Android
public class ShareService : IShareService
{
public void SharePageLink(string url)
{
var context = Forms.Context;
Activity activity = context as Activity;
Intent share = new Intent(Intent.ActionSend);
share.SetType("text/plain");
share.AddFlags(ActivityFlags.ClearWhenTaskReset);
share.PutExtra(Intent.ExtraSubject, "Brusselslife");
share.SetPackage("com.whatsapp");
share.PutExtra(Intent.ExtraText, url);
activity.StartActivity(Intent.CreateChooser(share, "Share link!"));
}
}
In IOS where to put this 'com.whatsapp'
public class ShareService : IShareService
{
public void SharePageLink(string url)
{
var window = UIApplication.SharedApplication.KeyWindow;
var rootViewController = window.RootViewController;
//SetPackage
var activityViewController = new UIActivityViewController(new NSString[] { new NSString(url) }, null);
activityViewController.ExcludedActivityTypes = new NSString[] {
UIActivityType.AirDrop,
UIActivityType.Print,
UIActivityType.Message,
UIActivityType.AssignToContact,
UIActivityType.SaveToCameraRoll,
UIActivityType.AddToReadingList,
UIActivityType.PostToFlickr,
UIActivityType.PostToVimeo,
UIActivityType.PostToTencentWeibo,
UIActivityType.PostToWeibo
};
rootViewController.PresentViewController(activityViewController, true, null);
}
}

Making a login method in Xamarin.forms

Once the user submits the login info, and presses login button the following method is called;
public Page OnLogInButtonClicked (string email, string password)
{
var client = new RestClient("http://babyAPI.com");
var request =
new RestRequest("api/ApiKey?email=" + email + "&password=" + password, Method.GET);
var queryResult = client.Execute(request);
if (queryResult.StatusCode == HttpStatusCode.OK)
{
var deserial = new JsonDeserializer();
var x = deserial.Deserialize<ApiKey>(queryResult);
return;
}
else
{
return;
}
}
Is this the correct way to do it? And if the user is authenticated, I need to navigate to a new page, else show authentication failed. How can that be done?
Your question is quite broad. What are you looking to solve?
Here's what I think can be improved in this code. Assuming this is a ViewModel class - if it isn't read up on MVVM in Xamarin. I'd suggest also reading up on Separation of Concerns (also see the links on the bottom about DRY and Single Responsibility)
// make the method async so your UI doesn't lock up
public async Task AuthenticateAndNavigate(string email, string pass){
// your MVVM framework may have IsBusy property, otherwise - define it
// it should be bindable so you can use it to bind activity indicators' IsVisible and buttons' IsEnabled
IsBusy = true;
try{
// split out the code that talks to the server in a separate class - don't mix UI, ViewModel and server interactivity (separation of concerns principle)
// the assumption here is that a null is returned if auth fails
var apiKey = BabyApi.GetApiKey(email, pass);
// the Navigation property below exists in some MVVM frameworks, otherwise it comes from a Page instance that calls this code
if (apiKey!=null)
await Navigation.PushModalAsync(new HomePage());
else
await Navigation.PushAsync(new FailedToAuthenticatePage()); // or just do nothing
} catch {
await Navigation.PushAsync(new FailedToAuthenticatePage { Error: x.Message });
} finally {
IsBusy = false;
}
}

Resources