Xamarin: Saving files to external storage on Android with an API level >=29 - xamarin

I'm trying to export files to the public external storage of an Android phone in Xamarin, for a backup DB. However, in the last version of Android phones (11.0 - API30), one can't opt-out of scoped storage using the property android:requestLegacyExternalStorage="true" of the <application> tag in the manifest.xml.
I made sure that the permissions READ_EXTERNAL_STORAGE & WRITE_EXTERNAL_STORAGE are granted before trying to create the file. Still, when trying to create a file, a System.UnauthorizedAccessException exception is thrown.
/* file 1: */
// ....
private async void Export_Tapped (object sender, EventArgs e) {
// check if permission for writing in external storage is given
bool canWrite = await FileSystem.ExternalStoragePermission_IsGranted ();
if (!canWrite) {
// request permission
canWrite = await FileSystem.ExternalStoragePermission_Request ();
if (!canWrite) {
// alert user
return;
}
}
// find the directory to export the db to, based on the platform
string fileName = "backup-" + DateTime.Now.ToString ("yyMMddThh:mm:ss") + ".db3";
string directory = FileSystem.GetDirectoryForDatabaseExport (); // returns "/storage/emulated/0/Download"
string fullPath = Path.Combine (directory, fileName);
// copy db to the directory
bool copied = false;
if (directory != null)
copied = DBConnection.CopyDatabase (fullPath);
if (copied)
// alert user
else
// alert user
}
// ....
/* file 2: */
class DBConnection
{
private readonly string dbPath;
// ....
public bool CopyDatabase(string path)
{
byte[] dbFile = File.ReadAllBytes(dbPath);
File.WriteAllBytes(path, dbFile); // --> this is where the exception is thrown <--
return true;
}
// ....
}
So the question stands: how does one write a new file to the public external storage of an Android device with an API level of 29 or more?
All the resources I have found so far, maybe you can gather more information than I did:
https://forums.xamarin.com/discussion/179999/access-denied-to-external-storage
(regarding private external storage) https://forums.xamarin.com/discussion/171039/saving-files-to-external-storage
https://learn.microsoft.com/en-us/xamarin/android/platform/files/external-storage?tabs=windows
https://developer.android.com/about/versions/11/privacy/storage#permissions

Try This , I use a dependency service to call this method in Native Android and save files like docx and pdf from their by byte array.
public async Task<bool> CreateFile(string fileName, byte[] docBytes, string fileType)
{
try
{
Java.IO.File file = new Java.IO.File(Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath, fileName + fileType);
OutputStream os = new FileOutputStream(file);
os.Write(docBytes);
os.Close();
}
catch
{
return false;
}
return true;
}

The path you use is incorrect, please try the following file path .
Context context = Android.App.Application.Context;
var filePath = context.GetExternalFilesDir(Android.OS.Environment.DirectoryDocuments);
Refer to https://forums.xamarin.com/discussion/comment/422501/#Comment_422501 .

Related

Download and open picture from url/http [Android Xamarin App]

Hello, would any of you send a working code to download a photo from a given http address on android Xamarin c #?
First, I need to create a new folder for my application files.
My goal is to download the file from the internet to my Android folder (saving this file with its original name is best).
The next step is to display the image from that folder in "ImageView". It is also important that there are permissions in android and I do not fully understand it.
Could any of you send it to me or help me understand it and explain the topic?
*Actually i have this code:
string address = "https://i.stack.imgur.com/X3V3w.png";
using (WebClient webClient = new WebClient())
{
webClient.DownloadFileCompleted += WebClient_DownloadFileCompleted;
webClient.DownloadFile(address, Path.Combine(pathDire, "MyNewImage1.png"));
//System.Net.WebException: 'An exception occurred during a WebClient request.'
}
Loading image from url and display in imageview.
private void Btn1_Click(object sender, System.EventArgs e)
{
var imageBitmap = GetImageBitmapFromUrl("http://xamarin.com/resources/design/home/devices.png");
imagen.SetImageBitmap(imageBitmap);
}
private Bitmap GetImageBitmapFromUrl(string url)
{
Bitmap imageBitmap = null;
using (var webClient = new WebClient())
{
var imageBytes = webClient.DownloadData(url);
if (imageBytes != null && imageBytes.Length > 0)
{
SavePicture("ImageName.jpg", imageBytes, "imagesFolder");
imageBitmap = BitmapFactory.DecodeByteArray(imageBytes, 0, imageBytes.Length);
}
}
return imageBitmap;
}
download image and save it in local storage.
private void SavePicture(string name, byte[] data, string location = "temp")
{
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
documentsPath = System.IO.Path.Combine(documentsPath, "Orders", location);
Directory.CreateDirectory(documentsPath);
string filePath = System.IO.Path.Combine(documentsPath, name);
using (FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate))
{
int length = data.Length;
fs.Write(data, 0, length);
}
}
you need to add permission WRITE_EXTERNAL_STORAGE and READ_EXTERNAL_STORAGE in AndroidMainfeast.xml, then you also need to Runtime Permission Checks in Android 6.0.
private void checkpermission()
{
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) == (int)Permission.Granted)
{
// We have permission, go ahead and use the writeexternalstorage.
}
else
{
// writeexternalstorage permission is not granted. If necessary display rationale & request.
}
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadExternalStorage) == (int)Permission.Granted)
{
// We have permission, go ahead and use the ReadExternalStorage.
}
else
{
// ReadExternalStorage permission is not granted. If necessary display rationale & request.
}
}

Can upload image using ASP.NET WEB API but not when deployed

I am using ASP.NET WEB API to upload image to server. But when i upload the source code of my web api to gearhost.com and make a post request. I am unable to post the image. This is my web api controller code:
[Route("upload")]
[HttpPost]
public async Task<string> Upload()
{
var ctx = HttpContext.Current;
var root = ctx.Server.MapPath("/uploads/");
var provider = new MultipartFormDataStreamProvider(root);
try
{
await Request.Content
.ReadAsMultipartAsync(provider);
foreach (var file in provider.FileData)
{
var name = file.Headers
.ContentDisposition
.FileName;
// remove double quotes from string.
name = name.Trim('"');
var localFileName = file.LocalFileName;
var filePath = Path.Combine(root, "files", name);
// File.Move(localFileName, filePath);
// SaveFilePathSQLServerADO(localFileName, filePath);
// SaveFileBinarySQLServerADO(localFileName, name);
// SaveFilePathSQLServerEF(localFileName, filePath);
SaveFileBinarySQLServerEF(localFileName, name, filePath);
if (File.Exists(localFileName))
File.Delete(localFileName);
}
}
catch
{
return "Error";
}
return "File uploaded successfully!";
}
public void SaveFileBinarySQLServerEF(string localFile, string fileName, string filePath)
{
// 1) Get file binary
byte[] fileBytes;
using (var fs = new FileStream(localFile, FileMode.Open, FileAccess.Read))
{
fileBytes = new byte[fs.Length];
fs.Read(fileBytes, 0, Convert.ToInt32(fs.Length));
}
// 2) Create a Files object
var file = new tblimage()
{
Data = fileBytes,
Names = fileName,
ContentType = filePath
};
// 3) Add and save it in database
using (var ctx = new coachEntities())
{
ctx.tblimages.Add(file);
ctx.SaveChanges();
}
}
Here is the successful call from localhost:
Image posted through localhost
However when deployed the same code and make request through postman then I get this error:
Image posted through live server
Maybe, "uploads" doesn't have write permission
Check the permission in your uploads folder.
Go to properties-- security
Give the read write permission.
Though it is not good idea to return the exception details in live code. As you are not maintaining log. For testing, Please return the exception details. Also, how are you getting the response like "unable to upload, try again" because it is not there in your code

Sending an email with attachment in Xamarin.Forms for Android and iOS

I am creating an app to send an email with an attachment. I have tried using mailTo to do it but it is not working. Are there any other ways to send an email with attachment?
This is the code that I have tried so far
private void Button_Clicked_1(object sender, EventArgs e)
{
string toEmail = "toemail#nyp.edu.sg";
string emailSubject = "Test Email";
string emailBody = "Email Body";
string attachment = "C:/Users/L30901/Desktop/download.jpg";
Device.OpenUri(new Uri(String.Format("mailto:{0}?subject={1}&body={2}&attachment=file:///{3}", toEmail, emailSubject, emailBody, attachment)));
}
This is what I did for iOS
MainPage.xaml.cs
private void Button_Clicked_1(object sender, EventArgs e)
{
DependencyService.Get<IDependency>().SendEmail();
}
IDependency.cs
public interface IDependency
{
void SendEmail();
}
IOSEmail.cs
[assembly: Dependency(typeof(IOSEmail))]
namespace Notification.iOS
{
public class IOSEmail : IDependency
{
MFMailComposeViewController mailController;
public void SendEmail()
{
if (MFMailComposeViewController.CanSendMail)
{
mailController = new MFMailComposeViewController();
mailController.SetToRecipients(new string[] { "some.person#somewhere.com" });
mailController.SetSubject("Testing");
mailController.SetMessageBody("See attached file", false);
NSData data = NSData.FromFile(pdffilename);
mailController.AddAttachmentData(data, "application/jpeg", "xxxxx.jpg");// For JPEG
mailController.Finished += HandleMailFinished;
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(mailController, true, null);
}
}
private void HandleMailFinished(object sender, MFComposeResultEventArgs e)
{
e.Controller.DismissViewController(true, null);
}
What you need to do can only be done using Xamarin.Forms DependencyService:
For Android you need to do something like this:
Get the Directory of your attachment using Java.IO:
Java.IO.File dir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads);
Note: I have used the downloads directory you can change it as per your need at any point in time.
Once you are done with picking that directory using Android OS environment then pick the file something like this;
Java.IO.File file = new Java.IO.File(dir, "yourFile.pdf"); //This could be any other type of file aswell
Get the URI of that Android file something like this:
Android.Net.Uri path = Android.Net.Uri.FromFile(file);
Create the Intent to send the data from to your Emailing app something like this:
var email = new Intent(Android.Content.Intent.ActionSend);
email.PutExtra(Android.Content.Intent.ExtraEmail,
new string[] { "some.person#somewhere.com" }); // to whom you want to send the email
email.PutExtra(Android.Content.Intent.ExtraCc,
new string[] { "some.person#somewhereelse.com" }); // to whom you want to Copy in the email (CC)
email.PutExtra(Android.Content.Intent.ExtraSubject, "Awesome File");
email.PutExtra(Android.Content.Intent.ExtraText,
"See attached file");
email.PutExtra(Intent.ExtraStream, path);
email.SetType("message/rfc822");
StartActivity(email);
For IOS you need to do something like this:
Create an instance of MFMailComposeViewController and add a subject and message body to it;
var mailer= new MFMailComposeViewController();
mailer.SetSubject("xxxx");
mailer.SetMessageBody("", true);
mailer.Finished += HandleMailFinished; //This controllers finished event in case you need it
Now it is mandatory that you have your file in the document directory for it to be available to the application for more information check Working with the File System
Assuming that you read that and you have added the file to the document directory of your application get the NSData for your attachment:
NSData data = NSData.FromFile(pdfFileName);
mailer.AddAttachmentData(data, "application/pdf", "xxxxx.pdf");// For PDF
mail.AddAttachmentData(csvdata, "application/csv", "csv.txt"); // For CSV/TXT
Get the currently displayed Viewcontroller something like this:
public UIViewController GetCurrentUIController()
{
UIViewController viewController;
var window = UIApplication.SharedApplication.KeyWindow;
if (window == null)
{
return null;
}
if (window.RootViewController.PresentedViewController == null)
{
window = UIApplication.SharedApplication.Windows
.First(i => i.RootViewController != null &&
i.RootViewController.GetType().FullName
.Contains(typeof(Xamarin.Forms.Platform.iOS.Platform).FullName));
}
viewController = window.RootViewController;
while (viewController.PresentedViewController != null)
{
viewController = viewController.PresentedViewController;
}
return viewController;
}
Then Present the Viewcontroller something like this:
var _CurrentViewComtroller= GetCurrentUIController();
_CurrentViewComtroller?.PresentViewController(mailer, true, null);
The Handled Finish method would be something like this:
private void HandleMailFinished(object sender, MFComposeResultEventArgs args)
{
DismissViewController (true, null);
}

store voice file path android xamarin

the following code is what I use to grab a voice file to send an email as an attachment. However, I am not able to find this file. no clue where it is stored. (this is what I see when I hover the path /storage/emulated/0/test.mp4). the storage folder is empty even when this run, for this reason, the attachment can't be sent. any ideas? Thank you! updated code
string path = "";
public Recorder_Droid()
{
var sqlliteFilname = "test.mp4";
string filePath = global::Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;
path = Path.Combine(filePath, sqlliteFilname);
_recorder = new MediaRecorder();
_player = new MediaPlayer();
_player.Completion += (sender, e) => {
_player.Reset();
};
}
MediaRecorder _recorder;
MediaPlayer _player;
public void PlayAudio()
{
if (File.Exists(path))
{
File.Delete(path);
}
if (_recorder == null)
{
_recorder = new MediaRecorder();
}
_recorder.Reset();
_recorder.SetAudioSource(AudioSource.Mic);
_recorder.SetOutputFormat(OutputFormat.Mpeg4);
_recorder.SetAudioEncoder(AudioEncoder.Aac);
_recorder.SetOutputFile(path);
_recorder.Prepare(); // Prepared state
_recorder.Start(); // Recording state.
return;

Unable to attach file in WithAttachment in Xam.Plugins.Messaging

please review my code as I am not able to attach any file in EmailMessageBuilder.
Also I need to understand about the ContentType, what should I pass in ContentType?
FileData filedata = await CrossFilePicker.Current.PickFile();
String Path = CrossGetLocalFilePath.Current.GetLocalPath(filedata.FileName);
var emailMessenger = CrossMessaging.Current.EmailMessenger;
if (emailMessenger.CanSendEmail)
{
var email = new EmailMessageBuilder()
.To("to.plugins#xamarin.com")
.Subject("Xamarin Messaging Plugin")
.Body("Well hello there from Xam.Messaging.Plugin")
.WithAttachment(Path, "image/jpeg")
.Build();
emailMessenger.SendEmail(email);
}
I am using above code in Xamarin.forms (Portable), my attachment could be an image, video or any file.
Getting error:
Failed to attach file due to IO error.
I never used the CrossFilePicker plugin and CrossGetLocalFilePath plugin before but I find the source code here:
CrossFilePicker : https://github.com/Studyxnet/FilePicker-Plugin-for-Xamarin-and-Windows/tree/master/FilePicker/FilePicker
CrossGetLocalFilePath:https://github.com/bradyjoslin/GetLocalFilePathPlugin/blob/master/GetLocalFilePath/GetLocalFilePath.Plugin.Android/GetLocalFilePathImplementation.cs
This is the FileData object you got when you call CrossFilePicker.Current.PickFile();
namespace Plugin.FilePicker.Abstractions
{
public class FileData
{
public byte[] DataArray { get; set; }
public string FileName { get; set; }
}
}
DataArray is your file data, and FileName is your file name. It does not contain the file path.
And you call the another plugin CrossGetLocalFilePath to get the file path according to the file name.
in the CrossGetLocalFilePath source code it just implements in Android platform:
public class GetLocalFilePathImplementation : IGetLocalFilePath
{
public string GetLocalPath(string fileName)
{
string path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
return System.IO.Path.Combine(path, fileName);
}
}
it not return the file path you want. It returned system special file path.
So in your case it is not possible to get the file path by these plugins.
But I recommend you to overwrite the CrossFilePicker plugin.
Take UWP as an example:
public class FilePickerImplementation : IFilePicker
{
public async Task<FileData> PickFile()
{
var picker = new Windows.Storage.Pickers.FileOpenPicker();
picker.ViewMode = Windows.Storage.Pickers.PickerViewMode.List;
picker.SuggestedStartLocation =
Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary;
picker.FileTypeFilter.Add("*");
Windows.Storage.StorageFile file = await picker.PickSingleFileAsync();
if (file != null)
{
var array = await ReadFile(file);
return new FileData
{
DataArray = array,
FileName = file.Name
FilePath = file.Path;
};
}
else
{
return null;
}
}
This is the implementation of file picker in UWP. You can add the FilePath property in the FileData Object as the code shows before.
We can get the path if we are using the below plugin for the Media Capture and Select image from the Gallery.
Xam.Plugin.Media
Thank You.

Resources