Back to projects

BouldR

A native Android app for climbers who want to log routes, track progress and review their sessions. Built during a mobile development course at Umeå University.

  • Kotlin
  • Jetpack Compose
  • Room
  • CameraX
  • MVVM
Role
Developer & Designer
Timeline
Spring 2025
Type
Course project

Problem

The brief was to build a native Android app with a local database and camera integration. I wanted something I'd actually use, so I built a route tracker for climbers. Whether you're a beginner documenting your first sends or an experienced climber keeping a project list, BouldR gives you a simple way to log what you've done and what you're still working on.

The application

BouldR is built entirely in Kotlin with Jetpack Compose. The app centers on a single data model, a route, which carries a photo, a name, a difficulty grade, personal notes, a creation date, and a flag for whether it's been completed.

Routes are stored locally using Room, with a DAO layer handling all database operations. The architecture follows MVVM. A single ViewModel manages all route state and exposes derived values for completed count, planned count, hardest grade, and recent activity. These update reactively across the UI.

Empty home screen showing zero completed boulders, zero planned, and a dash for hardest grade, with a prompt to add the first route.
Home — empty state
Home screen showing one completed boulder, one planned route with hardest grade 3c, and a recent activity list with The Slab as planned and Warmup as completed.
Home — with routes
Routes screen divided into Planerade and Avklarade sections, showing The Slab as planned and Warmup as completed, each with a thumbnail, grade, date and status badge.
Routes
Route detail screen for The Slab showing a photo of yellow holds, name, grade 5c, notes, a mark as completed checkbox, a save changes button and a delete route option.
Route detail

Adding a route

Adding a new route starts from the dedicated add screen in the navigation bar. The user either takes a photo in-app using the CameraX-powered camera view, or uploads one from their gallery. Both paths prompt for the relevant permission the first time.

Once a photo is selected it appears in the form alongside fields for name, grade, and notes. The user can also mark the route as completed before saving, which is useful for logging routes after the fact.

New route form with an empty photo placeholder, Ta foto and Ladda upp buttons, and empty fields for route name, grade, notes and a mark as completed checkbox.
Add route — empty
Full-screen camera view of a bouldering wall with yellow holds, with a cancel button top left and a take photo button at the bottom.
Camera
New route form filled in with a photo of yellow holds, route name The Slab, grade 5c, and notes reading Ser ut att vara en klurig start.
Add route — filled in

Planned and completed routes

Routes live in one of two states: planned or completed. The routes screen shows both in separate sections, each entry showing a thumbnail, grade, date and a status badge. Tapping a route opens the detail screen where the user can edit any field, mark it as completed, or delete it. Marking it as completed moves it to the completed section and updates the home screen counters right away.

Completed routes can be moved back to planned from the same screen, in case something was marked by mistake.

Routes screen in empty state showing no planned and no completed routes.
Routes — empty state
Routes screen with one planned route and one completed route, each showing a thumbnail, grade, date and status badge.
Routes — with entries
Route detail screen for The Slab showing a photo of yellow holds, name, grade 5c, notes, a mark as completed checkbox, a save changes button and a delete route option.
Route detail

Technical decisions

All data stays on the device.

There's no backend and no user accounts. Everything is stored locally using Room. This kept the scope realistic for a course project and made sense for the use case. A personal training log doesn't need to live anywhere other than your own phone. The settings screen exposes a single destructive option: clear all data.

CameraX over the standard camera intent.

Using CameraX instead of delegating to the system camera allowed for a fully custom camera UI embedded within the app. Photos taken in-app and images uploaded from the gallery are both copied to internal storage with timestamp-based filenames. This keeps the URI stable even if the source file is later moved or deleted.

ViewModelFactory for correct lifecycle handling.

A PlannedRouteViewModelFactory is used to construct the ViewModel with the DAO injected, rather than instantiating it directly. This ensures the ViewModel survives configuration changes like screen rotation and that the DAO dependency is wired up correctly.

Revisiting the UI

The original UI wasn't really designed. It was quickly put together to get the flow working, which was the point of the assignment.

About a year later, revisiting the project for my portfolio gave me a reason to go back and build the interface properly. I rebuilt it from scratch: a home screen with stat cards for completed routes, planned count, and hardest grade, a cleaned-up navigation bar, and consistent components across all screens. The core logic stayed intact.

What I'd add next

The most useful addition would be filtering. Being able to sort routes by grade, date, or status would make the list easier to navigate as it grows. I'd also like to add the ability to draw or annotate directly on a route photo, which would make planning beta more practical. Landscape support was started on the navigation bar as a proof of concept but wasn't prioritized since the app is designed primarily for portrait use.

View on GitHub(opens in new tab)