In the first part of this series – I looked at how to register a Xamarin.Forms app on iOS to receive and display a PDF. In this post I’ll do the same thing – but this time on Android.
The Scenario
Again the overall scenario we’re talking about here is:
- We wrote a Xamarin.Forms application.
- This application is capable of special handling of a specific file type (in the demo app I’m using in this blog post, that’s displaying a PDF file).
- We want the operating system to know that our app is capable of performing operations on that file type.
- Thus our app should appear as an option in a list of target apps to send the file to, if the user should so decide.
The demo code can be found on GitHub here.
So let’s get to it.
Android Registration
Much like you use Intents to send data between Activities in Android – you can use Intents to do the same between apps.
Explicit vs Implicit Intents
When you know the exact Activity you want to open within you app – you create what’s called an Explicit Intent. When instantiating an Explicit Intent – you specify the class type of the Activity that should be started, and then the operating system will start that when the StartActivity()
is encountered.
Easy enough – but what happens when the developer of the “source app” doesn’t know which Activity should be started, because that Activity is going to reside in a separate application? All the app wants to do is “send” out a notification that it has something it wants to deliver to another app, so that app can take action on it?
This is where an Implicit Intent comes into the picture. The developer creates an Implicit Intent by not specifying an Activity to start.
However, the Implicit Intent still needs to contain enough other information so the Android operating system can determine which app should receive the information. We’re not going to cover how to create Implicit Intents, as we’re only concerned about consuming them, but this article covers them nicely.
Intent Filters
All of the information about Explicit vs Implicit Intents is a lead up to introduce a concept known as Intent Filters. Intent Filters are a means by which our app – the app that will be on the receiving end of the file from another – registers with Android to say it knows how to deal with whatever data is coming its way.Intent Filters are associated with the Activity in our app that should get started when the file is received. And the Intent Filter definition appears in the AndroidManifest.xml file along with the definition for the Activity.To determine which apps have the potential to be launched and displayed as options whenever the user wants to send a file of a specific type to another app, Android looks at all of the Intent Filters registered with the operating system and “filters” apps to display based on whether the criteria they list in their Intent Filters match the criteria declared in the Implicit Intent.
So how do you build an Intent Filter?
Intent Filter Declaration
As I mentioned, Intent Filters are defined along with the Activities that should get launched when the file is received.
An example of an Activity and Intent Filter declaration from the AndroidManifest.xml file will look like the following:
<activity android:configChanges="orientation|screenSize" android:icon="@drawable/icon" android:label="OpenFiles.Droid" android:theme="@style/MyTheme" android:name="md5d37a78bf190038b83be9873b4223f8d1.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/pdf" />
</intent-filter>
</activity>
So there’s a couple of things to notice. The first is that a single Activity can have more than one Intent Filter. The second are the keywords involved with the
So – in this particular case of RECEIVING files from other apps, here are what the values mean (note, they can and do have meanings in other situations).
<action android:name=”android.intent.action.SEND” />
– this means that this Activity is only going to respond to Implicit Intents that are meant to send things to other apps.<category android:name=”android.intent.category.DEFAULT” />
– this always needs to appear and it always needs to be this value to receive Implicit Intents.<data android:mimeType=”application/pdf” />
– this is the type of data … or MIME type that we’re saying our application can handle.
Now that we have all of the Android “theory” out of the way – let’s look at hooking this up and making it work in a Xamarin.Forms app!
Integrating with Xamarin.Forms
The first thing that we need to do is figure out how to add the Intent Filter to the Android portion of the Xamarin.Forms app.
Xamarin provides a lot of tools so that we don’t have to touch the AndroidManifest.xml file very often. If you ever find yourself needing to add a value into it – look at either the visual designer for it (by double clicking the file) or by looking into various attribute classes.
It just so happens that Intent Filters can be added by the IntentFilterAttribute
class.
Since there is only a single Activity in a Forms app – we’ll need to decorate the MainActivity
class with that attribute.
To generate the XML in the AndroidManifest.xml that’s identical to what we looked at above, the MainActivity
class declaration will look like the following:
[Activity(Label = "OpenFiles.Droid", Icon = "@drawable/icon", Theme = "@style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
[IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = @"application/pdf")]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity {
Notice there still is the ActivityAttribute
class that will create the activity node in the manifest.
Then the IntentFilterAttribute
class takes care of adding the Intent Filter to that Activity’s node. (Full documentation on the IntentFilterAttribute
can be found here.) It specifies the action, category, and incoming data type. The action and category are arrays – so more than one can be specified. And there are more parameters too that will help “filter” the matching criteria down.
By decorating the class that way – Android will now recognize our app as being able to work with PDF files.
Handling the Incoming File
Now our app is being recognized by Android as a valid target – we need to handle the situation when something gets sent to it.
The OnCreate
function in the MainActivity
is going to get invoked when the file comes in.
The Activity’s Intent
property will hold all the information we need to get at the underlying file.
When receiving a file – you can generally expect the needed information to be in the ClipData
object of the Intent
.
That info will be an Android.Net.Uri
which then can be used with a ContentResolver
to open a stream of data – and then you can save that stream into your app as a local file.
Depending on the application sending the data, you also could look for the URI of the file being sent in the Intent’s Extras – Intent.ExtraStream
.
Regardless of where you get the Uri
from, you’ll want to be sure to save the file into your app. That way you know you’ll have full access to it.
The full OnCreate
is shown below.
App _mainForms;
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
var mainForms = new App();
LoadApplication(mainForms);
if (Intent.Action == Intent.ActionSend)
{
// This is just an example of the data stored in the extras
var uriFromExtras = Intent.GetParcelableExtra(Intent.ExtraStream) as Android.Net.Uri;
var subject = Intent.GetStringExtra(Intent.ExtraSubject);
// Get the info from ClipData
var pdf = Intent.ClipData.GetItemAt(0);
// Open a stream from the URI
var pdfStream = ContentResolver.OpenInputStream(pdf.Uri);
// Save it over
var memOfPdf = new System.IO.MemoryStream();
pdfStream.CopyTo(memOfPdf);
var docsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
var filePath = System.IO.Path.Combine(docsPath, "temp.pdf");
System.IO.File.WriteAllBytes(filePath, memOfPdf.ToArray());
mainForms.DisplayThePDF(filePath);
}
}
This is the normal Xamarin.Forms OnCreate
. The big difference is that after calling the Xamarin.Forms’ LoadApplication
, it then checks to see if the Intent
was loaded with information from another app invoking an Implicit Intent.
If it finds data in the Intent
property that has an action of Intent.ActionSend
– meaning a file is being sent to the app – it goes through the process of copying the file into the app’s local storage – then calls the DisplayThePDF()
function.
If you remember from the last post, DisplayThePDF()
displays a modal page with a web view on it – and that web view is capable of displaying a PDF based on the file name passed in. That WebView works off of a custom renderer. The code from the custom renderer was obtained via this set of documentation at Xamarin’s site.
When it’s all said and done, the Android app will be displaying PDF files like the image below:
PDF being displayed in Xamarin.Forms – Android app!
Summary
All in all – once you understand the theory behind how Android sends data between applications – which isn’t too far from how it sends data between Activities within the same app – the platform-specific side of receiving files in Android is pretty straight-forward.
You do need to declare an Intent Filter on the MainActivity
class in the Android project of the Xamarin.Forms solution by using the IntentFilterAttribute
.
Once that’s done – the correct markup will be placed into the AndroidManifest.xml file for you – and all you need to do is handle the incoming data in the OnCreate()
function. Which, ideally, will just be a hand-off to a function already inside your Xamarin.Forms project.
Comments