Reputation: 1452
I have a Xamarin forms PCL app, using the Xam.Plugin.Media helper. I have a page where the user invokes a camera helper on a button push to take a picture. The bytes from the camera helper return to the page and serve as the source for an image. There is a save button on the page where I basically call the messaging service and save the bytes to PCL SQlite storage. The problem is I get about 3 successful loads of this page, and can take a picture with the camera helper before I get an exception AFTER the image is taken with the camera, but before it returns with the bytes. The exception message is 'Too many open files'. This is for iOS. All relevant code is below. Thanks
Camera Helper:
public class CameraHelper
{
private MediaFile file;
public async Task<byte[]> TakePicture()
{
await CrossMedia.Current.Initialize();
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
throw new Exception("No camera available");
}
using (MediaFile file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
Name = $"photo{DateTime.Now.ToString("yyyyMMddHHmmss")}.jpg",
PhotoSize = PhotoSize.Small,
CompressionQuality = 80,
AllowCropping = true,
}))
{
if (file == null)
{
return null;
}
using (System.IO.Stream stream = file.GetStream())
{
ImgBytes = new byte[stream.Length];
await stream.ReadAsync(ImgBytes, 0, Convert.ToInt32(stream.Length));
file.Dispose();
}
}
return ImgBytes;
}
}
Take Photo Page:
<ContentPage.Content>
<StackLayout VerticalOptions="FillAndExpand" Padding="12,10,12,15">
<Label x:Name="photoTypeLabel" Text="Take *photo type* Photo" VerticalOptions="Start" />
<StackLayout Padding="0,30,0,70">
<ffimageloading:CachedImage x:Name="Image" Grid.Row="0" FadeAnimationEnabled="true" Aspect="AspectFill"
HeightRequest="200" WidthRequest="125" >
<ffimageloading:CachedImage.GestureRecognizers>
<TapGestureRecognizer Tapped="OnImageTapped" />
</ffimageloading:CachedImage.GestureRecognizers>
</ffimageloading:CachedImage>
</StackLayout>
<Label Grid.Row="0" Grid.Column="1"
Text="{ x:Static local:GrialShapesFont.PhotoCamera }"
Style="{StaticResource FontIcon}"
HorizontalTextAlignment="Center"
Opacity="1"
FontSize="60"
TextColor="#FF000000"
VerticalOptions="Center"
HorizontalOptions="Center">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="OnCameraTapped" />
</Label.GestureRecognizers>
</Label>
<Button Style="{StaticResource PrimaryActionButtonStyle}" VerticalOptions="End" Text="Save" WidthRequest="{ artina:OnOrientationDouble
LandscapePhone=200,
LandscapeTablet=400 }" HorizontalOptions="{ artina:OnOrientationLayoutOptions
PortraitPhone=Fill,
LandscapePhone=Center,
PortraitTablet=Fill,
LandscapeTablet=Center }" Clicked="saveButtonClicked" />
<Button Style="{StaticResource PrimaryActionButtonStyle}" VerticalOptions="End" Text="Cancel" WidthRequest="{ artina:OnOrientationDouble
LandscapePhone=200,
LandscapeTablet=400 }" HorizontalOptions="{ artina:OnOrientationLayoutOptions
PortraitPhone=Fill,
LandscapePhone=Center,
PortraitTablet=Fill,
LandscapeTablet=Center }" Clicked="cancelButtonClicked" />
</StackLayout>
</ContentPage.Content>
Take Photo Page Code Behind:
private async void OnCameraTapped(object sender, EventArgs args)
{
CameraHelper cameraHelper = new CameraHelper();
try
{
ImgBytes = await cameraHelper.TakePicture();
Image.Source = ImageSource.FromStream(() =>
{
return new MemoryStream(ImgBytes);
});
}
catch (Exception ex)
{
if (ex.Message == "No camera available")
{
await DisplayAlert("Error", "No camera available", "Ok");
}
else
{
await DisplayAlert("Error", "Unable to take picture.", "Ok");
}
}
}
Upvotes: 0
Views: 281
Reputation: 1
I sugest you to see Xamarin Essential camera view. You can implement your own custom camera screen.
https://learn.microsoft.com/pt-br/xamarin/community-toolkit/views/cameraview?source=recommendations
Then you can save the images in your local storage and just save the path in local database, or if you want can save in Base64 string, but i don't recomend.
Upvotes: 0
Reputation: 13352
If I'm reading the code correctly, GetStream
opens a new stream to the file every time. I would try wrapping your CrossMedia.Current.TakePhotoAsync
call and the stream in using
statements just to make sure they get disposed of properly.
public class CameraHelper
{
public async Task<byte[]> TakePicture()
{
await CrossMedia.Current.Initialize();
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
throw new Exception("No camera available");
}
using ( MediaFile file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
Name = $"photo{DateTime.Now.ToString("yyyyMMddHHmmss")}.jpg",
PhotoSize = PhotoSize.Small,
CompressionQuality = 80,
AllowCropping = true,
}) ) {
if (file == null) {
return null;
}
using (System.IO.Stream stream = file.GetStream()) {
byte[] ImgBytes;
ImgBytes = new byte[stream.Length];
stream.Read(ImgBytes, 0, Convert.ToInt32(stream.Length));
}
}
return ImgBytes;
}
}
Upvotes: 2