Is anyone able to give a short, self-contained example on how to access the camera with Xamarin.Forms 1.3.x? Simply calling the native camera application and retrieving the resulting picture would be great. Displaying a live view on the Xamarin.Forms page would be awesome!
I already tried to use Xamarin.Mobile and Xamarin.Forms.Labs, but I couldn't get any solution to work on both platforms (focussing on Android and iOS for now). Most code snippets found on the web (including stackoverflow) are incomplete, e.g. not showing the implementation of an IMediaPicker object or where to anchor the method for taking pictures.
I finally created a minimum solution for iOS and Android.
The shared project
First, let's look into the shared code. For an easy interaction between the shared App class and the platform-specific code we store a static Instance within the public static App:
public static App Instance;
Furthermore, we will display an Image, which will be filled with content later. So we create a member:
readonly Image image = new Image();
Within the App constructor we store the Instance and create the page content, which is a simple button and the aforementioned image:
public App()
{
Instance = this;
var button = new Button {
Text = "Snap!",
Command = new Command(o => ShouldTakePicture()),
};
MainPage = new ContentPage {
Content = new StackLayout {
VerticalOptions = LayoutOptions.Center,
Children = {
button,
image,
},
},
};
}
The button's click handler calls the event ShouldTakePicture.
It is a public member and the platform-specific code parts will assign to it later on.
public event Action ShouldTakePicture = () => {};
Finally, we offer a public method for displaying the captured image:
public void ShowImage(string filepath)
{
image.Source = ImageSource.FromFile(filepath);
}
The Android project
On Android we modify the MainActivity.
First, we define a path for the captured image file:
static readonly File file = new File(Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures), "tmp.jpg");
At the end of OnCreate we can use the static Instance of the created App and assign an anonymous event handler, which will start a new Intent for capturing an image:
App.Instance.ShouldTakePicture += () => {
var intent = new Intent(MediaStore.ActionImageCapture);
intent.PutExtra(MediaStore.ExtraOutput, Uri.FromFile(file));
StartActivityForResult(intent, 0);
};
Last but not least, our activity has to react on the resulting image. It will simply push its file path to the shared ShowImage method.
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
App.Instance.ShowImage(file.Path);
}
That's about it!
Just don't forget to set the "Camera" and the "WriteExternalStorage" permission within "AndroidManifest.xml"!
The iOS project
For the iOS implementation we create a custom renderer.
Therefore, we add a new file "CustomContentPageRenderer" and add the corresponding assembly attribute right after the using statements:
[assembly:ExportRenderer(typeof(ContentPage), typeof(CustomContentPageRenderer))]
The CustomContentPageRenderer inherits from PageRenderer:
public class CustomContentPageRenderer: PageRenderer
{
...
}
We override the ViewDidAppear method and add the following parts.
Create a new image picker controller referring to the camera:
var imagePicker = new UIImagePickerController { SourceType = UIImagePickerControllerSourceType.Camera };
Present the image picker controller, as soon as the ShouldTakePicture event is raised:
App.Instance.ShouldTakePicture += () => PresentViewController(imagePicker, true, null);
After taking the picture, save it to the MyDocuments folder and call the shared ShowImage method:
imagePicker.FinishedPickingMedia += (sender, e) => {
var filepath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "tmp.png");
var image = (UIImage)e.Info.ObjectForKey(new NSString("UIImagePickerControllerOriginalImage"));
InvokeOnMainThread(() => {
image.AsPNG().Save(filepath, false);
App.Instance.ShowImage(filepath);
});
DismissViewController(true, null);
};
And finally, we need to handle a cancellation of the image taking process:
imagePicker.Canceled += (sender, e) => DismissViewController(true, null);
Try out James Montemagno's MediaPlugin.
You can install the plugin using Package Manager Console by simply typing and running Install-Package Xam.Plugin.Media -Version 2.6.2 or else go to Manage NuGet Packages... and type Xam.Plugin.Media and install the plugin. (The plugins has to be installed in all your projects - including the client projects)
A readme.txt will be prompted and follow the instructions there. After that, add the following codes (as required) to your shared project. The instructions to be followed in the above readme.txt file are as follows.
For Android Project
In your BaseActivity or MainActivity (for Xamarin.Forms) add this code:
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
You must also add a few additional configuration files to adhere to the new strict mode:
Add the following to your AndroidManifest.xml inside the <application> tags:
<provider android:name="android.support.v4.content.FileProvider"
android:authorities="YOUR_APP_PACKAGE_NAME.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths"></meta-data>
</provider>
YOUR_APP_PACKAGE_NAME must be set to your app package name!
Add a new folder called xml into your Resources folder and add a new XML file called file_paths.xml
Add the following code:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Android/data/YOUR_APP_PACKAGE_NAME/files/Pictures" />
<external-path name="my_movies" path="Android/data/YOUR_APP_PACKAGE_NAME/files/Movies" />
</paths>
YOUR_APP_PACKAGE_NAME must be set to your app package name!
For iOS Project
Your app is required to have keys in your Info.plist for NSCameraUsageDescription and NSPhotoLibraryUsageDescription in order to access the device's camera and photo/video library. If you are using the Video capabilities of the library then you must also add NSMicrophoneUsageDescription. The string that you provide for each of these keys will be displayed to the user when they are prompted to provide permission to access these device features.
Such as:
<key>NSCameraUsageDescription</key>
<string>This app needs access to the camera to take photos.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to photos.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone.</string>
For Shared Project
To simply open camera, save photo and display an alert with the file path, enter the following to the shared project.
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
await DisplayAlert("No Camera", ":( No camera avaialble.", "OK");
return;
}
var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
PhotoSize = Plugin.Media.Abstractions.PhotoSize.Medium,
Directory = "Sample",
Name = "test.jpg"
});
if (file == null)
return;
await DisplayAlert("File Location", file.Path, "OK");
Here is how you can do it on Xamarin Forms cross Xamarin iOS.
This is a good base to start but it requires a Page to rendered first in which you could just specify the UIApplication for it to provide the UIView for Camera / Photo Picker controllers.
https://stackoverflow.com/a/28299259/1941942
Portable Project
public interface ICameraProvider
{
Task<CameraResult> TakePhotoAsync();
Task<CameraResult> PickPhotoAsync();
}
private Command AttachImage
{
var camera = await DependencyService.Get<ICameraProvider>().TakePhotoAsync();
}
iOS Project
[assembly: Xamarin.Forms.Dependency(typeof(CameraProvider))]
public class CameraProvider : ICameraProvider
{
private UIImagePickerController _imagePicker;
private CameraResult _result;
private static TaskCompletionSource<CameraResult> _tcs;
public async Task<CameraResult> TakePhotoAsync()
{
_tcs = new TaskCompletionSource<CameraResult>();
_imagePicker = new UIImagePickerController { SourceType = UIImagePickerControllerSourceType.Camera };
_imagePicker.FinishedPickingMedia += (sender, e) =>
{
_result = new CameraResult();
var filepath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "tmp.png");
var image = (UIImage)e.Info.ObjectForKey(new NSString("UIImagePickerControllerOriginalImage"));
_result.ImageSource = ImageSource.FromStream(() => new MemoryStream(image.AsPNG().ToArray()));
_result.ImageBytes = image.AsPNG().ToArray();
_result.FilePath = filepath;
_tcs.TrySetResult(_result);
_imagePicker.DismissViewController(true, null);
};
_imagePicker.Canceled += (sender, e) =>
{
UIApplication.SharedApplication.KeyWindow.RootViewController.DismissViewController(true, null);
};
await UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewControllerAsync(_imagePicker, true);
return await _tcs.Task;
}
public async Task<CameraResult> PickPhotoAsync()
{
_tcs = new TaskCompletionSource<CameraResult>();
_imagePicker = new UIImagePickerController
{
SourceType = UIImagePickerControllerSourceType.PhotoLibrary,
MediaTypes = UIImagePickerController.AvailableMediaTypes(UIImagePickerControllerSourceType.PhotoLibrary)
};
_imagePicker.FinishedPickingMedia += (sender, e) =>
{
if (e.Info[UIImagePickerController.MediaType].ToString() == "public.image")
{
var filepath = (e.Info[new NSString("UIImagePickerControllerReferenceUrl")] as NSUrl);
var image = (UIImage)e.Info.ObjectForKey(new NSString("UIImagePickerControllerOriginalImage"));
//var image = e.Info[UIImagePickerController.OriginalImage] as UIImage;
_result.ImageSource = ImageSource.FromStream(() => new MemoryStream(image.AsPNG().ToArray()));
_result.ImageBytes = image.AsPNG().ToArray();
_result.FilePath = filepath?.Path;
}
_tcs.TrySetResult(_result);
_imagePicker.DismissViewController(true, null);
};
_imagePicker.Canceled += (sender, e) =>
{
UIApplication.SharedApplication.KeyWindow.RootViewController.DismissViewController(true, null);
};
await UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewControllerAsync(_imagePicker, true);
return await _tcs.Task;
}
}
Here's what I needed to get async camera-capture running in my app:
In iOS:
public async Task<string> TakePicture()
{
if (await AuthorizeCameraUse())
{
var imagePicker = new UIImagePickerController { SourceType = UIImagePickerControllerSourceType.Camera };
TaskCompletionSource<string> FinishedCamera = new TaskCompletionSource<string>();
// When user has taken picture
imagePicker.FinishedPickingMedia += (sender, e) => {
// Save the file
var filepath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "tmp.png");
var image = (UIImage)e.Info.ObjectForKey(new NSString("UIImagePickerControllerOriginalImage"));
image.AsPNG().Save(filepath, false);
// Close the window
UIApplication.SharedApplication.KeyWindow.RootViewController.DismissViewController(true, null);
// Stop awaiting
FinishedCamera.SetResult(filepath);
};
// When user clicks cancel
imagePicker.Canceled += (sender, e) =>
{
UIApplication.SharedApplication.KeyWindow.RootViewController.DismissViewController(true, null);
FinishedCamera.TrySetCanceled();
};
// Show the camera-capture window
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(imagePicker, true, null);
// Now await for the task to complete or be cancelled
try
{
// Return the path we've saved the image in
return await FinishedCamera.Task;
}
catch (TaskCanceledException)
{
// handle if the user clicks cancel
}
}
return null;
}
And if you need the authorization routine, make sure you also fill out the camera use in info.plist too, and here's the function to get authorization:
public static async Task<bool> AuthorizeCameraUse()
{
var authorizationStatus = AVCaptureDevice.GetAuthorizationStatus(AVMediaType.Video);
if (authorizationStatus != AVAuthorizationStatus.Authorized)
{
return await AVCaptureDevice.RequestAccessForMediaTypeAsync(AVMediaType.Video);
}
else
return true;
}
In Android:
private TaskCompletionSource<bool> _tcs_NativeCamera;
public async Task<string> TakePicture()
{
_tcs_NativeCamera = new TaskCompletionSource<bool>();
// Launch the camera activity
var intent = new Intent(MediaStore.ActionImageCapture);
intent.PutExtra(MediaStore.ExtraOutput, Android.Net.Uri.FromFile(cameraCaptureFilePath));
NextCaptureType = stype;
StartActivityForResult(intent, SCAN_NATIVE_CAMERA_CAPTURE_ASYNC);
// Wait here for the activity return (through OnActivityResult)
var Result = await _tcs_NativeCamera.Task;
// Return the camera capture file path
return Result != Result.Canceled ? cameraCaptureFilePath : null;
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
switch (requestCode)
{
case SCAN_NATIVE_CAMERA_CAPTURE_ASYNC:
_tcs_NativeCamera.SetResult(resultCode);
break;
}
}
Related
I have created a very basic MauiApp because I wanted to try the MediaPicker on the Windows Platform.
Thus I copied the code from the official documentation and tried to run my application
However if I add <uap:Capability Name="webcam"/> to the Package.appxmanifest file as suggested in the documentaion, and run the application it gives me the following error:
Error DEP0700: Registration of the app failed. [0x80080204] error 0xC00CE169: App
manifest validation error: The app manifest must be valid as per schema: Line 39, Column
21, Reason: 'webcam' violates enumeration constraint of 'documentsLibrary
picturesLibrary videosLibrary musicLibrary enterpriseAuthentication
sharedUserCertificates userAccountInformation removableStorage appointments contacts
phoneCall blockedChatMessages objects3D voipCall chat'.
The attribute 'Name' with value 'webcam' failed to parse. MauiApp3
So in order to solve this problem I tried to change the capability from <uap:Capability Name="webcam"/> to <DeviceCapability Name="webcam"/>.
In this way I can run the application without errors, but photo is always null:
public async void TakePhoto(object sender, EventArgs e)
{
if (MediaPicker.Default.IsCaptureSupported)
{
FileResult photo = await MediaPicker.Default.CapturePhotoAsync();
if (photo != null)
{
// save the file into local storage
string localFilePath = Path.Combine(FileSystem.CacheDirectory, photo.FileName);
using Stream sourceStream = await photo.OpenReadAsync();
using FileStream localFileStream = File.OpenWrite(localFilePath);
await sourceStream.CopyToAsync(localFileStream);
}
else
{
// *** IT ALWAYS ENTERS IN THE ELSE CLAUSE ***
// *** BECAUSE photo IS ALWAYS NULL ***
CounterBtn.Text = $"Capture is supported but {photo} is null";
}
}
}
Note: The function above is called when I click to this button that I've defined in MainPage.xaml file:
<Button
x:Name="ImageBtn"
Text="Take Photo"
SemanticProperties.Hint="Take Image"
Clicked="TakePhoto"
HorizontalOptions="Center" />
Unfortunately this is a old and known bug for WinUI3.
microsoft/WindowsAppSDK#1034
As a workaround you can create a custom mediapicker that inherit everything from the MAUI one for Android, iOS and Catalyst.
Then implement a custom mediapicker for Windows, intheriting all the Capture... methods and reimplementing the CapturePhotoAsync using a custom class for camera capture:
public async Task<FileResult> CaptureAsync(MediaPickerOptions options, bool photo)
{
var captureUi = new CameraCaptureUI(options);
var file = await captureUi.CaptureFileAsync(photo ? CameraCaptureUIMode.Photo : CameraCaptureUIMode.Video);
if (file != null)
return new FileResult(file.Path,file.ContentType);
return null;
}
This is the new CameraCaptureUI class:
public async Task<StorageFile> CaptureFileAsync(CameraCaptureUIMode mode)
{
var extension = mode == CameraCaptureUIMode.Photo ? ".jpg" : ".mp4";
var currentAppData = ApplicationData.Current;
var tempLocation = currentAppData.LocalCacheFolder;
var tempFileName = $"CCapture{extension}";
var tempFile = await tempLocation.CreateFileAsync(tempFileName, CreationCollisionOption.GenerateUniqueName);
var token = Windows.ApplicationModel.DataTransfer.SharedStorageAccessManager.AddFile(tempFile);
var set = new ValueSet();
if (mode == CameraCaptureUIMode.Photo)
{
set.Add("MediaType", "photo");
set.Add("PhotoFileToken", token);
}
else
{
set.Add("MediaType", "video");
set.Add("VideoFileToken", token);
}
var uri = new Uri("microsoft.windows.camera.picker:");
var result = await Windows.System.Launcher.LaunchUriForResultsAsync(uri, _launcherOptions, set);
if (result.Status == LaunchUriStatus.Success && result.Result != null)
{
return tempFile;
}
return null;
}
Also in the Package.appxmanifest add these capabilities:
<DeviceCapability Name="microphone"/>
<DeviceCapability Name="webcam"/>
Credits for the CameraCapture class
Edit:
Modified code to allow video capturing, credits here
So I have this button in my Portable Class Library that should directly attach an xml file I've created into a mail and send it using the Messaging PlugIn. The problem is that .WithAttachment() is not supported in PCL so I wanted to ask if I can get around this using DependencyService and if so, how?
Can I just return .WithAttachment() from the UWP class (as UWP is my target platform)? Wouldn't there be a conflict because I've read that the overload of .WithAttachment() in UWP is .WithAttachment(IStorageFile file).
private void Senden_Clicked(object sender, EventArgs e)
{
var emailMessenger = CrossMessaging.Current.EmailMessenger;
if (emailMessenger.CanSendEmail)
{
var email = new EmailMessageBuilder()
.To("my.address#gmail.com")
.Subject("Test")
.Body("Hello there!")
//.WithAttachment(String FilePath, string ContentType) overload showing in PCL
//.WithAttachment(IStorageFile file) overload for the UWP according to the documentation
.Build();
emailMessenger.SendEmail(email);
}
}
EDIT:
I've been able to modify Nick Zhou's answer a little bit to be able to send an email with attachment via button-click. I just changed this peace of code:
var picker = new Windows.Storage.Pickers.FileOpenPicker
{
ViewMode = Windows.Storage.Pickers.PickerViewMode.List,
SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary
};
picker.FileTypeFilter.Add("*");
to this:
StorageFolder sf = ApplicationData.Current.LocalFolder;
var file = await sf.GetFileAsync("daten.xml");
Of course you then need to create the file inside the app's local folder instead of the documents library.
The problem is that .WithAttachment() is not supported in PCL so I wanted to ask if I can get around this using DependencyService and if so, how?
Of course you can use DependencyService to achieve sending email with attactment. But you could create two interface like code behind.
SendEmail Interface
public interface IMessageEmail
{
void SendEmailMehod(string address, string subject, string body, StorageFile attactment = null);
}
IMessageEmail implementation in UWP project.
public void SendEmailMehod(string address, string subject, string body, StorageFile attactment = null)
{
var emailMessenger = CrossMessaging.Current.EmailMessenger;
if (emailMessenger.CanSendEmail)
{
if (attactment != null)
{
var email = new EmailMessageBuilder()
.To(address)
.Subject(subject)
.Body(body)
.WithAttachment(attactment)
.Build();
emailMessenger.SendEmail(email);
}
else
{
var email = new EmailMessageBuilder()
.To(address)
.Subject(subject)
.Body(body)
.Build();
emailMessenger.SendEmail(email);
}
}
}
As you can see the .WithAttachment(attactment) parameter is IStorageFile. So you need pass a file to the method. Hence you could create another DependencyService.
IFilePicker Interface
public interface IFilePicker
{
Task<StorageFile> getFileAsync();
}
IMessageEmail implementation in UWP project.
public async Task<StorageFile> getFileAsync()
{
var picker = new Windows.Storage.Pickers.FileOpenPicker
{
ViewMode = Windows.Storage.Pickers.PickerViewMode.List,
SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary
};
picker.FileTypeFilter.Add("*");
var file = await picker.PickSingleFileAsync();
if (file != null)
{
return file;
}
return null;
}
You can try the project I have upload to github.
I have uploaded image on my profile page and I want to hold that image until I logout in xamarin forms.
My image will be lost if I select another page so I want to hold it until I log out.
var profile = new Image { };
profile.Source = "profile.png";
profile.HorizontalOptions = LayoutOptions.StartAndExpand;
profile.VerticalOptions = LayoutOptions.StartAndExpand;
var profiletap = new TapGestureRecognizer();
profiletap.Tapped += async (s, e) =>
{
var file = await CrossMedia.Current.PickPhotoAsync();
if (file == null)
return;
await DisplayAlert("File Location", file.Path, "OK");
im = ImageSource.FromStream(() =>
{
var stream = file.GetStream();
//file.Dispose();
return stream;
});
profile.Source = im;
// await Navigation.PushModalAsync(new PhotoPage(im));
};
profile.GestureRecognizers.Add(profiletap);
Pages do not get destroyed when navigating to another page and coming back, which is why a page's constructor only gets executed the first time it is shown. So I am not sure what you mean when you say you want to hold that image.
Having said that, you could always assign the entire profile variable to a static global variable in your App class like below so that it stays the same no matter what. Then you would have to assign/initialize the global variable at the correct time.
But again, I am not sure if that is necessary, so you might try to explain more what the issue actually is:
In the App class:
public class App : Application {
public static Image ProfileImage = new Image {
Source = "profile.png",
HorizontalOptions = LayoutOptions.StartAndExpand,
VerticalOptions = LayoutOptions.StartAndExpand
};
....
}
Then in your page:
public class ProfilePage : ContentPage {
public ProfilePage() {
....
App.ProfileImage.GestureRecognizers.Add(profiletap);
}
}
Edit: See my answer here for an example of using a plugin to allow the user to choose a photo from their device's camera roll. Once you have the photo path, you can simply use HttpClient to send the image and a base64 string. There are plenty of other example online about how to do that.
Edit #2: After this line of your code:
var file = await CrossMedia.Current.PickPhotoAsync();
You now have the file and the path in file variable. So currently all you are doing is showing the image using ImageSource.FromStream but in order to keep showing the image when you return to the page, you need to also save the image to the device. In order to do that, you will need to write platform specific code in each project and reference that in your shared code. Something like this:
In your iOS and Android project, create a new file (FileHelper_Android.cs and FileHelper_iOS.cs for example) and add the following (the same code can be added to both iOS and Android files, just change the name of the class and file:
using ....;
[assembly: Dependency(typeof(FileHelper_Android))]
namespace YourNamespace.Droid{
/// <summary>
/// Responsible for working with files on an Android device.
/// </summary>
internal class FileHelper_Android : IFileHelper {
#region Constructor
public FileHelper_Android() { }
#endregion
public string CopyFile(string sourceFile, string destinationFilename, bool overwrite = true) {
if(!File.Exists(sourceFile)) { return string.Empty; }
string fullFileLocation = Path.Combine(Environment.GetFolderPath (Environment.SpecialFolder.Personal), destinationFilename);
File.Copy(sourceFile, fullFileLocation, overwrite);
return fullFileLocation;
}
}
}
Do the same on iOS and just change the file name. Now in your shared project you need to create IFileHelper.cs like so:
public interface IFileHelper {
string CopyFile(string sourceFile, string destinationFilename, bool overwrite = true);
}
Finally, in your page you would write the following:
_fileHelper = _fileHelper ?? DependencyService.Get<IFileHelper>();
profiletap.Tapped += async (s, e) =>
{
var file = await CrossMedia.Current.PickPhotoAsync();
if (file == null)
return;
await DisplayAlert("File Location", file.Path, "OK");
profile.Source = im;
imageName = "SomeUniqueFileName" + DateTime.Now.ToString("yyyy-MM-dd_hh-mm-ss-tt");
filePath = _fileHelper.CopyFile(file.Path, imageName);
im = ImageSource.FromFile(filePath)
// await Navigation.PushModalAsync(new PhotoPage(im));
};
Above, once the user chooses the file, we copy that file locally and the we also set your im variable to the new local file path, which gets returned from the IFileHelper.CopyFile method.
You still need to handle the case when the user comes back to the page or turns the app off and on again. In that situation, you need to load the saved image path. I would suggest either saving the image path into the DB, unless the user will only ever have a single profile image, then you could always just load that same path and filename. Let me know if you still have issues.
I am trying to create one sample application in Xamarin / Android, Which downloads one image from Internet and displays it in ImageView. But soon after executing var imageContent = await httpClient.GetByteArrayAsync (ImageUrl); The UI / Application hangs. No call back response is coming. I am adding my full source code for you reference. Please help whats wrong in my sample.
[Activity (Label = "ImageDownloadSample", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : Activity
{
private const string ImageUrl = "http://www.olympusimage.com.sg/content/000006422.jpg";
private ImageView imgView;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
SetContentView (Resource.Layout.Main);
var button = FindViewById<Button> (Resource.Id.downloadImage);
imgView = FindViewById<ImageView>(Resource.Id.imageView);
button.Click+=((sender, e) =>
DownloadImageAsync());
}
private async void DownloadImageAsync()
{
var httpClient = new HttpClient ();
imgView.SetImageResource (Android.Resource.Drawable.IcMenuGallery);
var imageContent = await httpClient.GetByteArrayAsync (ImageUrl);
var documentsPath = System.Environment.GetFolderPath (System.Environment.SpecialFolder.Personal);
string localFilename = "mytestImage.jpg";
string localPath = System.IO.Path.Combine (documentsPath, localFilename);
File.WriteAllBytes (localPath, imageContent);
var localImage = new Java.IO.File (localFilename);
if (localImage.Exists ()) {
var bitmapImage = BitmapFactory.DecodeFile (localImage.AbsolutePath);
imgView.SetImageBitmap (bitmapImage);
}
}
}
}
Please Help
They have a sample app that does exactly this: https://github.com/xamarin/monodroid-samples/blob/master/AsyncImageAndroid/AsyncImageAndroid/MainActivity.cs
Also, that image you're trying to download looks pretty huge...I'd definitely get something more reasonable just to rule that out as an error while trying to get things working.
I have same problem. Check the exception! If you have timeout set HttpClient.Timeout larger!
You might need to set up the event handler as such:
button.Click+=(async (sender, e) => DownloadImageAsync());
To indicate that it contains code that can run asynchronously
I want to choose a picture from library or take a picture with the camera and show the result to the view (ImageView)
But according to a few posts including this one, the MvxHttpImageView I use needs a Uri to show the image (wheter it comes from file systemor camera). This implies, converting the Stream into a file and getting the Uri back.
I wrote a Picture Service that does the job:
public class PictureService : IPictureService,
IMvxServiceConsumer<IMvxPictureChooserTask>,
IMvxServiceConsumer<IMvxSimpleFileStoreService>
{
private const int MaxPixelDimension = 1024;
private const int DefaultJpegQuality = 92;
public void TakeNewPhoto(Action<string> onSuccess, Action<string> onError)
{
this.GetService<IMvxPictureChooserTask>().TakePicture(
PictureService.MaxPixelDimension,
PictureService.DefaultJpegQuality,
pictureStream =>
{
var newPictureUri = this.Save(pictureStream);
if (!string.IsNullOrWhiteSpace(newPictureUri))
onSuccess(newPictureUri);
else
onError("No picture selected");
},
() => { /* cancel is ignored */ });
}
public void SelectExistingPicture(Action<string> onSuccess, Action<string> onError)
{
this.GetService<IMvxPictureChooserTask>().ChoosePictureFromLibrary(
PictureService.MaxPixelDimension,
PictureService.DefaultJpegQuality,
pictureStream =>
{
var newPictureUri = this.Save(pictureStream);
if (!string.IsNullOrWhiteSpace(newPictureUri))
onSuccess(newPictureUri);
else
onError("No photo taken");
},
() => { /* cancel is ignored */ });
}
private string Save(Stream stream)
{
string fileName = null;
try
{
fileName = Guid.NewGuid().ToString("N");
var fileService = this.GetService<IMvxSimpleFileStoreService>();
fileService.WriteFile(fileName, stream.CopyTo);
}
catch (Exception)
{
fileName = null;
}
return fileName;
}
}
But for privacy reason, I do not want to save the picture on filesystem. The workflow is:
Take or select picture
Show it on screen (with additional info)
Save my model on server sending image to the cloud: not trace on the
device
My question is: how can I handle the Streams containing picture data without saving on filesystem?
Or
How to use a temporary storage system that is not accessible to user (ignore "rooted" device case)?
Thanks for your help.
You could try creating your own custom ImageView control:
1. Use a MemoryStream to collect the received pictureStream into a byte[] property on the ViewModel, e.g. MyBytes
pictureStream => {
var memoryStream = new MemoryStream();
pictureStream.CopyTo(memoryStream);
TheRawImageBytes = memoryStream.GetBuffer()
}
where TheRawImageBytes is:
private byte[] _theRawImageBytes;
public byte[] TheRawImageBytes
{
get { return _theRawImageBytes; }
set { _theRawImageBytes = value; RaisePropertyChanged(() => TheRawImageBytes); }
}
2. Create your own MyImageView class derived from ImageView, add the (context, attr) constructor, then expose a byte[] property on the MyImageView - when that byte[] is set then use BitmapFactory.DecodeByteArray and SetBitmap to render the picture from the incoming bytes
private byte[] _rawImage;
public byte[] RawImage
{
get { return _rawImage; }
set
{
_rawImage = value;
if (_rawImage == null)
return;
var bitmap = BitmapFactory.DecodeByteArray(_rawImage, 0,_rawImage.Length);
SetImageBitmap(bitmap);
}
}
3. Use <yourapp.namespace.to.MyImageView ... /> in the axml instead of the normal <ImageView ... />
4. In the axml bind the View byte[] property to the source ViewModel byte[] property.
local:MvxBind="{'RawImage':{'Path':'TheRawImageBytes'}}"
5. That's it - although you might want to add some error handling and do some testing
This approach is adapted from the answer to MvvmCross Android Bind Image from byte[]
As mentioned in that question, an alternative approach would be to use a standard ImageView with a custom binding instead.
For more on creating custom views/widgets based on standard views/widgets - including on how to replace <yourapp.namespace.to.MyImageView ... /> with an abbreviation <MyApp.MyImageView ... />, see http://slodge.blogspot.co.uk/2012/10/creating-custom-views-is-easy-to-do.html