Jul 192011
 

We are proud to announce a BETA release of the DYMO Label Mobile SDK for Android. The DYMO Label Mobile SDK for Android is a complete toolset containing libraries, documentation, and samples that make it easy to add label printing to any Android app. All DYMO LabelWriter and DYMO LabelManager printers are supported. Using the DYMO Label Mobile SDK in combination with DYMO Label Web SDK allows creating both native and web-based apps for Android.

Please Note

This release is a BETA and is not supported by DYMO. It has not been extensively tested outside DYMO and should be used for developer testing only, NOT for production software releases.

Architecture Overview

It is essentially the same as the architecture of DYMO Label Mobile SDK for iOS.

DYMOLabelMobileSDKforAndroid

The major piece here is a computer that has a DYMO printer plugged-in and DYMO Label software installed. DYMO Label software contains a service called DYMO Label Proxy that allows communication between an Android device and the printer.

Notes:

  • right now DYMO Label Proxy is available for Windows only. So, a printer must be connected to a Windows machine, or be available from Windows machine that has DYMO Label Proxy installed (see below).
  • the printer itself does not have to be connected directly to the computer running DYMO Label Proxy. The printer has to be ACCESSIBLE from that machine. This means that the printer might be really connected to a different machine and be a “shared” network printer. Or the printer might be connected to a DYMO Print Server and be “installed” on the computer using a RAW TCP/IP port. This setup might be used to simplify installation (see below).
  • the local network is used for communications between the proxy service and the mobile device. That means that on the mobile device the Wi-Fi should be turned on and the device should be connected to the network. Using just 3G is not enough.

Installation

SDK Installation

SDK is available here. It is a zip file, just extract it to any folder. Inside there are following folders:

  • docs – SDK documentation. Contains API reference documentation in JavaDoc format.
  • libs – jars to be included with an application.
  • Samples – sample apps.

Samples were tested with Eclipse Helios and Android SDK r11. The SDK supports Android API level 8 (2.2) and later.

DYMO Label Proxy Installation

The installation is fairly simple. Just install DYMO Label software, that’s it. DYMO Label Proxy service will be installed and run as a part of the installation. DYMO Label Proxy is a Windows service running on port 8631 by default. Because of that there are couple of considerations that must be taken into account:

  • DYMO Label Software will configure Windows Firewall to open the port 8631 for inbound requests. If a different firewall software is installed, you have to configure it manually.
  • The port number may be changed from the default one. Either use the configuration utility included or specify the port manually in the service’s application .config file. In any case don’t forget to update the firewall rules to open the port. Otherwise clients will not be able to connect to the service.
  • The final release to the public will add the ability to select the port number and autostart options for the service during installation.

Note: This version of DYMO Label Proxy contains enchantments and bug fixes over the version released for Web and iOS SDKs. To be able to use with Android SDK you must update it to the latest version.

DYMO Label Proxy Configuration

To configure the service use DYMOLabelProxyServiceConfiguration.exe utility. It lets you change the port number the service is listening to as well as stop/start/restart the service. In addition the utility displays a list of urls that might be used to connect to the service.

image_thumb

Changing Port Number

To change the port number enter a new value into “Port Number” field and click “Apply” button. Right now the service is not restarted automatically, so don’t forget to restart it.

Service Control

You can start/stop/restart the service from within the configuration utility. Alternatively the standard “Services” panel of the  “Computer Management” tool can be used.

Service ip-address

Usually the SDK is able to discover the service on the network automatically using Bonjour. But sometimes Bonjour does not work. One common case is when the service and the Android device are on different subnets, e.g. the mobile device is connected to a “wireless” subnet and the service to a “wired” subnet. This is not a problem with the DYMO service, it is how Bonjour works in its default configuration. There are solutions for this problem, but the detailed descriptions are beyond the scope of this post. Some ideas:

In this case it is necessary to know the service’s ip-address to be able to connect to it from the Android device (see below). The configuration utility can help here as well. Service’s ip-address(es) is displayed in Service URIs list.

API Overview

The SDK contains two complete samples those demonstrate most of the functionality the SDK provides. Here we will look at the API from the point of tasks that are necessary to perform to print a label. To print a label the following tasks usually have to be performed:

  1. framework initialization
  2. discover a printer to print the label on
  3. load label layout
  4. set label data
  5. perform actual printing
  6. Monitor printing progress

Note: these tasks might be performed in a different order, e.g. printer discovery might be performed after the label is prepared for the printing. Though usually printer discovery should be performed first, because it can take some time.

Framework Initialization

All SDK classes and interfaces are accessed from a top label Framework object. A constructor of Framework class accept Context as a parameter. The context might be either an activity/service or be the application context. The Framework instance can be created inside activity’s onCreate()  handler. Note, that Framework initialization might take some time; so if the activity ca be frequently recreated or if the Framework should be accessed from different activities, it make sense to use some sort of singleton to make sure only one instance of the Framework exists.

import android.content.Context;
import com.dymo.label.framework.Framework;

class PrintLabelManager
{
    static private PrintLabelManager instance_;

    static PrintLabelManager instance(Context context)
    {
        if (instance_ == null)
            instance_ = new PrintLabelManager(context);

        return instance_;
    }

    private Framework framework_;

    private PrintLabelManager(Context context)
    {
        framework_ = new Framework(context.getApplicationContext());
    }

    Framework getFramework()
    {
        return framework_;
    }
}
import com.dymo.label.framework.Framework;

import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends Activity
{
    private Framework framework_; 

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        setupFramework();
    }

    private void setupFramework()
    {
        framework_ = PrintLabelManager.instance(this).getFramework();
    }
}

 

Discover Printers

Before something can be printed it is necessary to know on which printer it will be printed. Printer discovery is an asynchronous operation. The reason is that it requires network communications and various delays and timeouts might happen during the process. To handle async notifications of the discovering progress a PrintersListener object should be assigned to the Framework object. PrintersListener defines two methods, one called when a new printer or printers are discovered, another when some sort of failure happened. To assign the listener use setPrintersListeners() method. To start discovering call startRefreshPrinters() method. This method is asynchronous and returns almost immediately. When a printer is found the listener’s method newPrintersFound() will be called. The list of available printers can be obtained by the Framework.getPrinters() or by  NewPrintersFoundEvent.getPrinters() methods. If the Proxy service could not be contacted during the discovering, e.g. it went offline, the printerLookupFailure() method will be called. Here is a code snippet to demonstrate that:

public class MainActivity extends Activity
{
    private void setupFramework()
    {
        framework_ = PrintLabelManager.instance(this).getFramework();
        framework_.setPrintersListener(new PrintersListener()
        {
            @Override
            public void printerLookupFailure(PrinterLookupFailureEvent event)
            {
                final String message = String.format("Unable to contact '%s' (%s)", event.getPrinterUri(),
                    event.getLocation());
                Log.e("Print Label", message);

                MainActivity.this.runOnUiThread(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show();
                    }
                });
            }

            @Override
            public void newPrintersFound(final NewPrintersFoundEvent event)
            {
                runOnUiThread(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        // output all discovered printers to the log
                        Iterable<Printer> printers = event.getPrinters();
                        for (Printer p : printers)
                            Log.i("Print Label", p.getName());
                    }
                });
            }
        });
    }
}

Please note, the listener’s methods are called from a non-UI thread. So, if you need to perform any UI related operations that must be called from the UI thread, don’t forget to use Activity.runOnUiThread() or View.postDelayed() methods.

Unlike iOS API there is no way to automatically determine when printer discovering should be stopped. It is important to stop it manually by calling stopRefreshPrinters()method. If stopRefreshPrinters() is not called this may lead to an extensive battery drain (underneath implementation uses Bonjour, that uses multicast network requests, and the requests use a lot of power). Call stopRefreshPrinters() after a timeout and when the activity is been stopped.

    // refreshes printers and update currently selected one
    private void refreshPrinters()
    {

        framework_.startRefreshPrinters();

        // / stop refreshing in 5 seconds
        labelContentEditText_.postDelayed(new CheckedRunnable()
        {
            @Override
            public void run2()
            {
                Log.i("PrintLabel", "----------- Stopping printers lookup after 5 seconds");
                framework_.stopRefreshPrinters();

                // usually this is called from newPrintersFound() handler
                // newPrintersFound will not be called if all printers are gone 
                updateCurrentPrinter(framework_.getPrinters());
            }
        }, 5000);
    }

 

Load Label Layout

A label layout specifies what will printed on a label. The layout contains ‘label objects’ – text, address, barcode, image, etc. Each object has a ‘reference name’, so the object can be referenced programmatically from the SDK. Usually DYMO Label software is used to create a layout and save it as xml file. Because the layout is serialized as a xml document, the layout can be created or modified using any xml editor or even programmatically. This and that blog posts describe label format in great details. To load label layout use one of Framework.openLabel() methods. Usually we will load the layout from an asset. Something like this:

    Label label = framework_.openLabel(this, "Address.label");

This assumes, that the Android project  has “Address.label” file in the assets folder.

image

Set Label Data

Label Layout can already contain data to be printed, e.g. an address. this might be a case when a label is generated dynamically on the server. But usually we will need to set data programmatically based on user’s input. This can be done using two different methods.

The first method allows printing of one label only. Use Label.setObjectText(String objectName, String objectText) method to set text data of Address, Text, Barcode, and CircularText objects. Use setObjectImage(String objectName, Bitmap image) to set image data for Image objects.

The second method is more universal, and it allows printing multiple labels at once. It is implemented using a “label set” concept. Label set is similar to a recordset, a database table. Label set consist of a set of “label records”. Each label record represents data to be printed on one label. Label record itself is conceptually is a dictionary (associative array), where dictionary keys are label object names, and dictionary values are label object data. To manipulate a label set use LabelSet interface. Use addRecord method to add a record into a label set. To set object data in the record, use LabelSetRecord methods. There are methods to add text and image data similar to ones above. Also, there is way to specify formatted/styled text data where each character or line can have different formatting (font size and style). To create a LabelSet use Framework.createLabelSet() method.

public class MainActivity extends Activity
{
    void printWithLabelSet(Printer printer)
    {
        // open a label
        Label label = framework_.openLabel(this, "Address.label");

        // create a label set
        LabelSet labelSet = framework_.createLabelSet();

        // label #1
        LabelSetRecord record = labelSet.addRecord();
        record.setText("TEXT", "6x7=42");

        //label #2
        record = labelSet.addRecord();
        record.setTextMarkup("TEXT", "font family='Arial' size='36'>6x7=<b>42</b></font>");

        // print label
        label.print(printer, labelSet);
    }
}

Actual Printing

To start printing call Label.print() methods. There are several  overrides, the most generic one is print(Printer printer, PrintParams printParams, LabelSet labelSet)

We have to provide three parameters:

  • printer – printer to print the label on. The printer instance can be obtained from a list of available printers populated during printer discovering process described above. This is the only required parameter, all other can be omitted.
  • printParams – printing parameters, like number of copies, print quality, etc. This parameter can be null, in witch case defaults will be used.
  • labelSet – a label set contains data to be printed. If omitted, the data contained by label instance will be printed.

This method call is asynchronous as well. Right after all necessary data are assembled the method returns. Printing progress monitoring can be done using PrintJob instance returned by the print() method (see below).

Print Progress Monitoring

PrintJob instance returned by the Label.print() call can be used to monitor print progress. Periodically call getStatus() method to retrieve current status information. Note: currently only this “polling” schema is supported; in the future we might add “pushing” data from the printer when it status has changed. getStatus() is an async call as well; when the status data has been retrieved from the printer, the PrintJobListener method statusReceived(PrintJobStatusReceivedEvent event) is called. Here is an example:

public class MainActivity extends Activity
{
    private void printButtonClick()
    {
        try
        {
            //... open label, assemble print data, etc 

            // print with default parameters
            final PrintJob job = label.print(currentPrinter);

            final Runnable getStatus = new CheckedRunnable()
            {
                @Override
                public void run2()
                {
                    job.getStatus();
                }
            };

            // an executor service to ask for the status in the background 
            final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

            // set the listener to be called when the status received
            job.setOnStatusListener(new PrintJobListener()
            {

                @Override
                public void statusReceived(PrintJobStatusReceivedEvent event)
                {
                    try
                    {
                        final PrintJobStatus jobStatus = event.getPrintJobStatus();
                        final JobStatusId statusId = jobStatus.getStatusId();
                        final String statusMessage = jobStatus.getStatusMessage();
                        Log.i("job status", statusMessage);

                        switch (statusId)
                        {
                        case ProcessingError:
                            // bad - unrecoverable printing error, do not ask status again
                            break;

                        case Finished:
                            // done - printing is done, do not ask status again
                            break;

                        default:
                            // OK - printing is in progress, ask the status after one second delay
                            scheduler.schedule(getStatus, 1, TimeUnit.SECONDS);
                            break;
                        }

                    }
                    catch (Exception e)
                    {
                        handleException(e);
                    }
                }
            });

            // schedule first status request in a second
            scheduler.schedule(getStatus, 1, TimeUnit.SECONDS);

        } catch (Exception e)
        {
            handleException(e);
        }
    }
}

Setup an Eclipse Project to Use SDK

Add Jars into a classpath

To use classes from the SDK two jars must be added to a class path (both can be found in the libs folder). The first one is DymoLabelFramework.jar – it contains the SDK classes. The second one is jmdns.jar. It contains Bonjour implementation used for printer discovering. JmDNS is an open source implementation of Bonjour/multicast-DNS for Java. The version bundled with the SDK is JmDNS 3.4.0 and can be downloaded from http://sourceforge.net/projects/jmdns/

Check Android SDK Target Version

The DymoLabelFramework internally uses some classes available only starting from Android SDK API level 8 (Android 2.2, Froyo). Make sure the project targets Android 2.2 or later.

Set Proper Permissions

The SDK requires two permissions to be set in AndroidManifest.xml. The first is android.permission.INTERNET, so the SDK be communicate with the Proxy service. The second is android.permission.CHANGE_WIFI_MULTICAST_STATE that is required to be able to use Bonjour for printer discovering.

Note: samples applications use another one, android.permission.ACCESS_NETWORK_STATE. It is uses to determine Wi-Fi status and ask a user to turn Wi-Fi on if necessary because otherwise no printers can be discovered. It is strictly to be a little bit more user-friendly, and is NOT required.

Setup an Ant Build Script to Use SDK

Use Android SDK tools to generate/update initial build.xml file.

Check Android SDK target version in default.properties file, so target=android-8 (or later).

Specify path where the jars are located in build.properties by using jar.libs.dir  property. E.g. for the sample projects jar.libs.dir=../../libs.

Note: to compile sample projects from the command line you have to specify path to the Android SDK in the local.properties file by using sdk.dir property.

Conclusion

DYMO Label Mobile SDK for Android provides a simple way to add label printing capabilities to any Android app. Along with DYMO Label Web SDK developers can use DYMO printers from their web-based applications or  from native apps.