Skip to main content
← Back to Portfolio Mobile App

Vending Machine — Smart Vending Machine Control App

An Android companion app for a vending machine controller: browse the product catalogue, pay for a slot, and let the app drive the physical machine over USB serial.

Mobile App Android UI/UX Design Kotlin IoT USB Serial

Client

Vending Machine

Year

2024

Role

Android engineering & UI design

Duration

2024

Vending Machine — Smart Vending Machine Control App
Screenshot from Vending Machine — Smart Vending Machine Control App

The Challenge

A vending machine is an unattended machine. Whatever happens on the phone has to end in a physical action - a motor turning, a slot opening - and it has to keep working when the shop Wi-Fi is down. The team needed a companion app that could show a real catalogue, take the order, and still drive the controller when the network was gone, instead of a screen that only worked as long as three image URLs were alive.

The Approach

The app was split into three layers so the hardware never leaks into the UI. The presentation layer holds the catalogue screen and the payment screen; the domain layer holds the use cases and the models, including an explicit order status machine; the data layer holds the repository implementation, the Retrofit/Moshi service, and a serial link that owns the USB connection. The catalogue is served from a local list with the remote fetch kept one line away, because a machine that stops selling when the network hiccups is a machine that loses money. Payment is modelled as an order that walks through CREATED, PAID, DISPENSING and COMPLETED, and the dispense command is written to the controller at 9600 8N1 only once the order reaches DISPENSING - the customer is never told the product is on its way before payment has actually settled.

What Was Built

12 · 5 with screenshots

Screenshot from the running project.

Vending Machine — Smart Vending Machine Control App — Product Catalogue Grid
01

Product Catalogue Grid

The app opens on the machine catalogue: four products on a two-column grid, each card showing the product image, its name, and the price that will be charged. The search field and the filter icon sit right above the grid, where a thumb naturally lands.

Screenshot from the running project
Vending Machine — Smart Vending Machine Control App — Live Catalogue Search
02

Live Catalogue Search

The search field filters the catalogue as you type, matching on product name and description. It was a dead control in the original build; it is now wired to the cached catalogue, and clearing the field restores the full list.

Screenshot from the running project
Vending Machine — Smart Vending Machine Control App — Product Detail & Quantity
03

Product Detail & Quantity

Tapping a card opens the product detail: the bundled product image, the unit price, a quantity stepper, and the running total. The stepper is capped at five items so a single tap can never send the machine into an unreasonable order.

Screenshot from the running project
Vending Machine — Smart Vending Machine Control App — Payment Processing State
04

Payment Processing State

While the payment settles, the screen states exactly what is happening - memproses pembayaran, then mengirim produk ke slot dispense. The button is disabled during the transaction so a second payment cannot be started by an impatient tap.

Screenshot from the running project
Vending Machine — Smart Vending Machine Control App — Payment Success Confirmation
05

Payment Success Confirmation

When the order completes, a confirmation dialog reports the order number, the product, the quantity and the total paid, and the customer is sent back to the catalogue with a single button.

Screenshot from the running project

Also built

7

Offline-First Catalogue

The product list is served from the app itself and the images are bundled inside the APK, so the catalogue renders with no network at all. The remote fetch is still wired - it is one line away in the repository - but it is never on the critical path.

USB Serial Controller Link

The app talks to the vending machine controller over USB serial at 9600 baud, 8 data bits, no parity. The connection lives in a dedicated ArduinoLink component instead of inside the Activity, so the payment screen can reuse the same open port.

Order State Machine

An order is not a boolean. It moves through CREATED, PAID, DISPENSING and COMPLETED, with FAILED as the escape hatch. Each step is explicit in the model, so the UI, the log, and the hardware command all agree on what is happening.

Clean Architecture Layers

Presentation, domain and data are separated, and the domain layer knows nothing about Retrofit, Glide or USB. Use cases are the only entry point, which keeps the payment logic testable without an emulator.

Koin Dependency Injection

Modules declare the repository, the use cases and both ViewModels. The Koin module is covered by a test that resolves every definition, so a missing binding fails the build instead of the customer's session.

Retrofit + Moshi Data Layer

The remote catalogue uses Retrofit with a Moshi converter and generated adapters, backed by an OkHttp client with request logging for debugging. It is the same API the original project was built on, brought back to life on current library versions.

Modern Android Toolchain

Rebuilt on Android Gradle Plugin 8.7.3, Kotlin 2.1.0, Gradle 8.9 and JDK 17, with compileSdk 35, edge-to-edge insets handled properly, and the deprecated jcenter and kotlin-android-extensions removed.

Tech Stack

Kotlin Clean Architecture MVVM Koin Retrofit OkHttp Moshi Coroutines Glide usb-serial-for-android Data Binding

The Outcome

The machine can now be operated from a phone end to end: the catalogue renders with no network at all, an order moves through a visible state machine, and the controller receives its slot command the moment payment clears. The project, which had not compiled since it was archived, now builds on a current toolchain with unit tests covering the catalogue, the product lookup and order creation - and the product images ship inside the APK, so the cards are never blank again.

About This Project

A vending machine sells when nobody is there. The cabinet, the motor and the coin reader do their work on their own, but the machine still needed a face for the customer: something that could show what is in stock, take the order, and tell the controller to drop the right product into the right slot.

That face is this app. It opens on the catalogue of the machine, filtered live as you type, and every card is a product with a price. Tapping a product opens the detail screen, where the quantity can be adjusted and the total is recalculated. Paying moves the order through a real state machine - CREATED, PAID, DISPENSING, COMPLETED - and the moment the order reaches DISPENSING the app writes a dispense command to the controller over USB serial at 9600 baud. The customer gets a confirmation, the machine turns the motor, and the slot opens.

Underneath, the app is a small Clean Architecture project. The presentation layer holds the catalogue and the payment screen, the domain layer holds the use cases (get items, create order) and the models (item, order, order status), and the data layer holds the repository implementation, the Retrofit service, and the serial link to the hardware. Koin wires it together, Moshi parses the payloads, and Glide loads the product images.

The catalogue is deliberately offline-first. The original build fetched a product list from a placeholder API and three of its four image URLs pointed at third-party hosts that no longer served anything, so a customer standing in front of the machine saw half-empty cards. The catalogue now lives in the app, the images are bundled inside the APK, and the machine keeps selling when the shop Wi-Fi is down.

The repository had not compiled for years when it was picked up again: duplicated model classes, a broken parameter object, missing drawables, a font naming rule that Android no longer accepts, and a build pinned to jcenter and Android Gradle Plugin 4. It was rebuilt on AGP 8.7.3, Kotlin 2.1.0 and Gradle 8.9, cleaned up, and given unit tests for the catalogue and order creation.

The interface speaks Indonesian, uses large touch targets, and keeps every action one thumb away - it is meant to be used quickly, in front of the machine, by someone who just wants a drink.