How to Render Text as a QR code in Jetpack Compose
Tags: android, jetpack, compose
Published: 2023-01-08 07:46:48

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](https://developer.android.com/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`](https://mvnrepository.com/artifact/com.google.zxing/core/3.5.1) library by Google. [Zxing](https://github.com/zxing/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"
}
```

#### 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()
}
```

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()
}
```

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
       }
    }
}
```

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
```

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())
}
```
How you would use it:

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

Putting everything together we get: [QRPainter.kt](https://gist.github.com/dev-niiaddy/8f936062291e3d328c7d10bb644273d0)

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