Demo project

Barbershop booking app on a live API

A Flutter app wired to a real server: the price list, the free slots and the booking itself travel over the network, not out of stubs.

mobile app and its client API · Flutter · Dart · REST API · Go

5 screensfrom the price list to rescheduling
8 servicespriced by the server, not hardcoded
3 stepsfrom picking a service to booked
7 failuressorted in code, shown as sentences
iOS + Androidone codebase, both platforms
Demo first screen: Barbershop booking app on a live API

The task

BROMID is a barbershop in Yekaterinburg: four barbers, eight items on the price list, appointment only. A demo project - the studio, the barbers, the prices and the bookings are invented, the job is real and ordinary for the trade. Someone commissioning a mobile app is usually shown handsome screens filled with made-up data, and they have no way to tell a working app from a mockup that only scrolls.

So this demo has exactly one job: an app that talks to a real server. Neither the price list nor the schedule lives in the app’s code: services, prices, durations, free slots and the bookings themselves arrive over the network.

The solution

Five screens and three steps to a booking: pick a service, pick a barber and a time, confirm. Then the “You’re booked” card - service, time, barber, price, studio address, and buttons to reschedule or cancel.

  • The price list is set like a menu: name, dotted leader, price, duration underneath. All eight items come from the server’s catalogue, prices and minutes included.
  • Free time depends on the service chosen, because the server does the arithmetic: switching from a haircut to the combo - 45 minutes to 90 - visibly thins the grid.
  • The first tile in the barber strip is “Any barber, whoever is free first”. Guests usually pick a time rather than a person, so the server chooses who takes them.
  • Booking, rescheduling and cancelling are real: seven API endpoints, from opening a session to moving an appointment. Cancelling asks for confirmation in its own dialog instead of firing on the first tap.
  • The client API was written for this app: its own /api/app prefix, its own token, responses that carry no other guest’s name or phone number. The studio’s dashboard API is deliberately off limits to the app - it holds every client’s contact details, and those have no business in a screenshot.

Details that are easy to miss

  • Times are shown in the studio’s time zone, not the phone’s. The server sends the offset with each timestamp and the app reads it from there: a guest in another zone sees the studio’s 2:30 pm, not their own local hour.
  • A slot can be taken between the moment the screen opens and the moment the button is pressed. The server refuses, the app says “That time has just been taken. Please pick another” and turns the button into “Pick another time”, dropping the guest back onto a refreshed grid.
  • Seven kinds of failure are told apart in code: connection lost, a 12-second timeout, an expired session, a taken slot, a booking that no longer exists, a server refusal, a malformed response. The wording also depends on what the guest was doing: a lost connection on the price list reads “Could not reach the studio”, the same loss at the moment of booking reads “The connection dropped. The booking was not created”.
  • Neither a status code nor a technical string reaches the screen: a 500 is shown as “The studio isn’t answering. Please try later.”
  • An expired session does not throw the guest back to the start: a “Retry” button appears, and on that tap the app opens a new session and repeats the same action - on the confirmation screen, the very same booking.
  • While the price list is in flight there is no full-screen spinner: a skeleton of eight rows, exactly the number of items on the list, holds the layout so nothing jumps when the data lands.
  • The number of visits left before the free haircut is declined properly in Russian grammar - a detail whose absence is spotted at once.
  • Demo hygiene: a disclaimer line sits under the price list and a separate “About” screen spells it out, so no screenshot can pass for a real studio service.
  • Fonts ship inside the repository rather than being pulled from the network. Beyond Flutter itself the app has one runtime dependency: the HTTP client. There is no state-management library: screens switch between explicit idle, loading, loaded and failed states.

The limits of this demo

The scope is camera-ready, not store-ready: there will be no live link, and the app is not published to the App Store or Google Play. There is no web build either - the app picks the server address through dart:io, which a browser does not have. The interface is Russian only, with no localisation, so the lines quoted above are translations. The series of frames is shot on iOS; Android has one control screen so far. System font scaling is pinned in the demo so frames match between runs - a real store app must not do that.