Save image locally and then save file path as a string into a database in Xamarin Forms - xamarin

How do i after i take a picture/ select an image (either will overwrite the same image variable regardless), save the image locally and then save the file path as a string into a variable to be inserted into an existing SQLite db.

I had the same exact issue-
Here's how I got it to work
First, in your model where you've created the columns for your tables, make sure there is a property for the string that will be the imagepath
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Image { get; set; }
Then we need a method to upload and save your Image locally and another to get the file path and set it to a string. Assuming this will be in your code-behind, first add these:
public static string photofilename;
public static string imagePath;
static SQLiteConnection db;
YourModel yourmodel = new YourModel();
Then the LoadPhoto method
async void LoadPhotoAsync(FileResult photo)
{
// canceled
string PhotoPath;
if (photo == null)
{
PhotoPath = null;
return;
}
// save the file into local storage
var newFile = Path.Combine(FileSystem.AppDataDirectory, photo.FileName);
using (var stream = await photo.OpenReadAsync())
using (var newStream = File.OpenWrite(newFile))
await stream.CopyToAsync(newStream);
PhotoPath = newFile;
}
Then a method to upload/save the photo (In this case I'm having the user upload from the device) which I have attached to an Upload Image button. In this method, I display the image to the Image in my XAML called ImageViewer, but this might not be necessary for you.
async void Image_ClickedAsync(System.Object sender, System.EventArgs e)
{
try
{
var photo = await MediaPicker.PickPhotoAsync();
LoadPhotoAsync(photo);
photofilename = photo.FileName;
imagePath = Path.Combine(FileSystem.AppDataDirectory, photofilename);
}
catch (FeatureNotSupportedException fnsEx)
{
// Feature is not supported on the device
}
catch (PermissionException pEx)
{
// Permissions not granted
}
catch (Exception ex)
{
Console.WriteLine($"CapturePhotoAsync THREW: {ex.Message}");
}
ImageViewer.Source = imagePath;
ImageViewer.IsVisible = true;
}
What this has done is open up MediaPicker, Allow the user to choose an Image and set the Image Path to the string imagePath. In my case here, I also have "ImageViewer" which is an Image in my XAML used to display the image, but we're not done yet- we haven't yet saved it to your SQLite db. Here's the method I used attached to a "Save" button-
private void SaveEvent(object sender, EventArgs e)
{
var databasePath = Path.Combine(FileSystem.AppDataDirectory, "yourdb.db");
db = new SQLiteConnection(databasePath);
yourmodel.Image = imagePath;
db.Insert(yourmodel);
}
Then, assuming your using ListView or something bound to the Tables in the db, you'll have an image in your XAML like so
<Image HeightRequest="340" WidthRequest="550" x:Name="Image" Source="{Binding Image}"/>
and that should do the trick- Let me know if this works for you!

Related

Xamarin Form - Can't get return value data from Service class method into MainPage method for filtering

I'm new to Xamarin, I have an app in Xamarin-Form that it's fetching data from web api and getting user input from Entry control.
The web api service class is working fine and reaches the deserialization in the getCourses method as seen below in Code Snippet 1.
The Entry control as well is working fine until it retrieves the user input on the MainPage class, OnOkGetCourseButton method as seen below Code Snippet 2.
What I want to achieve is, inside MainPage.xaml.cs, I create a method that takes the user input data and check agaisnt the deseriaized json data (the Id specially),
if it finds the Id in deserialized List of data, then it can send the found data to another ViewPage and display them.
if It cannot find the data, it shows a dialog box.
So far, I tried to call Task<ObservableCollection> getCourses() method from the MainPage class, inside CheckCourseComplete as seen below but it giving me no value/nothing, some kind of null value.
I don't want to filter the user input against web api json response inside getCourses(),
I want to do that in a separate method to follow S-OLID (Single Responsibility Principle).
If it's not possible in a separate method, then I just need to get it worked.
Please what is the best way to achieve it?
Code Snippet 1
public class CourseService : ICourseService
{
string Base_Url = "https://www.test.com/api/TheCourse";
public async Task<ObservableCollection<Course>> getCourses()
{
try
{
string url = Base_Url;
HttpClient client = new HttpClient();
HttpResponseMessage responseMessage = await client.GetAsync(url);
if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK)
{
var result = await responseMessage.Content.ReadAsStringAsync();
var deserializedClass = JsonConvert.DeserializeObject<ObservableCollection<Course>>(result);
// I don't want to do that here, as it will violate SRP (SOLID)
return deserializedClass;
}
return null;
}
catch (Exception)
{
throw;
}
}
}
Code Snippet 2
namespace CourseMobile
{
public partial class MainPage : ContentPage
{
private string _getEntryText;
private readonly CourseViewModel orderViewModel;
public Course FetchCourse { get; set; }
public MainPage()
{
InitializeComponent();
CheckCourseComplete();
BindingContext = new CourseViewModel();
}
public string GetEntryText
{
get => _getEntryText;
set => _getEntryText = value;
}
public async void OnOkGetCourseButton(object sender, EventArgs e)
{
var inputtedCourseNumber = this.GetEntryText;
if(inputtedCourseNumber == string.Empty)
{
await DisplayAlert("", "Please enter your Course number", "OK 3");
}
else
{
CheckCourseComplete();
this.GetEntryText = inputtedCourseNumber;
await DisplayAlert("New Text", inputtedCourseNumber, "OK 2");
}
}
void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
var newText = e.NewTextValue;
this.GetEntryText = newText;
}
public async void CheckCourseComplete()
{
CourseService myCourse = new CourseService();
await myCourse.getCourses(); // It doesn't return the json data (web api data)
// I need to check user input + (web api data) here
}
}
}
getCourses is async, so you need to use await when calling it
public async void CheckCourseComplete()
{
CourseService myCourse = new CourseService();
var data = await myCourse.getCourses();
// now filter data
}

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

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 .

How to share contact data of my app with whtsapp in .vcf /vcard format using xamarin forms

I have an application that creates a .vcf file after you input data. it stores storage provided by the phone, I want to click on a list view item and get the vcf file and share it via Email, SMS, WhatsApp, Skype etc how do I implement this in IOS and Android.
Thank you
I have got the answer of creating .vcf file which is given below. and for share that file follow this link : https://github.com/adamped/ShareDialog
private void Share_Clicked(object sender, EventArgs e)
{
try
{
var _btn = sender as Button;
var record = _btn.BindingContext as Contact;
int tempcontactID = record.ContactID;
if (record.CardFrontImage == null)
{
record.CardImage = record.CardBackImage;
}
else
{
record.CardImage = record.CardFrontImage;
}
string baseimage = Convert.ToBase64String(record.CardImage);
var vcf = new StringBuilder(); //vcf code start
vcf.AppendLine("BEGIN:VCARD");
vcf.AppendLine("VERSION:3.0");
vcf.AppendLine($"N:{record.ContactName};{string.Empty}; ;;");
vcf.AppendLine($"FN:{record.ContactName}");
vcf.AppendLine($"ORG:{record.CompanyName}");
vcf.AppendLine($"TITLE:{record.Designation}");
vcf.AppendLine($"PHOTO;ENCODING=BASE64;TYPE=PNG:{baseimage}");
vcf.AppendLine($"TEL;TYPE=work,voice;VALUE=uri:tel:{record.PhoneNumber}");
vcf.AppendLine("END:VCARD");
string fileName = Path.Combine("/storage/emulated/0/Android/data/com.Gamma.GammaNetworkingApp/files/", record.ContactID + record.ContactName + ".vcf");
using (var writer = new StreamWriter(fileName))
{
writer.Write(vcf.ToString());
}
string text = File.ReadAllText(fileName);
bool doesExist = File.Exists(fileName);
if (doesExist == true)
{
var share = DependencyService.Get<IShare>();
share.Show("Contact share", record.ContactName, fileName);
}
}
catch (Exception ex)
{
string test = ex.ToString();
Navigation.PushAsync(new HomePage());
}
}

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);
}

Best approach to download image

I have a simple site (WebAPI) that returns a bunch of albums in the get method. Each album has attributes such as title, artist and so on. The attribute under here is the image (album photo) attribute. Each album has an image, what is the best way to send the image back to client. Should it be sent as binary data as part of the album object for example like
Public Class Album
{
string title;
byte[] image;
}
Or should I send the path to the image in the Album object and have the client download the image separately?
Like
Public Class Album
{
string title;
string imagePath;
}
You can see this post The downloaded file as stream in controller (ASP.NET MVC 3) will automatically disposed? as reference.
You can use FileStream.
protected override void WriteFile(HttpResponseBase response) {
// grab chunks of data and write to the output stream
Stream outputStream = response.OutputStream;
using (FileStream) {
byte[] buffer = new byte[_bufferSize];
while (true) {
int bytesRead = FileStream.Read(buffer, 0, _bufferSize);
if (bytesRead == 0) {
// no more data
break;
}
outputStream.Write(buffer, 0, bytesRead);
}
}
}
Instead of passing
Public class Album
{
string title;
byte[] image;
}
back to the client, I would change image to a int imageId:
Public class Album
{
string Title{get;set};
int ImageId{get;set};
}
Then create a WebApi controller that handles images with a method written like this:
public async Task<HttpResponseMessage> Get(HttpRequestMessage request, int imageId)
{
byte[] img = await _myRepo.GetImgAsync(imageId);
HttpResponseMessage msg = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(img)
};
msg.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
return msg;
}

Resources