Prime Table Generator in Jetpack Compose
Tags: android, kotlin, jetpackcompose
Published: 2021-03-25 17:11:09

It's been more than 6 years since I wrote an original version of this prime table generator. That was back at the very beginning of my coding career, after learning C and SDL in the first semester of university. An archived version of that original project [is available here](https://zsmbprogsite.netlify.app/projects/primetable/), including the sources, [120 lines of excellent C code](https://zsmbprogsite.netlify.app/projects/primetable/primetable.c).

### Premise

Recapping from the article linked above briefly: the point of this project is to create a visually pleasing and concise representation of prime numbers. The original, on-paper version contained prime numbers up to 4000, and looked like this:

![The full table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4h9n2951jxkr9wf0rge7.jpg)

How does it work? Each square represents a block of ten numbers. Since primes (above 2) may only end on the digits 1, 3, 7, or 9, each corner of the square can indicate whether or not a given ending digit is a prime within the 10 number wide block.

![Example of the first few squares of the table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/97io2wvnzx72mbb6xgqv.png)

As an example, the third block of the table corresponds to the numbers 21-30, and the two connected corners indicate that only 23 and 29 are primes within this range.

Now, let's get to coding this for Android!

> You can find the code for the completed project [on GitHub](https://github.com/zsmb13/PrimeTableGenerator).

### Creating a grid

In [the previous Jetpack Compose article on this blog](https://zsmb.co/compose-o-clock/), we created an animated clock with a bottom-up approach. This time, we'll design things top-down, and start with rendering a grid in Compose. For this, we'll use the experimental [`LazyVerticalGrid`](https://developer.android.com/jetpack/compose/lists#grids) APIs.

```kotlin
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Primes() {
    LazyVerticalGrid(
        modifier = Modifier // 1
            .fillMaxSize()
            .background(Color(0xFFE53935))
            .padding(8.dp),
        cells = GridCells.Fixed(10), // 2
    ) {
        items(count = 100) { // 3
            Box( 
                Modifier // 4
                    .aspectRatio(1f)
                    .padding(1.dp)
                    .background(Color.DarkGray)
            )
        }
    }
}
```

Breaking down the code above:

1. The `LazyVerticalGrid` composable fills the entire screen, has a red background, and a small bit of padding.
2. It displays a grid with a fixed number of columns.
3. The grid contains 100 items.
4. Each item is a simple `Box` for a start, which is constrained to be a square shape, has a bit of padding, and a dark background colour.

> Note how we had to opt-in to using the experimental API with the `@OptIn` annotation, which also requires some additional project-level configuration to enable it. You can read more about this language feature in [Mastering API Visibility in Kotlin](https://zsmb.co/mastering-api-visibility-in-kotlin/#opt-in-apis).

Running the code above renders the (scrollable) grid of squares:

![A grid of dark squares](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1iexp8vihbbotdy50eyd.png)

### A single square

Let's refactor this a bit, and create a `PrimeSquare` composable for each item of the grid. This will receive the current offset that it should render prime numbers for.

```kotlin
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Primes() {
    LazyVerticalGrid(...) {
        items(count = 100) { index ->
            PrimeSquare(offset = index * 10)
        }
    }
}

@Composable
fun PrimeSquare(offset: Int) {
    Box(
        Modifier
            .aspectRatio(1f)
            .padding(1.dp)
            .background(Color.DarkGray)
    ) {
        CornerLine()
    }
}
```

For the content of a single `PrimeSquare`, we'll render just one line from the top left corner to the center, with our own `CornerLine` composable. We can do this in Compose using the `Canvas` API:

```kotlin
@Composable
fun CornerLine() {
    Canvas(Modifier.fillMaxSize()) {
        drawLine(
            color = Color.White,
            start = Offset.Zero,
            end = Offset(size.width / 2, size.height / 2),
            strokeWidth = 2.dp.toPx(),
        )
    }
}
```

This gives us the following look - a good start!

![Prime squares with lines all in the top left corner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p7d3lv68xb08rlfvw02r.png)

### Rotating and stacking squares

To get this line into the correct corner, we can rotate the canvas while drawing on it. A simple `rotate` function call takes care of this for us. We'll take the rotation amount as a parameter to `CornerLine`.

```kotlin
@Composable
fun CornerLine(degrees: Float) {
    Canvas(Modifier.fillMaxSize()) {
        rotate(degrees) {
            drawLine(
                color = Color.White,
                start = Offset.Zero,
                end = Offset(size.width / 2, size.height / 2),
                strokeWidth = 2.dp.toPx(),
            )
        }
    }
}
```

To make things super easy, we'll create a named composable for each corner, with the appropriate rotation:

```kotlin
@Composable fun One() = CornerLine(degrees = 0f)
@Composable fun Three() = CornerLine(degrees = -90f)
@Composable fun Seven() = CornerLine(degrees = -180f)
@Composable fun Nine() = CornerLine(degrees = -270f)
```

We'll have to know which number is a prime, for this we'll go with a very basic implementation.

```kotlin
fun Int.isPrime(): Boolean {
    if (this < 2) return false
    return (2 until this).none { this % it == 0 }
}
```

> Have a shorter implementation for this that's at least as correct for checking primes? [Tweet it at me!](https://twitter.com/zsmb13)

Now that we can check whether a number's a prime and can draw lines into each corner, we can implement `PrimeSquare` trivially:

```kotlin
@Composable
fun PrimeSquare(offset: Int) {
    Box(
        Modifier
            .aspectRatio(1f)
            .padding(1.dp)
            .background(Color.DarkGray)
    ) {
        if ((offset + 1).isPrime()) One()
        if ((offset + 3).isPrime()) Three()
        if ((offset + 7).isPrime()) Seven()
        if ((offset + 9).isPrime()) Nine()
    }
}
```

Of course, the way we're stacking Canvases here is not exactly optimal, but it's a good demonstration of how a `Box` works as a container. If we moved the prime calculations a level lower, we could draw all our lines on a single Canvas for better performance - try doing this as a practice exercise.

Still, our non-optimal implementation works well:

![A working implementation of the prime table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xnfoijmvx2ke83g4le4z.png)

### Final touches

There are two issues left here in our rendering, which you can spot if you look closely at the image above.
- The ends of the lines drawn extend beyond the grey boxes.
- The lines meeting in the middle don't meet as expected.

For the first issue, we can make the `Box` in the `PrimeSquare` composable _clip_ to its bounds:

```kotlin
Box(
    Modifier
        .aspectRatio(1f)
        .padding(1.dp)
        .background(Color.DarkGray)
        .clipToBounds()
) { ... }
```

To make the lines overlap more in the middle, we can draw them just ever so slightly longer - `2f` seems to do the trick:

```kotlin
end = Offset(size.width / 2 + 2f, size.height / 2 + 2f),
```

Running the app again gives us our final result.

![The final version of the prime table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1sl4dpsvily4srgx6lc9.png)

### Conclusion

Again, the completed source for this project is available [on GitHub](https://github.com/zsmb13/PrimeTableGenerator).

If you're looking for more similar Compose content, check out these articles:

- [Compose O'Clock](https://zsmb.co/compose-o-clock/)
- [Jetpack Compose: First Impressions and Learning Resources](https://dev.to/zsmb13/jetpack-compose-first-impressions-and-learning-resources-3ghp)
- [Build an Android Chat app with Jetpack Compose](https://proandroiddev.com/android-chat-app-jetpack-compose-dec472140ff1)