Android SharedPreferences Tutorial and Example

Sending data with Bluetooth on Android

Bluetooth lets an Android application exchange messages, sensor readings or small files without a server or cable. The short-range wireless link may join two phones, a phone and a peripheral, or nearby tablets, so the transport and protocol must match the data.

A dependable implementation handles permissions, discovery, pairing, connection state, fragmented reads and disconnects. Classic RFCOMM provides a practical first project, while Bluetooth Low Energy requires a GATT-specific design with stricter packet-size limits.

Choosing a Bluetooth mode

Classic Bluetooth uses RFCOMM, a serial-like profile suited to chat messages, configuration payloads and modest files. It offers a continuous byte stream, although discovery and pairing can involve more overhead than newer low-energy devices.

Bluetooth Low Energy, usually called BLE, uses GATT services and characteristics. It works well for small packets, sensor notifications and accessories that need low power consumption, but larger transfers require careful chunking.

Choose based on payload size, power limits and both ends of the connection. At a crowded event beside Sydney Harbour, dozens of discoverable devices can slow discovery and cause radio interference, so filter by the expected device name or address where the platform permits it.

Preparing permissions and pairing

Android 12 and newer require runtime BLUETOOTH_SCAN for discovery and BLUETOOTH_CONNECT for pairing or connecting. An app that advertises itself also needs BLUETOOTH_ADVERTISE. Declare all permissions in the manifest and request only those relevant to the current user action.

Android 6 through 11 normally use BLUETOOTH and BLUETOOTH_ADMIN, while discovery may also require location permission and enabled location services. If scan results are used to infer physical location, declare that purpose and request location access; otherwise, avoid collecting it.

createBond() merely starts the system pairing dialog. Listen for ACTION_BOND_STATE_CHANGED, wait until the device becomes bonded, and then create the communication channel. Keep device identifiers in app storage rather than source code, because a bonded device may still be unavailable or need a fresh connection.

Building a reliable connection

A paired device can be opened with a secure RFCOMM socket. Run discovery cancellation, socket creation and connect() away from the main thread, because each operation can block. This example assumes the peripheral exposes the Serial Port Profile UUID:

suspend fun openSocket(macAddress: String): BluetoothSocket =
    withContext(Dispatchers.IO) {
        val manager = getSystemService(BluetoothManager::class.java)
        val adapter = manager?.adapter ?: error("Bluetooth unavailable")
        val device = adapter.getRemoteDevice(macAddress)
        val spp = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")

        adapter.cancelDiscovery()
        val socket = device.createRfcommSocketToServiceRecord(spp)

        try {
            socket.connect()
            socket
        } catch (error: IOException) {
            socket.close()
            throw error
        }
    }

Replace the UUID when a peripheral uses a custom service, and do not use an insecure socket variant without a deliberate threat assessment. Close the socket and both streams in a finally block, and expose connection state to the rest of the app rather than allowing the activity to own the socket.

Defining and transferring data

Treat the stream as bytes, not as complete messages. read() may return only part of a frame, while write() does not guarantee that a peer has processed the data. Add a marker, message type, payload length and optional checksum so the receiver can validate each packet.

A simple text frame can use a two-byte length and UTF-8 payload:

val body = message.toByteArray(Charsets.UTF_8)
require(body.size <= 65_535)

val frame = byteArrayOf(
    0xA5,
    0x01,
    (body.size ushr 8).toByte(),
    body.size.toByte()
) + body

outputStream.write(frame)
outputStream.flush()

The receiver must accumulate bytes until the header and full payload have arrived. BLE follows a different model: writes are limited by the negotiated ATT MTU, commonly leaving 20 bytes per operation before extension, and the peripheral usually returns data through characteristic notifications. Respect those limits and reassemble messages above them.

Updating the user interface

Never read or write a socket on the main thread. Publish states such as disconnected, connecting, connected and failed through StateFlow or LiveData, then let the activity observe them. Disable the send control while disconnected, but keep cancellation available when a connection attempt is slow.

In a scrolling conversation view, the site's guide to vertical scrollbar styling helps users judge how much message history is visible. Status copy can use friendly Australian phrasing such as “No worries, try again” for a non-critical reconnect prompt, while technical failures remain in logs for diagnosis.

Handling failures and Australian conditions

Treat disconnects as normal radio events. Catch IOException and SecurityException, release resources, and retry with bounded backoff rather than creating sockets in a tight loop. Also prevent stale discovery and connection jobs from updating the screen after a newer request has started.

In the Red Centre around Alice Springs, mobile coverage can be weak, but a local Bluetooth link can continue working without internet access. Its range still depends on obstacles, antenna design and power, so test the actual distance needed by field staff rather than relying on a theoretical maximum.

A mining crew outside Kalgoorlie may use rugged Android tablets to collect readings from equipment across a large worksite. Reconnection logic, visible connection state and a clear rescan action matter more there than assuming every device will remain paired indefinitely.

If the payload contains personal information, apply the Privacy Act 1988 and the Australian Privacy Principles when relevant. Collect only necessary fields, obtain clear consent, encrypt sensitive data at rest and in transit, and keep Bluetooth discovery limited to the period when the user starts a transfer.

Comparing options and validating the release

The transport should follow the payload, battery budget and required distance. Classic mode is straightforward for moderate data on established devices; BLE is efficient for small packets; Bluetooth 5 Long Range can suit outdoor sensors where compatible hardware is already available.

Mode Typical use Power profile Main strength Main limitation
Classic RFCOMM Messages and modest files Moderate while connected Simple stream model Discovery overhead and blocking I/O
BLE GATT Sensor packets and notifications Low Efficient small transfers MTU limits and device-specific GATT behaviour
Bluetooth 5 LE Long Range Outdoor or industrial sensors Low Greater potential distance Hardware support and variable radio conditions

Test Android 12 or newer alongside every earlier release your app supports. Exercise denied permissions, removed pairing, rotation, backgrounding, partial reads, oversized payloads and sudden disconnects. Field testing around Alice Springs should use realistic obstructions and battery levels rather than a device parked beside the computer.

Australian Android releases normally reach users through Google Play, where compatibility matters across current Samsung Galaxy phones, Google Pixel devices and lower-cost tablets sold locally. Meeting current target API requirements and verifying pairing and transfer speed on representative hardware keeps the release aligned with that market.