Introduction
If you have ever had to develop a business application that targets the Android / mobile platform, you may have had to print from your application. Unlike printing from your desktop PC at home or work, you don't tend to connect your mobile device to a printer, so printing becomes a more difficult and problematic issue. In this article, I will describe how you can print from your Xamarin.Android
application. Although the article was written with Xamarin.Android
and C# in mind, the article lends itself just as easily to Eclipse and Java.
Background
This article uses a Star Micronics thermal printer. A thermal printer produces a digital image by heating thermal paper. The range from Star Micronics enable printing from web-based applications via HTTP requests. They are small, lightweight and have a very small memory footprint making them ideal for printing from mobile devices. They also support Bluetooth and so can be used wirelessly, making them even more appropriate for mobile device printing.
To use the printer in your application, you will firstly need to download the Star Micronics SDK. Once you have downloaded the SDK, you will need to add a reference the StarPrinter.dll assembly to your Visual Studio project.
A Note About Responsiveness
Before we get started, it is important to keep application responsiveness firmly in mind. The worst thing that can happen to your application is that it triggers an "Application Not Responding" (ANR) dialog. Within Android, the system guards against applications that are insufficiently responsive for a period of time by displaying a dialog stating your application has stopped responding.
Printing is exactly the sort of task that is likely to take relatively more time to complete than other tasks, and therefore a prime candidate for triggering an ANR dialog. You should therefore carefully consider how your application will perform printing tasks without triggering an ANR. You should not perform print tasks on the main UI thread, and instead consider another strategy such as implementing your print tasks using a Thread
or AsyncTask
instead.
A detailed discussion of Android application responsiveness is outside the scope of this article, but is certainly something that you need to consider when designing and implementing your application.
This article assumes familiarity with Android graphical elements such as Bitmap
, Canvas
, Paint
, TextLayout
and StaticLayout
.
Adding Star Micronics to Your Application
To include Star Micronics print functionality in your application, you will need to add the following reference to your code:
using Com.Starmicronics.Stario;
Finding Your Star Micronics Bluetooth Printer
Before your application can use the printer, it will firstly have to find it. Star printers can be located using either Bluetooth or TCP (LAN) protocols. Although in practice your application will use Bluetooth, I have added an example of how you can also search for your printer using TCP for the sake of completeness.
new Thread(() =>
{
try
{
IList<PortInfo> portList = StarIOPort.SearchPrinter("BT");
if (!portList.Any())
{
portList = StarIOPort.SearchPrinter("BT:DeviceName");
}
if (!portList.Any())
{
portList = StarIOPort.SearchPrinter("BT:MacAddress");
}
if (!portList.Any())
{
portList = StarIOPort.SearchPrinter("BT:Star Micronics");
}
if (!portList.Any())
{
portList = StarIOPort.SearchPrinter("TCP");
}
if (!portList.Any())
{
RunOnUiThread(() =>
ShowAlert("No printers found, connect on bluetooth first",
ToastLength.Long, Resource.Drawable.printer_cross));
}
}
catch (StarIOPortException ex)
{
RunOnUiThread(() =>
Toast.MakeText(this, "Error finding printers:
" + ex.Message, ToastLength.Long).Show());
}
}).Start();
To locate your Star printer, you need to invoke the SearchPrinter()
function. This function returns a list of printers that it finds using the specified interface types to search. It will search for printers on LAN or paired Bluetooth devices. You will probably want to invoke this code at application startup.
Note how the printer search is executed on a Thread()
so it does not occupy the main UI thread and possibly trigger an ANR. Note also that the mechanism invokes the SearchPrinter()
function sequentially, specifying several different interface types. This gives your application a greater chance of successfully finding your printer.
IList<PortInfo> portList = StarIOPort.SearchPrinter(string target);
Valid values for the target parameter are:
BT
BT:<DeviceName>
BT:<MacAddress>
TCP
TCP:<IPAddress>
Where <DeviceName>
, <MacAddress>
and <IPAddress>
are the device name, MAC address and IP address respectively. These can be specified optionally.
Finding a Printer Port
Once you have found the printer, you need to open a connection to it. The function GetPort()
is used to accomplish this. If successful, it will return a handle to the printer port.
StarIOPort port = StarIOPort.GetPort(string portName, string portSettings, int timeoutMillisecs);
portName
is the name of the printer found previously using the SearchPrinter()
function - i.e.
portList[0].PortName
portSettings
is for either Ethernet or Bluetooth. For the purposes of this article, I will focus only on Bluetooth. If using a mini printer, then specify this with mini. This is required for all Bluetooth printers. timeoutMillisecs
is the timeout in milliseconds used when writing or reading from the port. 1000 milliseconds is 1 second.
Example usage:
StarIOPort port = StarIOPort.GetPort(portList[0].PortName, "mini", 10000);
Memory Considerations
Before proceeding and showing how to actually print from your printer, it is important to note that Android devices are low powered, low memory devices. As such, they lack the corresponding CPU and memory horse power of other devices such as a laptop or desktop PC. Memory limitations were the single biggest issue I experienced when implementing print functionality for the Android platform.
If the text required to be printed contains images (such as logos, boxes around the text, signatures, etc.), then you may want to consider creating a bitmap image and printing this image. Due to the memory limitations of Android devices, you may want to consider how often you print out these images. Initially, my applications threw "Out of memory
" errors as I was printing the entire image list at the end of the print routine. I resolved this by building up a list of bitmaps and printing these one at a time to the printer.
Here is the class declaration for the bitmap list that the application used:
private static List<Bitmap> _imageList;
Here is the code to print your bitmap list:
private static readonly List<byte> InitialisePrinter = new List<byte> { Esc, 0x40 };
private static void PrintImageCollection(StarIOPort port)
{
try
{
if (_imageList != null && _imageList.Any())
{
foreach (var bmp in _imageList)
{
var command = new List<byte>();
var starBitmapReceipt = new StarBitmap(bmp, true, 832);
command.AddRange(InitialisePrinter);
command.AddRange(starBitmapReceipt.GetImageEscPosDataForPrinting(true, true));
port.WritePort(command.ToArray(), 0, command.Count);
}
_bitmap.Dispose();
_imageList.Clear();
}
}
catch (Exception ex)
{
throw ex;
}
}
The key method to note in this code snippet is WritePort(byte[] writeBuffer, int offset, int size)
.
WritePort(byte[] writeBuffer, int offset, int size);
writeBuffer
is the array containing the data to be written offset
is the start offset for writing data size
is the amount of data that should be written to the pointer
Full Listing of a Print Class
In the example code below, some basic details are printed, i.e. name and address. Each piece of information to be printed is added to a bitmap image one-at-a-time. The bitmaps are added to an image list. The print routine iterates through this image list and prints the individual bitmaps.
using System;
using System.Collections.Generic;
using Android.Graphics;
using Android.Text;
using Com.Starmicronics.Stario;
public class Printing
{
public PrintForm()
{
private static Canvas _canvas;
private static Paint _paint;
private static Bitmap _bitmap;
private static TextPaint _textPaint;
private static readonly List<byte> InitialisePrinter = new List<byte> { Esc, 0x40 };
private static List<Bitmap> _imageList;
private static readonly Bitmap.Config BitmapConfig = Bitmap.Config.Argb4444;
public static void PrintDetails(StarIOPort port)
{
try
{
PrintText("\nMy Details", Bold, Layout.Alignment.AlignNormal, 26);
PrintText("Name", String.Format("{0}",
"Dominic Burford"), 400, false, false, rowHeight);
PrintText("Addr1", String.Format("{0}",
"1 High Street"), 400, false, false, rowHeight);
PrintText("Addr2", String.Format("{0}",
"London"), 400, false, false, rowHeight);
PrintText("Postcode", String.Format("{0}",
"XX1 1YY"), 400, false, false, rowHeight);
_imageList.Add(_bitmap.Copy(BitmapConfig, false));
NewBitmapImage(port);
"details 1"), 400, false, false, rowHeight);
"details 2"), 400, false, false, rowHeight);
}
catch(Exception ex)
{
throw ex;
}
}
private static void NewBitmapImage(StarIOPort port)
{
try
{
if (port != null)
PrintImageCollection(port);
_bitmap.Dispose();
_bitmap = null;
_bitmap = Bitmap.CreateBitmap(832, 10, BitmapConfig);
_canvas = new Canvas(_bitmap);
_canvas.DrawColor(Color.White);
_canvas.Translate(0, 0);
}
catch (Exception ex)
{
throw ex;
}
}
private static void PrintImageCollection(StarIOPort port)
{
try
{
if (_imageList != null && _imageList.Any())
{
foreach (var bmp in _imageList)
{
var command = new List<byte>();
var starBitmapReceipt = new StarBitmap(bmp, true, 832);
command.AddRange(InitialisePrinter);
command.AddRange(starBitmapReceipt.GetImageEscPosDataForPrinting(true, true));
port.WritePort(command.ToArray(), 0, command.Count);
}
_bitmap.Dispose();
_imageList.Clear();
}
}
catch (Exception ex)
{
throw ex;
}
}
private static void PrintText(string text, Typeface typeface,
Layout.Alignment alignment, int textSize, int width = 832)
{
try
{
using (var details = _bitmap.Copy(BitmapConfig, false))
{
if (_paint == null)
_paint = new Paint();
_paint.SetTypeface(typeface);
_paint.TextSize = textSize;
_textPaint = new TextPaint(_paint);
var statlayout = new StaticLayout(text, _textPaint, width,
alignment, 1, 0, false);
_bitmap = Bitmap.CreateBitmap(832, statlayout.Height +
details.Height + 30, BitmapConfig);
_canvas = new Canvas(_bitmap);
_canvas.DrawColor(Color.White);
_canvas.Translate(0, 0);
_canvas.DrawBitmap(details, 0, 0, _paint);
_canvas.Translate(0, details.Height);
statlayout.Draw(_canvas);
_canvas.Translate(0, -details.Height);
details.Dispose();
}
}
catch (Exception ex)
{
throw ex;
}
}
}
}
You would need to repeat the below steps for each section of information you wanted to print if you had other sections of information to print.
PrintText("\nSome text", Bold, Layout.Alignment.AlignNormal, 26);
_imageList.Add(_bitmap.Copy(BitmapConfig, false));
NewBitmapImage(port);
Summary
Hopefully, this article has given you sufficient information to start printing from your own Xamarin.Android
application. Feel free to leave a comment if you would like me to further elaborate on anything within this article.