Lawrence Gimenez

Bluetooth printer scanning using Kotlin

One of the requirements of my current project is to be able to establish Bluetooth connection and send texts to print.

See the video below for the result.

Let's create an empty Android project and let's call it BluetoothPrintingSample. Then, create a layout folder inside res folder. Let's create a simple layout called activity_print.xml.

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingStart="44dp"
    android:paddingEnd="44dp"
    android:background="@color/design_default_color_secondary"
    >
    <EditText
        android:id="@+id/editTextPrintMessage"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:singleLine="false"
        android:background="@android:color/white"
        android:textColor="@android:color/black"
        android:layout_marginTop="30dp"
        android:layout_centerHorizontal="true"
        />
    <Button
        android:id="@+id/buttonPrintMe"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Print Me"
        android:backgroundTint="@color/teal_700"
        android:layout_below="@+id/editTextPrintMessage"
        />
</RelativeLayout>

Open your build.gradle (app) file and enable the ViewBinding feature.

buildFeatures {
    viewBinding true
}

Let's create our activity. It should look like the one below.

package me.lwgmnz.bluetoothprintingsample

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import me.lwgmnz.bluetoothprintingsample.databinding.ActivityPrintBinding

class PrintActivity : AppCompatActivity() {

    private lateinit var binding: ActivityPrintBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityPrintBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
    }
}

Now the permissions. Open your AndroidManifest.xml and add the following permissions below. Take note, to include the permission ACCESS_COARSE_LOCATION.

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

In your PrintActivity, add the runtime permission.

if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), COARSE_LOCATION_REQUEST_CODE)
        }

For newer devices, you also might need to add the runtime Bluetooth permission. But for my current project the app is running on Android 8.1 and Bluetooth permissions will work fine when added only on AndroidManifest.xml.

If Bluetooth is not yet enabled, we should enable it.

private var bluetoothAdapter: BluetoothAdapter? = null

override fun onCreate() {
    super.onCreate()
    bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
    if (bluetoothAdapter != null) {
        if (bluetoothAdapter!!.isEnabled) {
            bluetoothAdapter!!.startDiscovery()
            // Listen for broadcast
            val filter = IntentFilter(BluetoothDevice.ACTION_FOUND)
            registerReceiver(bluetoothScanBroadcastReceiver, filter)
         } else {
            // Enable Bluetooth
            val intentBluetooth = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
            startActivityForResult(intentBluetooth, Keys.BLUETOOTH_REQUEST_CODE)
         }
    } else {
        Snackbar.make(binding.root, "Bluetooth Not Supported", Snackbar.LENGTH_SHORT).show()
    }
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == Keys.BLUETOOTH_REQUEST_CODE) {
        if (resultCode == RESULT_OK) {
            initBluetooth()
        }
    }
    super.onActivityResult(requestCode, resultCode, data)
}

Add a BroadcastReceiver so we can receive whenever our app discovers Bluetooth devices when scanning. The device name of my Bluetooth printer is Bluetooth Printer so as you can see I purposely check for the specific name to save resources.

Also take note of the UUID, you should use that specific UUID as UUID.randomString() won't work.

private var bluetoothSocket: BluetoothSocket? = null
private var bluetoothAdapter: BluetoothAdapter? = null
private var outputStream: OutputStream? = null
private val bluetoothScanBroadcastReceiver = object : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            if (intent.action == BluetoothDevice.ACTION_FOUND) {
                val bluetoothDevice = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
                if (bluetoothDevice != null) {
                    if (bluetoothDevice.name == "BlueTooth Printer") {
                        val uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
                        bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(uuid)
                        bluetoothAdapter!!.cancelDiscovery()
                        try {
                            bluetoothSocket!!.connect()
                            outputStream = bluetoothSocket!!.outputStream
                        } catch (exception: IOException) {
                            Log.e("SignInActivity", "IOException: $exception")
                        }
                    }
                }
            }
        }
    }

Add our BroadcastReceiver to its proper Activity lifecycles.

override fun onResume() {
    super.onResume()
    val filter = IntentFilter(BluetoothDevice.ACTION_FOUND)
    registerReceiver(bluetoothScanBroadcastReceiver, filter)
}

override fun onPause() {
    super.onPause()
    unregisterReceiver(bluetoothScanBroadcastReceiver)
}

Time to print. Back to our PrintActivity let's add the lines below

override fun onCreate() {
    super.onCreate()
    binding.buttonPrintMe.setOnClickListener(this)
}

override fun onClick(view: View) {
    if (view.id == binding.buttonPrintMe.id) {
        if (outputStream != null) {
            outputStream!!.write(binding.editTextPrintMessage.text.toString().toByteArray())
        }
    }
}

It should print out the words you entered on the EditText.

That's it! Let me know if you have any questions. You can email me at yo@lwgmnz.me and let me know what you think.