Thursday 6 August 2015

Xamarin Forms - Mail service for Android - Send email with multiple attachments

Done number of research, testing, refactoring, etc. TBH, It was quite hard to get it work correctly.
Here are the list of websites that I've been through before I got it work, but none of them not really solve my problem... :(
https://gist.github.com/prashantvc/5213961
http://stackoverflow.com/questions/6972210/android-email-sqlite-database
http://stackoverflow.com/questions/23847561/send-email-with-html-content

How to send email from Android with Attachment? or with multiple attachments? 

A long my development and testing, I have figured out:

1. Intent ActionSend
If you use Intent.ActionSend, then you will basically allow to send an email with 1 attachment only.
var intent = new Intent(Intent.ActionSend);

This work well with:
intent.PutExtra(Intent.ExtraStream,  Android.Net.Uri.FromFile (fileIn)  );


2.Intent.ActionSendMultiple
However, if you prefer to send multiple attachments, then you need this:
var intent = new Intent(Intent.ActionSendMultiple); 

This will not work with
intent.PutExtra(Intent.ExtraStream,  Android.Net.Uri.FromFile (fileIn)  );

You need:
intent.PutParcelableArrayListExtra(Intent.ExtraStream, new List<IParcelable>(uris)); 

3. And,...
If you hit the problem where .... you file path is correct, but unable to attach file to your email client (such as Ms Outlook or gmail),. then you need this:
File fileIn = new File(file);
fileIn.SetReadable(true, false); 

The mail activity might not enough rights to read your file. Try to add myFile.setReadable(true, false) before adding to Attachments array.


Here is my example, it has been written as dependencies service for Xamarin.Forms:

[assembly: DependencyAttribute(typeof(MailServiceDroid))]
namespace Jeff.Droid
{
    public class MailServiceDroid : IMailService
    {
        public bool CanSend
        {
            get { return true; }
        }

        public void ShowDraft(string subject, string body, bool html, string to, MessageOrigin origin, IEnumerable<string> attachments = null)
        {
            ShowDraft (subject, body, html, new[] { to }, new string[] { }, new string[] { }, origin, attachments);
        }

        public void ShowDraft(string subject, string body, bool html, string[] to, string[] cc, string[] bcc, MessageOrigin origin, IEnumerable<string> attachments = null)
        {
            //var intent = new Intent(Intent.ActionSend); // This is for single attachment.
            var intent = new Intent(Intent.ActionSendMultiple);

            //intent.SetType ("message/rfc822"); //Set the type according to the attachment type. 
            intent.SetType("application/octet-stream"); // db3
            intent.PutExtra(Intent.ExtraEmail, to);
            intent.PutExtra(Intent.ExtraCc, cc);
            intent.PutExtra(Intent.ExtraBcc, bcc);
            intent.PutExtra(Intent.ExtraSubject, subject ?? string.Empty);

            if (html)
            {
                intent.PutExtra(Intent.ExtraText, Android.Text.Html.FromHtml(body));
            }
            else
            {
                IList<string> x = new List<string>();
                intent.PutStringArrayListExtra(Intent.ExtraText, x);
            }

            if (attachments != null) {
                IntentExtensions.AddAttachments (intent, attachments);
            }

            //Forms.Context.StartActivity (intent); //
            Forms.Context.StartActivity(Intent.CreateChooser(intent, "Send mail..."));
        }
    }


    public static class IntentExtensions
    {
        public static void AddAttachments(this Intent intent, IEnumerable<string> attachments)
        {
            var uris = new List<IParcelable>();
            foreach (var file in attachments)
            {
                File fileIn = new File(file);
                fileIn.SetReadable(true, false); 
                if (fileIn.CanWrite ()) {
                    Android.Net.Uri u = Android.Net.Uri.FromFile (fileIn);
                    uris.Add (u);


                    //This work for: //var intent = new Intent(Intent.ActionSend);
                    //intent.PutExtra(Intent.ExtraStream, u);
                }
            }
            intent.PutParcelableArrayListExtra(Intent.ExtraStream, new List<IParcelable>(uris));
        }

    }
}



Give me a 'like' if you this help! /Jeff

  

Monday 13 April 2015

Send email from iOS and Android via Xamarin Forms

Email service with default email composer
Send email from Xamarin Forms requires different implementation for iOS and Android. The default behaviour of using the default email service from each platform will populate a composer, which is visiable to the end user with the option to see, send, cancel, etc.

Concepts:  iOS

Compoment: MFMailComposeViewController
Present MFMailComposeViewController
In order to use the above iOS component as a dependency service, it is required to have a statement to present the view controller explicitly in your code. In this case, we use:
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(mailer, truenull)


Dismiss MFMailComposeViewController
When finished sending the email, it is required to dismiss the view controller manually. The DismissViewController has to be inside the MainThread of iOS.
mailer.Finished += (s,e)=>{
    UIApplication.ShareApplication.InvokeMainThread(()=>{
        ((MFMailComposeViewController)s).DismissViewController(true,()=>{});
    });
}


Set Mime Type for an attachment
It is important to provide the right "Mime Type" to the attachment you plan to add to your email.

Concepts: Android

Intent:  Intent.ActionSend  (Not "Intent.ActionSendTo" ,  using this will cause some error when sending email to exchange or google)
Intent type: "message/rfc822"  (follow the receipe from Xamarin website: http://developer.xamarin.com/recipes/android/networking/email/send_an_email/)

Present Composer
Setting the Intent’s mime type to message/rfc822 causes the mail application to launch. If multiple applications are capable of handling mail, the user will get a list to choose from.
Note: user can choose, sending out a message via gmail, exchange, skype, hangout. whatsapp, etc.

File object:
With the given file path of the attachment, you still need the Java object to form the file object. But, use Android.Net.Uri.FromFile to form the uri to the file.
var file = new Java.IO.File(attachmentPath);
Use intent.PutExtra(Intent.ExtraStream, Android.Net.Uri.FromFile(file)


Start Android Activity:

It is important to start the Android activity via the right context.
In Xamarin Forms, we use: 
Forms.Context.StartActivity(intent)
//Not: this.Context.StartActivity(intent)



It is important to set "setReadable()" after obtaining a file object in Android.

Without this line, you would't get the attachment even though you can see the attachment appear in the email composer.
file.SetReadable(true, false)

Usage:
//Example in Xamarin.Forms 
var mailService = DependencyService.Get<IEmailService>(); 
if (mailService.CanSend){
    mailService.ShowDraft("Subject","Body",false"receipient email address""path to the attachment");
}else{
    //Please activate or setup your email account from your device settings.
}

Alternative approach:

MAILTO 
string strMailTo = @"mailto:jeff.wei-lim@artesiansolutions.com?Subject=test&Body=testBody";
Device.OpenUri(new Uri(strMailTo));

Note for using MailTo:
  1. user can choose, sending out a message via email client only (such as email app or gmail...)
  2. Unable to send an email with attachment.

How to run unit test for your Xamarin Application in AppCenter?

How to run unit test for your Xamarin application in AppCenter?  When we talk about Building and Distributing your Xamarin app, you m...