The developer community is constantly searching for faster, lighter tools. Recently, an elite R&D squad, cheered on by Kodee, our favorite Kotlin mascot, set out to build a native, ultra-lightweight API testing tool. The vision was clear: a blazingly fast, Kotlin-powered alternative to bulky API clients, using a strict Unidirectional Data Flow (UDF)architecture.
For those new to the domain: an Application Programming Interface (API) is a communication bridge that allows different software systems to talk to each other.
The UI/UX team delivered a locked-in interface for this tool. However, at the critical integration phase, the core logic engineers were reassigned. The project is stalled: the interface is there but inert—it renders empty screens and emits user Actions into the void. Kodee has recruited you to implement the engine and bring this tool to life.
Before you dive in, ensure your machine is set up correctly. This README covers prerequisites, how to run the project, and where to get help. Because the app uses system-level tools (e.g. curl), having them installed and on your PATH is critical.
If you hit JVM crashes, environment errors, or build failures, see debugging.md for step-by-step troubleshooting (IDE config, Windows env vars, macOS JDK setup).
-
JDK 17 or 21
Gradle and the project require JVM 17 or 21. We recommend Eclipse Temurin (free, open-source):
https://adoptium.net/ — download JDK 17 for your OS. -
curl
The app runs HTTP requests by callingcurlon your system. It must be on your PATH.- macOS: Usually pre-installed. If not:
brew install curl. - Windows: Available on Windows 10+, or install curl for Windows.
- macOS: Usually pre-installed. If not:
We recommend using a terminal to build and run: the IntelliJ integrated terminal, or an external Bash (macOS/Linux) / PowerShell (Windows). This avoids IDE-run configuration issues and matches the commands below.
From the project root:
# Run the application
./gradlew run
# Build (compile)
./gradlew buildFrom the project root in PowerShell or Command Prompt:
# Run the application
.\gradlew.bat run
# Build (compile)
.\gradlew.bat buildIf java -version does not show 17 or 21, or Gradle fails with “requires JVM 17", set JAVA_HOME and PATH as below. Install the JDK first from Adoptium if needed.
- Install JDK 17 from Adoptium.
- Windows key → type env → open Edit the system environment variables → Environment variables.
- Under System variables:
- JAVA_HOME: Add or Edit → set to the JDK folder (e.g.
C:\Program Files\Eclipse Adoptium\jdk-17.x.x-hotspot). - Path: Edit → remove old Java entries → New → add
%JAVA_HOME%\binand move it to the top.
- JAVA_HOME: Add or Edit → set to the JDK folder (e.g.
- Click OK everywhere, then fully restart IntelliJ (File → Exit). Open a new terminal and run
java -version.
Temporary (current terminal only) in PowerShell:
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-17.x.x-hotspot" # your actual path
$env:Path = "$env:JAVA_HOME\bin;" + $env:Path
.\gradlew.bat run- Install JDK 17 from Adoptium.
- In a terminal:
/usr/libexec/java_home -VPick the version (e.g. 17) from the list, then:
export JAVA_HOME=$(/usr/libexec/java_home -v 17)
export PATH="$JAVA_HOME/bin:$PATH"
java -version- Then from the project root:
./gradlew --stop
./gradlew clean runFor persistent setup, add the export lines to your shell config (e.g. ~/.zshrc). More detail (including IDE settings) is in debugging.md.
-
Java 17 or 21:
java -version
You should see 17 or 21. If you see something else and the build fails, set
JAVA_HOMEas above. We strongly recommend Java 17. -
curl:
curl --version
Your work is in the engine room: Business and Logic layers that handle user actions and update application State.
You will implement logic in exactly four files:
| File | Role |
|---|---|
| StorageHelper.kt | Loads and saves the list of API requests to/from disk (JSON). Runs on Dispatchers.IO. Handles missing or invalid files by returning an empty list. |
| RequestsViewModel.kt | Holds the app’s main state: the list of requests, which one is active, and the current URL/Method/Headers/Body. Reacts to user actions (add, rename, delete, edit) and persists after every change. |
| ExecutionViewModel.kt | Validates input (URL, headers JSON), triggers execution via CurlExecutor, and exposes running state and output (stdout/stderr) to the UI. |
| CurlExecutor.kt | Runs the actual HTTP call by invoking the system curl command with the chosen method, URL, headers, and body. Streams output back to the caller. |
- Implement StorageHelper.kt:
loadRequestsandsaveRequestsonDispatchers.IO, pretty-printed JSON. If the file is missing or malformed, return an empty list. - RequestsViewModel: On startup, load saved requests; if none exist, create a default "New request", save it, set it active. On add/rename (ignore blank names), update state, set modified request active, save. On delete active, set active to the first remaining request, then save.
- In RequestsViewModel, track URL, HTTP Method, Headers, Body in real time. Update the active request in the
StateFlowand trigger a save viaStorageHelperon every change.
- CurlExecutor.kt + ExecutionViewModel: Validate before run: URL must not be blank, Headers must be valid JSON; otherwise show clear errors (
Error: URL is empty,Error: Headers must be valid JSON...) and exit code-1. Runcurl -s -S -X <METHOD> <URL>with-Hfor headers and write body to the process for POST/PUT/PATCH. No concurrent runs (ignore Send whenisRunning). While running, set status to "Sending...", clear previous output, stream stdout and stderr tooutputContent.
- Use Check Progress in the UI to run the local test suite. You need:
execution: 8/8helpers: 7/7viewmodel: 18/18
- When all pass, use the Submit button in the UI and enter your email.
-
"Cannot find a Java installation matching: {languageVersion=17}"
Gradle can auto-download JDK 17 on first build. If that fails (e.g. proxy), install Eclipse Temurin 17 and setJAVA_HOMEas above. -
Build fails on JDK 22+ / 25
Gradle 8.5 runs on JDK 17–21 only. Use JDK 17 or 21 and pointJAVA_HOMEto it. -
Process “stuck” at 96%
Normal: the app is running. Closing the app lets the task reach 100%.
Full steps for IDE config, Windows env vars, and macOS JDK: debugging.md.
- Click Submit solution in the status bar.
- Confirm and enter your email in the dialog.
- The app zips the project and sends it to the server.
The app follows Model–View–ViewModel (MVVM) with a unidirectional data flow: the UI only observes state from the ViewModels and sends Actions (e.g. “add request”, “send request”); ViewModels update state and talk to the “engine” (storage and execution). The View never touches the model or I/O directly.
- Model — Data and persistence:
ApiRequest,ApiCollection, and the JSON format used byStorageHelper. No UI or business logic here. - View — Compose UI:
ApiClientScreenand components (RequestPane,ResponsePane,RequestsPanel,StatusPanel, dialogs). They collect state from the ViewModels viacollectAsState()/mutableStateOfand call ViewModel methods on user input. - ViewModel — Two ViewModels:
- RequestsViewModel exposes
StateFlow<List<ApiRequest>>, the selected request, and status. It handles list and selection changes and delegates load/save to StorageHelper. - ExecutionViewModel exposes
isRunning,outputContent, and status. It validates input, calls CurlExecutor to run the request, and streams output back into state.
- RequestsViewModel exposes
StorageHelper and CurlExecutor are not part of MVVM’s “Model” in the strict sense—they are service layers the ViewModels use to persist data and run HTTP. The diagram below summarizes the flow.
flowchart LR
subgraph View["View (Compose UI)"]
Screen[ApiClientScreen]
RequestPane[RequestPane]
ResponsePane[ResponsePane]
RequestsPanel[RequestsPanel]
StatusPanel[StatusPanel]
end
subgraph ViewModels["ViewModels"]
RVM[RequestsViewModel]
EVM[ExecutionViewModel]
end
subgraph ModelAndServices["Model & Services"]
Model[(ApiRequest / ApiCollection)]
Storage[StorageHelper]
Curl[CurlExecutor]
end
Screen --> RVM
Screen --> EVM
RequestPane --> RVM
ResponsePane --> EVM
RequestsPanel --> RVM
StatusPanel --> EVM
RVM -->|"load / save"| Storage
RVM -->|"read / write"| Model
EVM -->|"execute"| Curl
Curl -->|"stdout / stderr"| EVM
Storage -->|"JSON"| Model
Flow in short: User acts in the View → ViewModel method is called → ViewModel updates its state (and may call StorageHelper or CurlExecutor) → View observes state and re-renders. No direct View → Model or View → disk/network.
Good luck—the launch depends on you!