I have a xamarin forms cross platform application. In that application i am using a xamarin forms dependency service to take photo from iOS and android device on native platform. Android camera working fine but iOS camera giving error and app crash suddenly. I log and trace issues of iOS camera as following.
** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present modally an active controller .'
Following are my iOS native code used to take photo and it returns photo stream as result.I call this method or service from my shared xamarin form project using dependency service.
public Task<CameraResult> TakePictureAsync(bool cameraFlag) { var tcs = new TaskCompletionSource<CameraResult>();
Camera.TakePicture(UIApplication.SharedApplication.KeyWindow.RootViewController, (imagePickerResult) =>
{
if (imagePickerResult == null)
{
tcs.TrySetResult(null);
return;
}
var photo = imagePickerResult.ValueForKey(new NSString("UIImagePickerControllerOriginalImage")) as UIImage;
// You can get photo meta data with using the following
// var meta = obj.ValueForKey(new NSString("UIImagePickerControllerMediaMetadata")) as NSDictionary;
var documentsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
string imageName = Settings.AppuserUserId + "_" + Guid.NewGuid();
string jpgFilename = Path.Combine(documentsDirectory, imageName + ".jpg");
NSData imgData = photo.AsJPEG();
NSError err = null;
if (imgData.Save(jpgFilename, false, out err))
{
CameraRes
ult result = new CameraResult();
result.Picture = ImageSource.FromStream(imgData.AsStream);
result.FilePath = jpgFilename;
tcs.TrySetResult(result);
}
else
{
tcs.TrySetException(new Exception(err.LocalizedDescription));
} });
return tcs.Task; }
void OnImagePickerFinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs args)
{
UIImage image = args.EditedImage ?? args.OriginalImage;
if (image != null)
{
// Convert UIImage to .NET Stream object
CameraResult result = new CameraResult();
NSData data = image.AsJPEG();
result.Picture = ImageSource.FromStream(data.AsStream);
taskCompletionSource.SetResult(result);
}
else
{
taskCompletionSource.SetResult(null);
}
imagePicker.DismissModalViewController(true);
}
void OnImagePickerCancelled(object sender, EventArgs args)
{
taskCompletionSource.SetResult(null);
imagePicker.DismissModalViewController(true);
}
}
Please share any solution for this issue and error.
Related
I have created a Xamarin forms application. The application should periodically (every 10 sec) get the location of the device (iOS and Android). How can I achieve this? I know there are some libraries for example: Xamarin.Essentials, but I can't decide how many times the location should be taken.
It should also be possible to get the local of the device when the Xamarin forms application runs in the background (on IOS and Android).
I passed for that headache in the past, a cross-platform app that update location every x seconds and it should run in the background.
I had developed a template in Xamarin Forms that support Background Location Updates, Background permissions, these capabilities need to be adjusted depending on the OS (iOs/Android).
Check my Github repo here
I used Xamarin Essentials and Messaging Center for this purpose.
Please let me know if the template works for your needs.
Thanks.
You can do it with device timer. The timer will run periodically & will check for location updates and notify if location updated. I have used Xam.Plugins.Notifier to generate local notification on location update.
Here is the code for it :
public partial class MainPage : ContentPage
{
Location oldLocation = null;
CancellationTokenSource cts;
public MainPage()
{
InitializeComponent();
Device.StartTimer(TimeSpan.FromSeconds(10), () =>
{
GetCurrentLocation();
return true;
});
}
protected async override void OnAppearing()
{
base.OnAppearing();
await GetCurrentLocation();
}
async Task GetCurrentLocation()
{
try
{
var request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10));
cts = new CancellationTokenSource();
var location = await Geolocation.GetLocationAsync(request, cts.Token);
if (location != null)
{
Debug.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
if (oldLocation == null)
{
oldLocation = location;
map.MoveToRegion(MapSpan.FromCenterAndRadius(
new Position(location.Latitude, location.Longitude), Distance.FromMiles(0.3)));
}
if (location.Latitude != oldLocation.Latitude || location.Longitude != oldLocation.Longitude)
{
map.MoveToRegion(MapSpan.FromCenterAndRadius(
new Position(location.Latitude, location.Longitude), Distance.FromMiles(0.3)));
oldLocation = location;
double zoomLevel = 0.5;
double latlongDegrees = 360 / (Math.Pow(2, zoomLevel));
if (map.VisibleRegion != null)
{
map.MoveToRegion(new MapSpan(map.VisibleRegion.Center, latlongDegrees, latlongDegrees));
}
var placemarks = await Geocoding.GetPlacemarksAsync(location.Latitude, location.Longitude);
var placemark = placemarks?.FirstOrDefault();
if (placemark != null)
{
var geocodeAddress =
$"AdminArea: {placemark.AdminArea}\n" +
$"CountryCode: {placemark.CountryCode}\n" +
$"CountryName: {placemark.CountryName}\n" +
$"FeatureName: {placemark.FeatureName}\n" +
$"Locality: {placemark.Locality}\n" +
$"PostalCode: {placemark.PostalCode}\n" +
$"SubAdminArea: {placemark.SubAdminArea}\n" +
$"SubLocality: {placemark.SubLocality}\n" +
$"SubThoroughfare: {placemark.SubThoroughfare}\n" +
$"Location : {placemark.Location}\n" +
$"Thoroughfare: {placemark.Thoroughfare}\n";
Debug.WriteLine(geocodeAddress);
}
CrossLocalNotifications.Current.Show("Location Updated", "You checked in to " + placemark.FeatureName + " " + placemark.Locality + " " + placemark.SubLocality, 101, DateTime.Now.AddSeconds(5));
}
}
}
catch (FeatureNotSupportedException)
{
// Handle not supported on device exception
}
catch (FeatureNotEnabledException)
{
// Handle not enabled on device exception
}
catch (PermissionException)
{
// Handle permission exception
}
catch (Exception)
{
// Unable to get location
}
}
}
For Android you can try to start a Service that uses the LocationManager of Android to start listening to Location changes. You can specify a timeinterval and a minimum distance you want to track.
This section helped me fiqure out how to use it. For me it was sending location updates even when the app was suspended (physical device running Android 6.1).
To get the location I made my Service a 'LocationListener' and implemented the ILocationListener-Interface like so:
[Service]
public class TestService : Service, ILocationListener
{
public override IBinder OnBind(Intent intent)
{
return null;
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
// start your location updates with the locationManager here
return StartCommandResult.Sticky; // remember to return sticky for the service to run when app is suspended
}
public override void OnDestroy() { }
...
public void OnLocationChanged(Location location)
{
// react to location changes here
}
public void OnProviderDisabled(string provider) { }
public void OnProviderEnabled(string provider) { }
public void OnStatusChanged(string provider, Availability status, Bundle extras) { }
}
For more information on Backgrounding and how to set up a service read this.
Important to note is that the locationUpdates where not consistantly timed (sometimes took more that 10 seconds), since you just give a minimumTime and the OS processes the Request based on its' capacities. But it wasn't too bad.
Update: this doesnt seem to work for Android 8.0 and above. see here
I'm develop cross-platform mobile application that use NFC. I already check the xamarin android beam sample here. Now i'm trying implement the same sample using xamarin forms so i'm using dependency service to call the function from android project.
I already create Ndef message and send function:
using System;
using System.Text;
using Android.App;
using MyApp.Droid;
using Android.Nfc;
using Xamarin.Forms;
[assembly: Dependency(typeof(PhoneBeam))]
namespace MyApp.Droid
{
public class PhoneBeam : Activity, NfcAdapter.ICreateNdefMessageCallback, NfcAdapter.IOnNdefPushCompleteCallback, iBeam
{
private NfcAdapter nfcAdapter;
public void Beam()
{
nfcAdapter = NfcAdapter.GetDefaultAdapter(MainActivity.Instance);
nfcAdapter.SetNdefPushMessageCallback(this, MainActivity.Instance);
nfcAdapter.SetOnNdefPushCompleteCallback(this, MainActivity.Instance);
}
public NdefMessage CreateNdefMessage(NfcEvent evt)
{
DateTime time = DateTime.Now;
var text = ("Beam me up!\n\n" + "Beam : " +
time.ToString("HH:mm:ss"));
NdefMessage msg = new NdefMessage(
new NdefRecord[]{ CreateMimeRecord (
"application/com.companyname.MyApp",
Encoding.UTF8.GetBytes (text)) });
return msg;
}
public NdefRecord CreateMimeRecord(String mimeType, byte[] payload)
{
byte[] mimeBytes = Encoding.UTF8.GetBytes(mimeType);
NdefRecord mimeRecord = new NdefRecord(
NdefRecord.TnfMimeMedia, mimeBytes, new byte[0], payload);
return mimeRecord;
}
public void OnNdefPushComplete(NfcEvent e){}
}
}
However, I really don't know how to receive a message. In android beam sample, they implement it in mainactivity. Here's sample:
protected override void OnResume ()
{
base.OnResume ();
if (NfcAdapter.ActionNdefDiscovered == Intent.Action) {
ProcessIntent (Intent);
}
}
void ProcessIntent (Intent intent)
{
IParcelable [] rawMsgs = intent.GetParcelableArrayExtra (
NfcAdapter.ExtraNdefMessages);
NdefMessage msg = (NdefMessage) rawMsgs [0];
mInfoText.Text = Encoding.UTF8.GetString (msg.GetRecords () [0].GetPayload ());
}
So i want to implement in class file so i can use dependencyService. Is there a way to implement this?
Edit: I did the send function:
public NdefMessage CreateNdefMessage (NfcEvent evt)
{
DateTime time = DateTime.Now;
var text = ("Beam me up!\n\n" +
"Beam Time: " + time.ToString ("HH:mm:ss"));
NdefMessage msg = new NdefMessage (
new NdefRecord[] { CreateMimeRecord (
"application/com.companyname.MyApp", Encoding.UTF8.GetBytes (text))
});
return msg;
}
But it return as "NEW TAG COLLECTED: application/com.companyname.MyApp". I want to resume MyApp and show the message. But it didn't.
I have a problem with Xamarin.Forms ver. 2.3.4.224 and Plugin.Media ver. 2.6.2. The problem occurs after taking about 20 photos (depends from the device): basically the app crashes without any apparently reason.
If you want to replicate the error, I created a test project for you on GitHub. With my iPad Air or iPad Pro after about 30 photos (video iPad Air - iPad Pro). All devices are iOS ver. 10.3.1 and they have enough space to storage photos.
The app is very simple: you have two buttons one for taking a picture and the other one to pick a photo. If you take photos one after another, after about 20 (32 in an iPad Air) the app crashes. I'm just take photos with the Plugin.Media nothing more.
Any ideas are welcome.
Update
In my project I had a reference to Refractored.MvvmHelpers and I noticed if I remove it, I can take more pictures. I created my BaseViewModel with INotifyPropertyChanged and I noticed I can take more photos.
I created then a new project (you can find it on GitHub under cameratesteasy) without MVVM and there is just the code to take a photo like:
public partial class cameratesteasyPage : ContentPage
{
int count = 0;
public cameratesteasyPage()
{
InitializeComponent();
CrossMedia.Current.Initialize();
}
void UpdateCount()
{
count++;
CountLabel.Text = $"{count} times";
}
async void StartCameraTapped(object sender, System.EventArgs args)
{
using (var file = await CrossMedia.Current.TakePhotoAsync(
new StoreCameraMediaOptions {}))
{
if (file == null)
return;
UpdateCount();
}
}
async void StartCameraTakeTapped(object sender, System.EventArgs args)
{
var file = await CrossMedia.Current.PickPhotoAsync();
if (file == null)
return;
UpdateCount();
}
}
In this case the app shut down after 52 photos. I saved the log for Xcode and you can see it here.
I used Xamarin Profile and the memory level is always low. After about 30 photos, an error occurs in Xamarin Profiler
Finally I could create a Xamarin Profiler file
Also I noticed this kind of error occurs on iPads. The same app in an iPhone is working fine (apparently) or I didn't find up to now the number of photos before crashing.
Update /2
I decided to implement a native function for taking photo.
Interface
public interface ICamera
{
void TakePicture();
}
Implementation
using System;
using cameratest.iOS;
using Foundation;
using UIKit;
using Xamarin.Forms;
[assembly: Xamarin.Forms.Dependency(typeof(Camera_iOS))]
namespace cameratest.iOS
{
public class Camera_iOS : ICamera
{
static UIImagePickerController picker;
static Action<NSDictionary> _callback;
static void Init()
{
if (picker != null)
return;
picker = new UIImagePickerController();
picker.Delegate = new CameraDelegate();
}
class CameraDelegate : UIImagePickerControllerDelegate
{
public override void FinishedPickingMedia(
UIImagePickerController picker, NSDictionary info)
{
var cb = _callback;
_callback = null;
picker.DismissModalViewController(true);
cb(info);
}
}
public static void TakePicture(UIViewController parent,
Action<NSDictionary> callback)
{
Init();
picker.SourceType = UIImagePickerControllerSourceType.Camera;
_callback = callback;
parent.PresentModalViewController(picker, true);
}
public static void SelectPicture(UIViewController parent,
Action<NSDictionary> callback)
{
Init();
picker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
_callback = callback;
parent.PresentModalViewController(picker, true);
}
public void TakePicture()
{
var rc = UIApplication.SharedApplication.KeyWindow.RootViewController;
TakePicture(rc, (obj) =>
{
var photo = obj.ValueForKey(
new NSString("UIImagePickerControllerOriginalImage")) as UIImage;
var documentsDirectory =
Environment.GetFolderPath(Environment.SpecialFolder.Personal);
// hardcoded filename, overwritten each time
string jpgFilename = System.IO.Path.Combine(documentsDirectory,
"Photo.jpg");
NSData imgData = photo.AsJPEG();
NSError err = null;
if (imgData.Save(jpgFilename, false, out err))
{
Console.WriteLine("saved as " + jpgFilename);
}
else
{
Console.WriteLine("NOT saved as " +
jpgFilename + " because" + err.LocalizedDescription);
}
});
}
}
}
With this code after about 30 photos, the app crashes. The only difference is with this code I can receive some alert from ReceiveMemoryWarning. If you have an interest, I updated the code on GitHub.
I am integrating Onedrive SDK with the UWP part of my Xamarin App. Once I press the download button I get the Onedrive signin page but it throws the above error in this line:
try
{
var appFolder = await OneDriveClient.Drive.Special.AppRoot.Request().GetAsync();
Debug.WriteLine(appFolder.Name);
}
catch (ServiceException e )
{
Debug.WriteLine(e.Message +" " + e.Error.Code);
}
here is the full relevant code:
public async Task Download(string filename)
{
//AccountSelectionLoaded();
await InitializeClient();
try
{
var appFolder = await OneDriveClient.Drive.Special.AppRoot.Request().GetAsync();
Debug.WriteLine(appFolder.Name);
}
catch (ServiceException e )
{
Debug.WriteLine(e.Message +" " + e.Error.Code);
}
var file = await OneDriveClient.Drive.Special.AppRoot.Children[filename].Content.Request().GetAsync();
//var fileStream = await fileBuilder.Content.Request().GetAsync();
IStorageFile appFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("test.db3",
CreationCollisionOption.OpenIfExists);
byte[] fileBytes;
using (DataReader reader = new DataReader(file.AsInputStream()))
{
fileBytes = new byte[file.Length];
await reader.LoadAsync((uint)file.Length);
reader.ReadBytes(fileBytes);
}
Debug.WriteLine(fileBytes.Length);
Debug.WriteLine("Writing");
await FileIO.WriteBytesAsync(appFile, fileBytes);
Debug.WriteLine("End of writing");
}
private async Task InitializeClient()
{
if (OneDriveClient == null)
{
Task authTask;
var msaAuthProvider = new MsaAuthenticationProvider(oneDriveConsumerClientId,oneDriveConsumerReturnUrl,scopes);
await msaAuthProvider.AuthenticateUserAsync();
OneDriveClient = new OneDriveClient(oneDriveConsumerBaseUrl, msaAuthProvider);
AuthenticationProvider = msaAuthProvider;
}
}
Thank you for reporting this issue. Indeed, we can see a associated issue using OneDrive .NET SDK 2.0.4 in a UWP app
I will report this issue through internal way.
As a workaround, please see this blog: Windows 10 - Implementing a UWP App with the Official OneDrive SDK
Laurent Bugnion described the detailed steps(and also a demo) to enable OneDrive features in a UWP app.
private IOneDriveClient _client;
public MainPage()
{
InitializeComponent();
AuthenticateButton.Click += async (s, e) =>
{
var scopes = new[]
{
"onedrive.readwrite",
"onedrive.appfolder",
"wl.signin"
};
_client = OneDriveClientExtensions.GetClientUsingOnlineIdAuthenticator(
_scopes);
var session = await client.AuthenticateAsync();
Debug.WriteLine($"Token: {session.AccessToken}");
};
}
At that time, the project is using 1.2.0 SDK which is still working now.
I receive correctly a notification from GCM but I want insert the message in a local (sqlite) database.
If I receive the notification when my app is not running, it doesn't insert the message but if my application was running then it does.
void SendNotification (string message)
{
var intent = new Intent (this, typeof(MainActivity));
intent.AddFlags (ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity (this, 0, intent, PendingIntentFlags.OneShot);
var notificationBuilder = new Notification.Builder (this)
.SetSmallIcon (Resource.Drawable.icstatbutton_click)
.SetContentTitle ("GCM Message")
.SetContentText ("U heeft een nieuwe vragenlijst.")
.SetAutoCancel (true)
.SetContentIntent (pendingIntent);
var notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
notificationManager.Notify (0, notificationBuilder.Build());
try
{
DataAccess.InsertDownload (message);
}
catch (Exception ex)
{
}
}
Can I access sqlite database when my application is not running ?
Is your DataAccess.InsertDownload() method in your shared code? If so, that is the same thing I ran into.
Probably not the best way to solve it but what I did was to save the JSON string into Android's SharedPreferences if the app is in fact closed. Then the app is loaded again, within MainActivity and after loading the shared project, I attempt to read out any SharedPreferences and save them to the DB.
Below is some code showing this. Here is a link to SettingsImplementation.
public async Task SaveNotifToDb(Notification notification) {
try {
DataAccess.InsertDownload (message);
} catch(System.InvalidOperationException) { //InvalidOperationException is the exception given when your shared code is not yet loaded, meaning the app is closed, so now lets save to Preferences
System.Console.WriteLine("\nIn APP.Droid.Helpers.GcmService.SaveNotificationAsync() - InvalidOperationException, the app is probably closed. Saving to Shared Preferences\n");
string notificationJson = Newtonsoft.Json.JsonConvert.SerializeObject(notification);
string emptyCheck = SettingsImplementation.GetValueOrDefault<string>(DroidConstants.NotificationSettingKeyPart + "0", DroidConstants.NotificationSettingDefault);
if(emptyCheck.Length > 0) {
int index = 0;
while(emptyCheck.Length > 0) {
emptyCheck = SettingsImplementation.GetValueOrDefault<string>(DroidConstants.NotificationSettingKeyPart + index.ToString(), DroidConstants.NotificationSettingDefault);
index ++;
}
SettingsImplementation.AddOrUpdateValue<string>(DroidConstants.NotificationSettingKeyPart + (index - 1).ToString(), notificationJson);
} else { SettingsImplementation.AddOrUpdateValue<string>(DroidConstants.NotificationSettingKeyPart + "0", notificationJson); }
return notification;
}
}
Now when the app is started, we wait for the shared code to load and then try to read all the notification JSON back out.
MainActivity.OnCreate():
base.OnCreate(bundle);
Xamarin.Forms.Forms.Init(this, bundle);
string notificationJson = SettingsImplementation.GetValueOrDefault(DroidConstants.NotificationSettingKeyPart + "0", DroidConstants.NotificationSettingDefault); //Check to see if we have a saved notification
if(notificationJson.Length > 0) {
int index = 0;
while(notificationJson.Length > 0) { //Keep trying until no more notifications can be gatherd
notificationJson = SettingsImplementation.GetValueOrDefault(DroidConstants.NotificationSettingKeyPart + index, DroidConstants.NotificationSettingDefault);
if(notificationJson.Length > 0) {
Data.Models.RemoteNotification notification = Newtonsoft.Json.JsonConvert.DeserializeObject<Data.Models.RemoteNotification>(notificationJson);
if(notification != null) {
try {
await App.RemoteNotificationRepo.InsertAsync(notification);
} catch(System.Exception e) {
System.Console.WriteLine("\nIn APP.Droid.MainActivity.OnCreate() - Exception attempting to create new in app notification\n{0}\n", e);
}
}
SettingsImplementation.Remove(DroidConstants.NotificationSettingKeyPart + index.ToString());
index++;
}
}
}
Yes. You can access Sqlite when application is not running. In your Activity's OnCreate you can check for new messages and update accordingly.