Back to projects
Take-home

Plate Calculator

A native Android rebuild of the plate calculator from myworkoutlogs.app. Enter a target weight and it tells you exactly which plates to load on each side of the bar.

Status
Complete
Role
Solo
Stack
Java, Android SDK, Material Components
View on GitHub

The problem

Loading a barbell is a small math problem you solve between sets: subtract the bar and collars, split the rest in half, and figure out which plates add up to that number. It gets harder when you don't own every plate size, and sometimes the exact weight isn't possible at all.

The calculator handles both cases. When the target can be hit exactly, it shows the plate combination per side. When it can't, it finds the closest weight you can actually load. It supports lb and kg plate sets, configurable bar and collar weight, and toggling which plates you have on hand.

How this project started

I reached out to the developer of myworkoutlogs.app, a solo indie app, about coding opportunities. They gave me an informal take-home assignment: rebuild the app's plate calculator natively on Android, matching the app's visual design while using native Android UI components. I built it on my own in Java and published the code publicly.

How it works

The calculation logic is completely separate from the UI. The Android layer collects the inputs (target weight, bar, collars, available plates) and hands them to a plain Java class that returns the result. That separation is what made the logic easy to unit test, since the tests never have to touch a screen.

For the UI side, there is a simple input box that takes a number as the weight. From there you get shown the Plate Calculator which shows available plates as clickable options to tell the app which plates you have available and a bar type, plate type, and collars drop down. All of these options are visually represented as different plates, bars, and collars in the plate calculator display.

Why I switched from greedy to dynamic programming

My first version was greedy: always grab the heaviest plate that still fits, then repeat. It's simple and it's right most of the time, but it has gaps when only certain plates are available.

For example, say you need 60 lb per side and you only have one 45, one 35, and one 25. Greedy grabs the 45 first, leaves 15 lb that nothing fits, and stops at 45. The right answer is 35 + 25 = 60. Greedy can't see that, because it commits to the biggest plate before checking what else is possible.

Dynamic programming fixes this by working out every weight that's reachable with the available plates, then picking the reachable weight closest to the target. It's guaranteed to find the true closest answer in every configuration, not just most of them.

// rebuilding the plate list from the DP table:
    int cursor = best;
    while (cursor > 0) {
        int pw = lastPlateScaled[cursor];
        double weight = pw / (double) scale;
        result.platesPerSide.merge(weight, 1, Integer::sum);
        cursor -= pw;
    }

What I'd do differently

I think for another project like this take-home, I would ask for more starting material. It was a bit challenging to figure out the color scheme and how some of the UI elements hand data off to the logic. I was especially unsure about how exactly their algorithm was supposed to work and handle edge cases in the weights. Instead, I tried to mimic the original implementation as closely as possible and had to get creative with the logic, which is why I switched from the greedy approach to a dynamic programming approach.

Next time I would send a short list of questions before getting started. Such as:

Back to projects