×
Namespaces

Variants
Actions
Revision as of 07:53, 25 February 2014 by Rob.Kachmar (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Transfer an Image with NFC

From Nokia Developer Wiki
Jump to: navigation, search
Featured Article
23 Feb
2014

This article will explain how to extend the NFC Talk project to allow transferring images.

WP Metro Icon NFC.png
SignpostIcon XAML 40.png
WP Metro Icon WP8.png
Article Metadata
Tested with
Devices(s): Nokia Lumia 822, 920, 925, 1020
Compatibility
Platform(s):
Windows Phone 8
Dependencies: protobuf-net v2.0.0.668
Platform Security
Capabilities: ID_CAP_MEDIALIB_PHOTO, ID_CAP_NETWORKING, ID_CAP_PROXIMITY
Article
Created: Rob.Kachmar (30 Dec 2013)
Last edited: Rob.Kachmar (25 Feb 2014)

Contents

Introduction

NFC is one of the hottest new technologies that continues to rise in popularity. It's time to ride the wave and add the functionality to your app. Give it that extra differentiator and make it shine among the crowd.

The two most basic things you're going to want to do with NFC are transferring text and images. The NFC Talk project from Nokia does a great job of working through all the necessary plumbing it takes to get up and running with NFC, but it stops at simple text transfers. In this article we're going to make a few adjustments to add the power of image transfers.

NFCImage Screenshot Lumia925.png NFCImage Screenshot Lumia920.png

Setup

  1. First we'll start by downloading the NFC Talk project and opening it in Visual Studio.
  2. Next, we need to install the protobuf-net library via NuGet. Go to:
  • Tools >>> Library Package Manager >>> Manage NuGet Packages for Solution...
  • Search for protobuf-net, and install the one created by Marc Gravell

NFCImage Screenshot NuGet-protobuf-net.PNG

Enhance Message.cs

There are a number of things we must do to the Message class to prepare it for handling image transfers.

New Members and ProtoBuf-Net

Now we need to update the Message.cs class with members to hold our image objects and attributes for ProtoBuf-Net to do its magic.

1. We need to add two new members: ImageName and ImageBytes

  • ImageName will hold the file name of our image. We'll use this to access the image from our local app storage when we want to display it.
  • ImageBytes will hold the image in a raw byte format, which we will only use when we transfer it. We don't want to persist storage of the images in the message objects because it could quickly eat up the phone's memory if too many messages were loaded that contained images. The file system is a much better choice for image storage.


2. We need to add the ProtoBuf namespace and specific attributes, which are explained in more detail here.

  • We will add a [ProtoContract] attribute above the Message class declaration.
  • We will add [ProtoMember(#)] attributes to each of our member declarations, beginning with 1 and incrementing by 1 with each additional member.

The modified Message.cs class should now look like this:

  1. using ProtoBuf;
  2.  
  3. namespace NFCTalk
  4. {
  5.     /// <summary>
  6.     /// Representation for a single chat message.
  7.     /// </summary>
  8.     [ProtoContract]
  9.     public class Message
  10.     {
  11.         public enum DirectionValue
  12.         {
  13.             In = 0,
  14.             Out = 1
  15.         }
  16.  
  17.         /// <summary>
  18.         /// Direction of message, in to this device, or out to the other device.
  19.         /// </summary>
  20.         [ProtoMember(1)]
  21.         public DirectionValue Direction { get; set; }
  22.  
  23.         /// <summary>
  24.         /// Sender's name.
  25.         /// </summary>
  26.         [ProtoMember(2)]
  27.         public string Name { get; set; }
  28.  
  29.         /// <summary>
  30.         /// Message.
  31.         /// </summary>
  32.         [ProtoMember(3)]
  33.         public string Text { get; set; }
  34.  
  35.         /// <summary>
  36.         /// Is this message archived.
  37.         /// </summary>
  38.         [ProtoMember(4)]
  39.         public bool Archived { get; set; }
  40.  
  41.         [ProtoMember(5)]
  42.         public string ImageName { get; set; }
  43.  
  44.         [ProtoMember(6)]
  45.         public byte[] ImageBytes { get; set; }
  46.     }
  47. }


Constants and Properties

These will help simplify some of our file operation related code.

  1. public static string PICTURES_FOLDER = "Pictures";
  2. public static System.Windows.Visibility VISIBLE = System.Windows.Visibility.Visible;
  3. public static System.Windows.Visibility COLLAPSED = System.Windows.Visibility.Collapsed;
  4.  
  5. // Provides a quick way to access the full image file path
  6. public string ImagePath
  7. {
  8.     get
  9.     {
  10.         return string.Concat(PICTURES_FOLDER, "\\", this.ImageName);
  11.     }
  12. }
  13.  
  14. // Returns a BitmapImage object of the image file in local storage for easy binding to the XAML
  15. public BitmapImage Image
  16. {
  17.     get
  18.     {
  19.         BitmapImage image = new BitmapImage();
  20.  
  21.         using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication())
  22.         {
  23.             if (isoStore.FileExists(ImagePath))
  24.             {
  25.                 using (var stream = isoStore.OpenFile(ImagePath, System.IO.FileMode.Open))
  26.                 {
  27.                     image.SetSource(stream);
  28.                 }
  29.             }
  30.         }                
  31.  
  32.         return image;
  33.     }
  34. }
  35.  
  36. // Handy non-async way of determining if an image file exists
  37. public bool ImageExists
  38. {
  39.     get
  40.     {
  41.         bool imageExists = false;
  42.  
  43.         using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication())
  44.         {
  45.             if (isoStore.FileExists(ImagePath))
  46.             {
  47.                 imageExists = true;
  48.             }
  49.         }
  50.  
  51.         return imageExists;
  52.     }
  53. }
  54.  
  55. // Property to easily bind to the image controls in XAML to determine when they should be visible or collapsed
  56. public System.Windows.Visibility ShowImage
  57. {
  58.     get
  59.     {
  60.         return (ImageExists) ? VISIBLE : COLLAPSED;
  61.     }
  62. }


ImageExistsAsync()

This is a quick little method we can call to determine if a picture we want to access exists in the local app storage. We'll see the purpose of the overrideImageName parameter later on when we are working with the temporarily selected image.

public async Task<bool> ImageExistsAsync(string overrideImageName = "")
{
bool exists = false;
 
StorageFolder localFolder;
StorageFolder pictureFolder;
 
try
{
localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
pictureFolder = await localFolder.CreateFolderAsync(PICTURES_FOLDER, CreationCollisionOption.OpenIfExists);
 
exists = (File.Exists(string.Concat(pictureFolder.Path, "\\", overrideImageName.Equals("") ? this.ImageName : overrideImageName)));
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Concat("Failed to determine if the image exists. >>> ", ex));
}
finally
{
pictureFolder = null;
localFolder = null;
GC.Collect();
}
 
return exists;
}


ConvertImageToByteArrayAsync()

This handles the work necessary to convert an image file into a byte array, so we can transfer it to another device.

  • First we stream the image file into a BitmapImage using the built in OpenStreamForReadAsync() method of the StorageFile class.
  • Then we pass it to a WriteableBitmap where we will use the built in SaveJpeg() method to convert it into a JPEG MemoryStream.
  • Finally, we use the ToArray() method of the MemoryStream class to finish the job of converting it into the byte array.
public async Task<bool> ConvertImageToByteArrayAsync(string overrideImageName = "")
{
bool success = false;
 
StorageFolder localFolder;
StorageFolder pictureFolder;
StorageFile tempFile;
BitmapImage bi;
WriteableBitmap wb;
byte[] imageBuffer;
 
try
{
if (!await ImageExistsAsync(overrideImageName))
{
System.Diagnostics.Debug.WriteLine(string.Concat("Failed to create the Byte Array because there is no existing image."));
}
else
{
// 1) Transform the image file into a JPEG byte array
localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
pictureFolder = await localFolder.CreateFolderAsync(PICTURES_FOLDER, CreationCollisionOption.OpenIfExists);
tempFile = await pictureFolder.GetFileAsync(overrideImageName.Equals("") ? this.ImageName : overrideImageName);
bi = new BitmapImage();
using (var s = await tempFile.OpenStreamForReadAsync())
{
bi.SetSource(s);
}
wb = new WriteableBitmap(bi);
using (var ms = new MemoryStream())
{
int quality = 90;
wb.SaveJpeg(ms, wb.PixelWidth, wb.PixelHeight, 0, quality);
imageBuffer = ms.ToArray();
}
 
// 2) Assign it to the message's byte array memeber and set success to true
this.ImageBytes = imageBuffer;
success = true;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(string.Concat("Failed to create the Byte Array. >>> ", ex));
}
finally
{
imageBuffer = null;
wb = null;
bi = null;
tempFile = null;
pictureFolder = null;
localFolder = null;
GC.Collect();
}
 
return success;
}


SaveByteArrayImageToFileSystemAsync()

This handles saving the byte array to the file system after it is received from a transfer.

  • We just use the built in OpenStreamForWriteAsync() method from the StorageFile class to easily stream the bytes into a file.
  • We also ensure that we clean up the byte array in the Message object, since we no longer need it once it is in our local app storage.
  • However, we only want to clean up the byte array on the receiving end of the transfer. We're going to use this same method on the transmission end to save off the temporary file right before transferring it, so we'll need to override the default behavior which cleans up the byte array.
  1. public async Task<bool> SaveByteArrayImageToFileSystemAsync(bool cleanUpByteArray = true)
  2. {
  3.     bool success = false;
  4.  
  5.     if (ImageBytes != null)
  6.     {
  7.         StorageFolder localFolder;
  8.         StorageFolder pictureFolder;
  9.         StorageFile tempFile;
  10.  
  11.         try
  12.         {
  13.             // 1) Save the byte array to the local Pictures folder we created
  14.             localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
  15.             pictureFolder = await localFolder.CreateFolderAsync(PICTURES_FOLDER, CreationCollisionOption.OpenIfExists);
  16.             tempFile = await pictureFolder.CreateFileAsync(this.ImageName, CreationCollisionOption.ReplaceExisting);
  17.             using (var s = await tempFile.OpenStreamForWriteAsync())
  18.             {
  19.                 s.Write(this.ImageBytes, 0, this.ImageBytes.Length);
  20.             }
  21.  
  22.             // 2) Unless overridden, NULL out the byte array now that the image is safely on the file system.
  23.             if (cleanUpByteArray)
  24.             {
  25.                 this.ImageBytes = null;
  26.             }
  27.             success = true;
  28.         }
  29.         catch (Exception ex)
  30.         {
  31.             System.Diagnostics.Debug.WriteLine(string.Concat("Failed to save the image. >>> ", ex));
  32.         }
  33.         finally
  34.         {
  35.             tempFile = null;
  36.             pictureFolder = null;
  37.             localFolder = null;
  38.             GC.Collect();
  39.         }                
  40.     }
  41.  
  42.     return success;
  43. }


Enhance App.xaml.cs

We'll need to add a new property and adjust the Application_Deactivated method.

New Property

We'll need a new static property, PhotoActivity, which will allow us to keep track of our photo related activities across the application.

public static bool PhotoActivity { get; set; }


Application_Deactivated()

Here we just need to wrap the call to _dataContext.Communication.Stop() in an if statement. We do not want to stop any communication with our peer just because we are performing a photo activity, like navigating away from the app to select a photo to send.

  1. private void Application_Deactivated(object sender, DeactivatedEventArgs e)
  2. {
  3.     if (!App.PhotoActivity)
  4.     {
  5.         _dataContext.Communication.Stop();
  6.     }
  7.     _dataContext.Save();
  8. }


Enhance TalkPage.xaml

We'll need new controls to choose and view the images.

Viewing the Images

We'll add a new image control after the 2nd TextBlock in each TalkTemplateSelector group (4 in all). The control will be bound to the Image and ShowImage properties we created earlier in Message.cs. Here's what the first group looks like:

  1. <local:TalkTemplateSelector.OutBoundTemplate>
  2.     <DataTemplate>
  3.         <StackPanel Width="436">
  4.             <StackPanel Background="#3366FF" MinWidth="160" MaxWidth="420" Margin="0,0,0,18" HorizontalAlignment="Left">
  5.                 <TextBlock Text="{Binding Name}" Margin="12,12,12,0" Style="{StaticResource PhoneTextSmallStyle}" Foreground="White"/>
  6.                 <TextBlock Text="{Binding Text}" Margin="12,6,12,12" Style="{StaticResource PhoneTextNormalStyle}" TextWrapping="Wrap" Foreground="White"/>
  7.                 <Image Source="{Binding Image}" CacheMode="BitmapCache" Visibility="{Binding ShowImage}" Margin="12,6,12,12" Width="100" Height="100" />
  8.             </StackPanel>
  9.         </StackPanel>
  10.     </DataTemplate>
  11. </local:TalkTemplateSelector.OutBoundTemplate>


Clean up Controls

We're going to switch to using an app bar menu button to send the messages, so we can comment out or remove the sendButton. We'll also increase the size of the messageInput TextBox to 456 since we no longer have a button out to the right of it. Additionally, we'll need an Image control to view the selected picture.

  1. <StackPanel Grid.Row="1">
  2.     <StackPanel Grid.Row="1" Orientation="Horizontal" >
  3.         <TextBox x:Name="messageInput" Width="456" InputScope="Chat" TextChanged="messageInput_TextChanged"/>
  4.         <!--<Button x:Name="sendButton" Width="100" Content="say" Click="sendButton_Click"/>-->
  5.     </StackPanel>
  6.     <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
  7.         <Image x:Name="messageImage" Height="150" Visibility="Collapsed" />
  8.     </StackPanel>
  9. </StackPanel>


Enhance TalkPage.xaml.cs

There are quite a few things we must do to TalkPage.xaml.cs to start rounding out our solution.

New Members

We need to add a few members to our code-behind class.

  1. public partial class TalkPage : PhoneApplicationPage
  2. {
  3.         private NFCTalk.DataContext _dataContext = NFCTalk.DataContext.Singleton;
  4.         private ApplicationBarIconButton sendButton = new ApplicationBarIconButton();
  5.         private PhotoChooserTask m_PhotoChooserTask = new PhotoChooserTask();
  6.         private string m_TempImageName = "tempImage.jpg";
  7.         private bool m_ImageReadyToTransfer = false;
  8. ...


Photo Chooser Event Handler

In the page constructor, we need to assign the PhotoChooserTask_Completed method to the Completed event of m_PhotoChooserTask. We'll also call a new BuildApplicationBar() method.

  1. public TalkPage()
  2. {
  3.     InitializeComponent();
  4.  
  5.     m_PhotoChooserTask.Completed += PhotoChooserTask_Completed;
  6.     BuildApplicationBar();
  7.  
  8.     DataContext = _dataContext;
  9. }


BuildApplicationBar()

We're going to link up the add image and send message actions to buttons on the app bar. First we'll need some quality icon images for them, so download this send message icon and image icon from the Templarian's open source WindowsIcons project, and drop them into the Assets/Icons folder. Then we'll drop this method into TalkPage.xaml.cs.

  1. private void BuildApplicationBar()
  2. {
  3.     // Set the page's ApplicationBar to a new instance of ApplicationBar.
  4.     ApplicationBar = new ApplicationBar();
  5.  
  6.     sendButton = new ApplicationBarIconButton(new Uri("/Assets/Icons/appbar.message.send.png", UriKind.Relative));
  7.     sendButton.Text = "Send";
  8.     sendButton.Click += sendButton_Click;
  9.     ApplicationBar.Buttons.Add(sendButton);
  10.  
  11.     ApplicationBarIconButton addImageButton = new ApplicationBarIconButton(new Uri("/Assets/Icons/appbar.image.png", UriKind.Relative));
  12.     addImageButton.Text = "Add Image";
  13.     addImageButton.Click += addImageButton_Click;
  14.     ApplicationBar.Buttons.Add(addImageButton);
  15. }


messageInput_TextChanged()

We need to change the enable/disable logic for the sendButton to also take into account whether or not an image has been selected to be sent by checking m_ImageReadyToTransfer.

  1. private void messageInput_TextChanged(object sender, TextChangedEventArgs e)
  2. {
  3.     sendButton.IsEnabled = (m_ImageReadyToTransfer || messageInput.Text.Length > 0);
  4. }


addImageButton_Click()

We'll also need to define the addImageButton_Click() handler we associated with the addImageButton.Click event in the BuildApplicationBar() method. Additionally, make note that we are setting the App.PhotoActivity property to true. This will come into play later on when we are avoiding communication disconnects as we are temporarily navigating away to select an image.

  1. private void addImageButton_Click(object sender, EventArgs e)
  2. {
  3.     App.PhotoActivity = true;
  4.     m_PhotoChooserTask.Show();
  5. }


PhotoChooserTask_Completed()

Here's the PhotoChooserTask_Completed method we need.

  1. private async void PhotoChooserTask_Completed(object sender, PhotoResult e)
  2. {
  3.     if (e.TaskResult == TaskResult.OK)
  4.     {
  5.         StorageFolder localFolder;
  6.         StorageFolder pictureFolder;
  7.         StorageFile tempFile;
  8.         BitmapImage bi;
  9.         WriteableBitmap wb;
  10.  
  11.         try
  12.         {
  13.             // 1) Assign the selected image to the messageImage control
  14.             bi = new BitmapImage();
  15.             bi.SetSource(e.ChosenPhoto);
  16.             messageImage.Source = bi;
  17.             messageImage.Visibility = System.Windows.Visibility.Visible;
  18.  
  19.             // 2) Write it to local storage
  20.             localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
  21.             pictureFolder = await localFolder.CreateFolderAsync(Message.PICTURES_FOLDER, CreationCollisionOption.OpenIfExists);
  22.             tempFile = await pictureFolder.CreateFileAsync(m_TempImageName, Windows.Storage.CreationCollisionOption.ReplaceExisting);
  23.             using (var wfs = await tempFile.OpenStreamForWriteAsync())
  24.             {
  25.                 e.ChosenPhoto.Seek(0, SeekOrigin.Begin);
  26.                 await e.ChosenPhoto.CopyToAsync(wfs);
  27.             }
  28.             m_ImageReadyToTransfer = true;
  29.             sendButton.IsEnabled = (m_ImageReadyToTransfer || messageInput.Text.Length > 0);
  30.  
  31.             System.Diagnostics.Debug.WriteLine(string.Concat("Successfully saved the image: ", tempFile.Path));
  32.         }
  33.         catch (Exception ex)
  34.         {
  35.             System.Diagnostics.Debug.WriteLine(string.Concat("Failed to save the image >>> ", ex));
  36.         }
  37.         finally
  38.         {
  39.             wb = null;
  40.             bi = null;
  41.             tempFile = null;
  42.             pictureFolder = null;
  43.             localFolder = null;
  44.             GC.Collect();
  45.         }
  46.     }
  47.  
  48.     App.PhotoActivity = false;
  49. }


OnNavigatedTo()

We need to wrap the custom behavior of the OnNavigateTo method in an if statement, so that it does not execute when we are in the middle of selecting a photo.

  1. protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
  2. {
  3.     base.OnNavigatedTo(e);
  4.  
  5.     if (!App.PhotoActivity)
  6.     {
  7.         if (_dataContext.Communication.IsConnected)
  8.         {
  9.             sendButton.IsEnabled = false;
  10.  
  11.             _dataContext.Communication.ConnectionInterrupted += ConnectionInterrupted;
  12.             _dataContext.Communication.MessageReceived += MessageReceived;
  13.  
  14.             _dataContext.Messages.CollectionChanged += MessagesChanged;
  15.  
  16.             Deployment.Current.Dispatcher.BeginInvoke(() =>
  17.             {
  18.                 scrollToLast();
  19.             });
  20.         }
  21.         else
  22.         {
  23.             NavigationService.GoBack();
  24.         }   
  25.     }
  26. }


OnNavigatingFrom()

We need to wrap the custom behavior of the OnNavigatingFrom method in an if statement, so that it does not execute when we are in the middle of selecting a photo.

  1. protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
  2. {
  3.     base.OnNavigatingFrom(e);
  4.  
  5.     if (!App.PhotoActivity)
  6.     {
  7.         _dataContext.Communication.ConnectionInterrupted -= ConnectionInterrupted;
  8.         _dataContext.Communication.MessageReceived -= MessageReceived;
  9.  
  10.         _dataContext.Messages.CollectionChanged -= MessagesChanged;
  11.  
  12.         _dataContext.Communication.Disconnect();
  13.  
  14.         foreach (Message m in _dataContext.Messages)
  15.         {
  16.             m.Archived = true;
  17.         }
  18.     }
  19. }


sendButton_Click()

If the user selected an image, we need to add that to the message, as well as clean up the temporary inputs after the message is sent. Here's where we make use of the overrideImageName parameter we mentioned earlier. Additionally, we have to change the e input parameter from RoutedEventArgs to just EventArgs since it is now called from the app bar instead of a button control.

  1. private async void sendButton_Click(object sender, EventArgs e)
  2. {
  3.     Message m = new Message()
  4.     {
  5.         Name = _dataContext.Settings.Name,
  6.         Text = messageInput.Text,
  7.         Direction = Message.DirectionValue.Out
  8.     };
  9.  
  10.     // If we are transferring an image, load it into the message from the temporary image file
  11.     if (m_ImageReadyToTransfer)
  12.     {
  13.         m.ImageName = string.Format("{0}.jpg", Guid.NewGuid()); // assign a unique image name
  14.         await m.ConvertImageToByteArrayAsync(m_TempImageName); // load up the temporary image into the message's byte array
  15.         await m.SaveByteArrayImageToFileSystemAsync(false); // save a local copy of the image, and do not clean up the byte array
  16.     }
  17.  
  18.     // Clean up input values
  19.     messageInput.Text = "";
  20.     messageImage.Source = null;
  21.     messageImage.Visibility = System.Windows.Visibility.Collapsed;
  22.     m_ImageReadyToTransfer = false;
  23.  
  24.     _dataContext.Messages.Add(m);
  25.  
  26.     await _dataContext.Communication.SendMessageAsync(m);
  27.  
  28.     scrollToLast();
  29. }


MessageReceived()

Here we need to attempt to save the image byte array to the file system. We already put logic into the SaveByteArrayImageToFileSystemAsync() method earlier to bail out if there is no byte array, so we can call this with every receive event without worrying about which one has an image or not.

  1. private void MessageReceived(Message m)
  2. {           
  3.     Deployment.Current.Dispatcher.BeginInvoke(async () =>
  4.     {
  5.         await m.SaveByteArrayImageToFileSystemAsync();
  6.  
  7.         _dataContext.Messages.Add(m);
  8.     });
  9. }


Enhance Communications.cs

The last set of significant changes will be with Communications.cs. We'll be using ProtoBuf-Net's serialization and making adjustments to handle the entire Message object during the transmissions.

Add Namespaces

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Threading.Tasks;
  5. using System.Windows;
  6. using Windows.Networking.Proximity;
  7. using Windows.Networking.Sockets;
  8. using Windows.Storage.Streams;
  9. using ProtoBuf;
  10. using System.IO;
  11. using System.Runtime.InteropServices.WindowsRuntime;


Methods Using ProtoBuf-Net

Here's where we bring in the magic of ProtoBuf-Net to handle serializing our entire Message object to and from a byte array. Drop these conversion helper methods anywhere in the Communication class.

  1. private byte[] ConvertMessageToByteArray(Message msg)
  2. {
  3.     if (msg == null)
  4.     {
  5.         return null;
  6.     }
  7.  
  8.     using (MemoryStream ms = new MemoryStream())
  9.     {
  10.         Serializer.Serialize<Message>(ms, msg);
  11.         return ms.ToArray();
  12.     }
  13. }
  14.  
  15. private Message ConvertByteArrayToMessage(byte[] msg)
  16. {
  17.     if (msg == null)
  18.     {
  19.         return null;
  20.     }
  21.  
  22.     using (MemoryStream ms = new MemoryStream(msg))
  23.     {
  24.         return Serializer.Deserialize<Message>(ms);
  25.     }
  26. }


SendMessageAsync()

Here we've commented out the old code that only sent the Text value of the Message object, and replaced it with code to handle sending the entire Message.

  1. public async Task SendMessageAsync(Message m)
  2. {
  3.     try
  4.     {
  5.         //if (m.Text.Length > 0)
  6.         if (m != null)
  7.         {
  8.             _writer.WriteUInt32(0); // protocol version
  9.             _writer.WriteUInt32(1); // operation identifier
  10.  
  11.             //uint length = _writer.MeasureString(m.Text);
  12.             //_writer.WriteUInt32(length);
  13.             //_writer.WriteString(m.Text);
  14.             IBuffer buf = ConvertMessageToByteArray(m).AsBuffer();
  15.             _writer.WriteUInt32(buf.Length);
  16.             _writer.WriteBuffer(buf);
  17.  
  18.             await _writer.StoreAsync();
  19.         }
  20.     }
  21.     catch (Exception)
  22.     {
  23.         if (ConnectionInterrupted != null)
  24.         {
  25.             ConnectionInterrupted();
  26.         }
  27.     }
  28. }


ListenAsync()

We just need to focus on the case 1: tier of the method's switch statement. We've commented out the old code that only expected the Text value of the Message object to be sent, and replaced it with code to handle receiving the entire Message.

  1. case 1: // message
  2.     {
  3.         await GuaranteedLoadAsync(sizeof(UInt32));
  4.         uint length = _reader.ReadUInt32();
  5.  
  6.         await GuaranteedLoadAsync(length);
  7.         //string text = _reader.ReadString(length);
  8.  
  9.         //Message m = new Message()
  10.         //{
  11.         //    Name = PeerName,
  12.         //    Text = text,
  13.         //    Direction = Message.DirectionValue.In
  14.         //};
  15.         Message m = ConvertByteArrayToMessage(_reader.ReadBuffer(length).ToArray());
  16.         m.Direction = Message.DirectionValue.In;
  17.  
  18.         if (MessageReceived != null)
  19.         {
  20.             MessageReceived(m);
  21.         }
  22.     }
  23.     break;


Start()

Since we are overriding the default expectation of a communication disconnect whenever the app is deactivated, we also need to update the Start() method to be aware of this possible scenario. We'll just turn the regular else into an else if to avoid errors if we are already connected. Additionally, we will just call the Stop() method if we are not idle or connected; no need to throw an error here.

  1. public void Start()
  2. {
  3.     if (_status == ConnectionStatusValue.Idle)
  4.     {
  5.         _status = ConnectionStatusValue.Searching;
  6.  
  7.         PeerName = "";
  8.  
  9.         PeerFinder.DisplayName = NFCTalk.DataContext.Singleton.Settings.Name;
  10.         PeerFinder.TriggeredConnectionStateChanged += TriggeredConnectionStateChanged;
  11.         PeerFinder.ConnectionRequested += ConnectionRequested;
  12.  
  13.         PeerFinder.Start();
  14.     }
  15.     else if (_status != ConnectionStatusValue.Connected)
  16.     {
  17.         //throw new Exception(string.Concat("Bad state, please stop first - _status >>> ", _status));
  18.         this.Stop();
  19.     }
  20. }


Wrapping Things Up

We're almost there!

WMAppManifest.xml

Now that we've added functionality to select photos, we need to make sure we update our app's permissions. Open the WMAppManifest.xml file, select the Capabilities tab, and check the box next to ID_CAP_MEDIALIB_PHOTO.

NFCImage Screenshot MediaLibCapabilities.png

Summary

There you have it, an application that can transfer both text and images! Now it's up to you to have some fun and give your app that extra differentiating edge.

If this was helpful for adding NFC to your app, be sure to add a comment below with a link to your app for others to see the great work you did.

Note.pngNote: You can download a forked version of the NFC Talk application here, which has all the changes implemented from the article.

This page was last modified on 25 February 2014, at 07:53.
163 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×