Xamarin.Forms and Plugin.Media: after about 20 photos something crashes - xamarin.forms

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.

Related

How do you hand over files to the user?

in a #WASM / #UNO-platform project, how do you hand over files to the user?
In my case I’m generation locally a PDF and had to download it or display it in the browser.
Any clue?
Regards,
Michael
There's no API to do that directly, yet. But you can create a data: url on an anchor (a) HTML element.
For this you'll need to create some JavaScript. Here's how you can do it:
IMPORTANT: following code will only work with very recent version of Uno.UI. Version starting with v3.0.0-dev.949+
Create a ContentControl for the <a> tag
[HtmlElement("a")]
public partial class WasmDownload : ContentControl
{
public static readonly DependencyProperty MimeTypeProperty = DependencyProperty.Register(
"MimeType", typeof(string), typeof(WasmDownload), new PropertyMetadata("application/octet-stream", OnChanged));
public string MimeType
{
get => (string) GetValue(MimeTypeProperty);
set => SetValue(MimeTypeProperty, value);
}
public static readonly DependencyProperty FileNameProperty = DependencyProperty.Register(
"FileName", typeof(string), typeof(WasmDownload), new PropertyMetadata("filename.bin", OnChanged));
public string FileName
{
get => (string) GetValue(FileNameProperty);
set => SetValue(FileNameProperty, value);
}
private Memory<byte> _content;
public void SetContent(Memory<byte> content)
{
_content = content;
Update();
}
private static void OnChanged(DependencyObject dependencyobject, DependencyPropertyChangedEventArgs args)
{
if (dependencyobject is WasmDownload wd)
{
wd.Update();
}
}
private void Update()
{
if (_content.Length == 0)
{
this.ClearHtmlAttribute("href");
}
else
{
var base64 = Convert.ToBase64String(_content.ToArray());
var dataUrl = $"data:{MimeType};base64,{base64}";
this.SetHtmlAttribute("href", dataUrl);
this.SetHtmlAttribute("download", FileName);
}
}
}
Use it in Your XAML Page
<myControls:WasmDownload FileName="test.txt" x:Name="download">
Click here to download
</myControls:WasmDownload>
Note you can put anything in the content of your control, as any other XAML ContentControl.
Set the File Content in Code Behind
Loaded += (sender, e) =>
{
download.MimeType = "text/plain";
var bytes = Encoding.UTF8.GetBytes("this is the content");
download.SetContent(bytes);
};
Result
Direct support by Uno
There is a PR #3380 to add this feature to Uno natively for all platforms. You can also wait for it instead of doing custom way.
The PR for FileSavePicker has been merged and the feature is now available in package Uno.UI since version 3.0.0-dev.1353.

Uno Platform camera preview control

How can I add an camera preview in a uno XAML Page?
Could be like this example
https://learn.microsoft.com/en-us/windows/uwp/audio-video-camera/simple-camera-preview-access
But the CameraCapture.InitializeAsyn is not implemented on Uno.
When it runs the following exception is thrown:
System.NotImplementedException: The member IAsyncAction
MediaCapture.InitializeAsync() is not implemented in Uno.
If is possible to use native android code, this sample do what I need.
https://learn.microsoft.com/en-us/samples/xamarin/monodroid-samples/android50-camera2basic/
Thanks!
On Android, you have at least two ways for doing something similar:
You can use the following, with native android intents, that you'll need to make conditional with __ANDROID__:
public MainPage()
{
...
BaseActivity.Current.ActivityResult += Current_ActivityResult;
}
private void Current_ActivityResult(object sender, BaseActivity.ActivityResultEventArgs e)
{
if (e.Data != null)
{
var bitmap = (Bitmap)e.Data.Extras.Get("data");
image.Source = bitmap;
}
else
{
image.Source = null;
}
}
public void button_Click(object sender, RoutedEventArgs e)
{
var intent = new Intent(MediaStore.ActionImageCapture);
BaseActivity.Current.StartActivityForResult(intent, 0);
}
or using the CameraCaptureUI class:
{
try
{
var captureUI = new CameraCaptureUI();
captureUI.PhotoSettings.Format = CameraCaptureUIPhotoFormat.Jpeg;
captureUI.PhotoSettings.CroppedSizeInPixels = new Size(200, 200);
var photo = await captureUI.CaptureFileAsync(CameraCaptureUIMode.Photo);
if (photo == null)
{
return;
}
else
{
var source = new BitmapImage(new Uri(photo.Path));
image.Source = source;
}
}
catch(Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
}
You can find the full examples for CameraCaptureUI, and native android.
Other types of camera capture are currently not implemented (as of Uno 3.0), and you'll need to go through native APIs to use them.

Xamarin Forms - Get device location every 10 seconds (when app runs in foreground/background)

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

xamarin forms ios camera crash issue

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.

error creating tile for microsoft-band

I have been getting errors when trying to create a tile on my band. I can't seem to get through it. Here is the error:
namespace Band_Test_3
{
#if !DISABLE_XAML_GENERATED_MAIN
public static class Program
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Windows.UI.Xaml.Build.Tasks"," 4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
static void Main(string[] args)
{
global::Windows.UI.Xaml.Application.Start((p) => new App());
}
}
#endif
partial class App : global::Windows.UI.Xaml.Application
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Windows.UI.Xaml.Build.Tasks"," 4.0.0.0")]
private bool _contentLoaded;
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Windows.UI.Xaml.Build.Tasks"," 4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public void InitializeComponent()
{
if (_contentLoaded)
return;
_contentLoaded = true;
#if DEBUG && !DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT
DebugSettings.BindingFailed += (sender, args) =>
{
global::System.Diagnostics.Debug.WriteLine(args.Message);
};
#endif
#if DEBUG && !DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
UnhandledException += (sender, e) =>
{
if (global::System.Diagnostics.Debugger.IsAttached) global::System.Diagnostics.Debugger.Break();
};
#endif
}
}
}
Using this code. The error is happening on the andClient.TileManager.AddTileAsync(tile) line:
private async void btnCreateTile_Click(object sender, RoutedEventArgs e)
{
txbMessage.Text = "";
// create a new Guid for the tile
// create a new tile with a new Guid
BandTile tile = new BandTile(tileGuid)
{
// enable badging (the count of unread messages)
IsBadgingEnabled = true,
// set the name
Name = "Band Test",
// set the icons
SmallIcon = await LoadIcon("ms-appx:///Assets/Certificate-WF Small.png"),
TileIcon = await LoadIcon("ms-appx:///Assets/Certificate-WF.png")
};
await bandClient.TileManager.AddTileAsync(tile);
}
The most common errors I have seen for failing to add a tile are:
Duplicate GUID for the tile as one already on the band. If you have installed the sample tiles with the sample apps then make sure you are using a GUID that is different from the sample apps. The best thing here is to generate your own GUID for your tile.
Maximum number of tiles are installed on the band. To check this, the exception should contain the error message, but you can query the number of tiles installed or you can look at the number installed via the Microsoft Health App in the Manage Tiles view (the number of tiles free is listed towards the top).
If those do now help, then please past in the exception you are getting.

Resources