DEV Community

Cover image for How to Render Text as a QR code in Jetpack Compose
Addy Godwin Nii
Addy Godwin Nii

Posted on

How to Render Text as a QR code in Jetpack Compose

A QR Code (quick repose code) is a type of barcode represented as a matrix of black and white squares. QR codes make it very convenient to represent text such as phone numbers, URLs and other information in an image format.

In Jetpack Compose (Google's new UI toolkit for android apps), it is very easy to create an instance of Painter which is used to render images and UI elements that can be drawn into a specified bounded area. There are number of Painter subclasses provided in compose by default but we will be focusing on the BitmapPainter.

Adding Zxing Core Dependency

The first step is to generate the QR code Bitmap image that will be rendered in our UI. To do this we will be using zxing-core library by Google. Zxing is an open-source barcode scanning libary for Java and Android. Add the dependency to the module level (in a simple project this would be app) build.gradle file:

dependencies {
    ...
    implementation "com.google.zxing:core:3.5.1"
}
Enter fullscreen mode Exit fullscreen mode

Defining the Painter function

Let's define a special type of composable function that returns a value and has a camelCase naming as opposed to our usual composable functions that emit UI with a PascalCase naming and return no value (Unit). This returns the BitmapPainter that will render the QR code when passed to our Image composable function:

@Composable
fun rememberQrBitmapPainter(
    content: String,
    size: Dp = 150.dp,
    padding: Dp = 0.dp
): BitmapPainter {
    return BitmapPainter()
}
Enter fullscreen mode Exit fullscreen mode

Our rememberQrBitmapPainter takes a content: String which represents the text value we want to encode in our QR code, size:Dp which represent the size(width x height) of our QR specified as a device-independent pixel value (to ensure we have a sharp image on different device with different pixel densities) and the padding:Dp represent the amount of white space border we want around the generated code. The padding is set to a default of 0 to override/remove the default padding that the zxing library adds when generating a QR bitmap.

Let's add 4 new variables to our function;
density to retrieve the current screen density information of the device using the LocalDensity composition local. The Density interface provides useful extension functions for performing simple operations like getting the device pixel equivalent of the .dp values we specified earlier.
The sizePx and paddingPx variables are used to represent the pixel equivalent of the size and padding respectively.
A bitmap mutable state variable to hold the bitmap of the QR code. In order to prevent our bitmap from being recalculated any time the UI is redrawn due to state changes (re-composition) we wrap it with a remember function with a key of content. The content key allows us to re-calculate the bitmap when the value our QR contains changes.

@Composable
fun rememberQrBitmapPainter(
    content: String,
    size: Dp = 150.dp,
    padding: Dp = 0.dp
): BitmapPainter {

    val density = LocalDensity.current
    val sizePx = with(density) { size.roundToPx() }
    val paddingPx = with(density) { padding.roundToPx() }

    var bitmap by remember(content) {
        mutableStateOf<Bitmap?>(null)
    }

    return BitmapPainter()
}
Enter fullscreen mode Exit fullscreen mode

The QR code is a matrix which consists of multiple pixels whose values if computed on the UI thread, can cause your UI to stutter or hang; for this reason we will make use of a LaunchedEffect and IO CoroutineDispatcher to offload the work required to compute the Bitmap of the QR code off the UI thread:

LaunchedEffect(bitmap) {
    if (bitmap != null) return@LaunchedEffect

    launch(Dispatchers.IO) {
       val qrCodeWriter = QRCodeWriter()

       val encodeHints = mutableMapOf<EncodeHintType, Any?>()
           .apply {
              this[EncodeHintType.MARGIN] = paddingPx
           }

       val bitmapMatrix = try {
           qrCodeWriter.encode(
              content, BarcodeFormat.QR_CODE,
              sizePx, sizePx, encodeHints
            )
       } catch (ex: WriterException) {
          null
       }
    }
}
Enter fullscreen mode Exit fullscreen mode

Here we create a QRCodeWriter instance which would be used to encode the QR. The encodeHints variable is a map of EncodeHintType to values which is used to configure the QRCodeWriter. A bitmapMatrix is created by encoding the content string and specifying the height and width of the QR.

We need to create a bitmap from the bitmapMatrix and set the color for each pixel (either white or black):

val matrixWidth = bitmapMatrix?.width ?: sizePx
val matrixHeight = bitmapMatrix?.height ?: sizePx

val newBitmap = Bitmap.createBitmap(
    bitmapMatrix?.width ?: sizePx,
    bitmapMatrix?.height ?: sizePx,
    Bitmap.Config.ARGB_8888,
)

for (x in 0 until matrixWidth) {
    for (y in 0 until matrixHeight) {
       val shouldColorPixel = bitmapMatrix?.get(x, y) ?: false
       val pixelColor = if (shouldColorPixel) Color.BLACK else Color.WHITE

       newBitmap.setPixel(x, y, pixelColor)
     }
}

bitmap = newBitmap
Enter fullscreen mode Exit fullscreen mode

In our final step we return an instance of BitmapPainter with the computed bitmap passed as an argument to it. If the bitmap is yet to be computed, we pass an empty bitmap of equal size with a transparent color. The bitmap is then converted to an image bitmap using the Bitmap.asImagePainter() extension function.

return remember(bitmap) {
   val currentBitmap = bitmap ?: Bitmap.createBitmap(
       sizePx, sizePx,
       Bitmap.Config.ARGB_8888,
   ).apply { eraseColor(Color.TRANSPARENT) }

   BitmapPainter(currentBitmap.asImageBitmap())
}
Enter fullscreen mode Exit fullscreen mode

How you would use it:

Image(
    painter = rememberQrBitmapPainter("https://dev.to"),
    contentDescription = "DEV Communit Code",
    contentScale = ContentScale.FillBounds,
    modifier = Modifier.size(135.dp),
)
Enter fullscreen mode Exit fullscreen mode

Putting everything together we get: QRPainter.kt

I hope you find this article useful and thanks for reading.

Top comments (0)