Print using Zebra QLn420 printer with andorid application in kotlin.

Himanshu Choudhary
5 min readJul 5, 2019
cover_image

In this post, we will be going to build an android application which will include the following :
1. Scanning for available Bluetooth devices.
2. Connecting to Zebra QLn420 printer.
3. Printing the provided template.

i. Scanning for available Bluetooth devices

private lateinit var listView: ListView
private val mDeviceList = ArrayList<String>()
private var mBluetoothAdapter: BluetoothAdapter? = null
private var bluetoothDeviceAddress = mutableListOf<String>()

We have to define certain variables which we will be going to use later in the program.

ListView -> To provide a list of names & address of nearby Bluetooth devices.
mDeviceList -> To store the names of visible Bluetooth devices.
mBluetoothAdapter -> This will let you perform fundamental Bluetooth tasks.
bluetoothDeviceAddress -> To store the addresses of visible Bluetooth devices.

In activity_main.xml file,

<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/listView"/>

In MainActivity — onCreate, we have to initialize the variables defined above

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

listView = findViewById(R.id.listView)

mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
mBluetoothAdapter?.startDiscovery()

val filter = IntentFilter(BluetoothDevice.ACTION_FOUND)
registerReceiver(mReceiver, filter)

listView.setOnItemClickListener { _, _, position, _ ->
printAction(bDeviceAddress = bluetoothDeviceAddress[position], content = content)
}
}

In this, I have used setOnItemClickListener because I know I’ll only click on the item showing my Zebra printer. You can add as many checks you want according to your preferences.

Then we have to have a broadcast receiver which will receive near by Bluetooth devices and will be defined as:

private val mReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.action
if (BluetoothDevice.ACTION_FOUND == action) {
val device = intent
.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
mDeviceList.add(device.name + "\n" + device.address)
bluetoothDeviceAddress.add(device.address)
Log.i("BT", device.name + "\n" + device.address)
listView.adapter = ArrayAdapter(
context,
android.R.layout.simple_list_item_1, mDeviceList
)
}
}
}

The device list is submitted to list view which results as

Bluetooth devices listing

We also have to unregister our receiver when our activity is destroyed.
In case you forget to unregister the receiver then it can result in more consumption of resources as the receiver is active in the background where you don’t want it to work.

override fun onDestroy() {
super.onDestroy()
unregisterReceiver(mReceiver)
}

We have initialised printAction() on list view item click where have to pass the bluetooth address of the printer and the string of the template you want to print.

This string is no ordinary string, it is known as ZPL String and looks like :

val content = "^XA\n" +
"^FX Second section with recipient address and permit information.\n" +
"^CFA,30\n" +
"^FO50,200^FDJohn Doe^FS\n" +
"^FO50,340^FD100 Main Street^FS\n" +
"^FO50,380^FDSpringfield TN 39021^FS\n" +
"^FO50,420^FDUnited States (USA)^FS\n" +
"^CFA,15\n" +
"^FO600,300^GB150,150,3^FS\n" +
"^FO638,340^FDPermit^FS\n" +
"^FO638,390^FD123456^FS\n" +
"^XZ"

This is actually a template with details in it. You can have you own template which you can pass and print.

private fun printAction(bDeviceAddress: String, content: String): ZebraPrinter? {
var printer: ZebraPrinter? = null
val printerConnection = BluetoothConnection(bDeviceAddress)
try {
printerConnection.open()
if (printer == null) {
printer = ZebraPrinterFactory.getInstance(PrinterLanguage.CPCL, printerConnection)
}
sendToPrint(printer!!, content)
printerConnection.close()
} catch (e: ConnectionException) {
Log.d("ERROR - ", e.message)
} finally {

}
return printer
}

In this function, we are setting up a bluetooth connection with the printer and we executing this code in try-catch block as there may be error while setting up the connection.

The instance of the printer and the ZPL string are then passed to another function named as ‘sendToPrint()’ to execute further steps.

private fun sendToPrint(printer: ZebraPrinter, content: String) {
try {
val filepath = getFileStreamPath("TEMP.LBL")
createFile("TEMP.LBL", content)
printer.sendFileContents(filepath.absolutePath)
} catch (e1: ConnectionException) {
Log.d("ERROR - ", "Error sending file to printer")
} catch (e: IOException) {
Log.d("ERROR - ", "Error creating file")
}

}

In sendToPrint, we have created a file with extension .LBL in our local phone storage. The createFile() is used to create that file and then the path of the saved file is passed to the printer which will result in performing print operation.

@Throws(IOException::class)
private fun createFile(fileName: String, content: String) {
val os = this.openFileOutput(fileName, Context.MODE_APPEND)
os.write(content.toByteArray())
os.flush()
os.close()
}

The whole work is executing on list view setOnItemClickListener(). You can modify the workflow according to your usage.

Full code:

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.ArrayAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothAdapter
import android.content.Intent
import android.content.BroadcastReceiver
import android.content.Context
import android.util.Log
import android.widget.ListView
import android.content.IntentFilter
import com.zebra.sdk.comm.BluetoothConnection
import com.zebra.sdk.comm.ConnectionException
import com.zebra.sdk.printer.PrinterLanguage
import com.zebra.sdk.printer.ZebraPrinter
import com.zebra.sdk.printer.ZebraPrinterFactory
import java.io.IOException


class MainActivity : AppCompatActivity() {

private lateinit var listView: ListView
private val mDeviceList = ArrayList<String>()
private var mBluetoothAdapter: BluetoothAdapter? = null
private var bluetoothDeviceAddress = mutableListOf<String>()
val content = "^XA\n" +
"^FX Second section with recipient address and permit information.\n" +
"^CFA,30\n" +
"^FO50,200^FDJohn Doe^FS\n" +
"^FO50,340^FD100 Main Street^FS\n" +
"^FO50,380^FDSpringfield TN 39021^FS\n" +
"^FO50,420^FDUnited States (USA)^FS\n" +
"^CFA,15\n" +
"^FO600,300^GB150,150,3^FS\n" +
"^FO638,340^FDPermit^FS\n" +
"^FO638,390^FD123456^FS\n" +
"^XZ"

private val mReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.action
if (BluetoothDevice.ACTION_FOUND == action) {
val device = intent
.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
mDeviceList.add(device.name + "\n" + device.address)
bluetoothDeviceAddress.add(device.address)
Log.i("BT", device.name + "\n" + device.address)
listView.adapter = ArrayAdapter(
context,
android.R.layout.simple_list_item_1, mDeviceList
)
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

listView = findViewById(R.id.listView)

mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
mBluetoothAdapter?.startDiscovery()

val filter = IntentFilter(BluetoothDevice.ACTION_FOUND)
registerReceiver(mReceiver, filter)

listView.setOnItemClickListener { _, _, position, _ ->
print(bDeviceAddress = bluetoothDeviceAddress[position], content = content)
}
}

override fun onDestroy() {
super.onDestroy()
unregisterReceiver(mReceiver)
}

private fun print(bDeviceAddress: String, content: String): ZebraPrinter? {
var printer: ZebraPrinter? = null
val printerConnection = BluetoothConnection(bDeviceAddress)
try {
printerConnection.open()
if (printer == null) {
printer = ZebraPrinterFactory.getInstance(PrinterLanguage.CPCL, printerConnection)
}
sendToPrint(printer!!, content)
printerConnection.close()
} catch (e: ConnectionException) {
Log.d("ERROR - ", e.message)
} finally {

}
return printer
}

private fun sendToPrint(printer: ZebraPrinter, content: String) {
try {
val filepath = getFileStreamPath("TEMP.LBL")
createFile("TEMP.LBL", content)
printer.sendFileContents(filepath.absolutePath)
} catch (e1: ConnectionException) {
Log.d("ERROR - ", "Error sending file to printer")
} catch (e: IOException) {
Log.d("ERROR - ", "Error creating file")
}

}

@Throws(IOException::class)
private fun createFile(fileName: String, content: String) {
val os = this.openFileOutput(fileName, Context.MODE_APPEND)
os.write(content.toByteArray())
os.flush()
os.close()
}
}

A document shared by Nuri Şahin

link -https://www.zebra.com/content/dam/zebra_new_ia/en-us/solutions-verticals/product/Software/Printer%20Software/Link-OS/print-connect/PC-UserGuide-P1082444-001.pdf

I hope this article would have helped you. In case of any suggestions or queries please do comment.

--

--