Tomacco

Docs First Android app

Your first Android app#

From an installed Tomacco to a Jetpack Compose app running on the Android Emulator, with a screenshot, a tap checked from outside the app and a passing UI test. Every step is one tomaccoctl call; an agent makes the same calls over MCP.

On this page (10)

How long it takes. On the development host, with the Android SDK, the system image, a JDK and Gradle's dependency cache already present, steps 2 to 8 below took 103 seconds, from configuring the tools to a passing UI test. A first run downloads more: about 5 GB for the SDK and emulator image in tomaccoctl setup, and Gradle's Compose dependencies at the first build. Those downloads were not timed.

1. Install and set up#

curl -fsSL https://tomacco.dev/install.sh | bash
tomaccoctl setup

Say yes to Android in tomaccoctl setup: it installs the SDK and an emulator called Tomacco. Getting started has the other ways to install, and route C the manual Android setup.

The timed run used its own state directory, TOMACCO_HOME=/absolute/tutorial-home, so it left the regular emulators and settings alone. You can do the same; leave it out to use your normal setup.

2. Check what this computer can do#

tomaccoctl call capabilities '{}'

Under android, build, emulator and devices should each say "ready": true. emulator also lists the emulators you have under avds. If something is not ready, its missing names what, and tomaccoctl doctor says how to fix it.

If you already pointed Tomacco at an SDK, JDK and Gradle elsewhere, settings.configure does it in one call:

tomaccoctl call settings.configure '{"android_sdk":"/absolute/Android/Sdk","java_home":"/usr/lib/jvm/default","executables":{"gradle":"/usr/bin/gradle"}}'

3. Start an emulator#

tomaccoctl setup made one called Tomacco. To make another, name an installed system image and a phone profile from tomaccoctl call catalog.list:

tomaccoctl call android.avd_create '{"name":"FirstRun","system_image":"system-images;android-36;google_apis;x86_64","profile_id":"pixel-11"}' --wait
tomaccoctl call android.avd_start '{"name":"FirstRun","headless":true}'

--wait follows a job until it ends and prints the final job. android.avd_start returns at once: the emulator runs as a job for as long as it is up, so do not wait for that job. Instead repeat device.list until the emulator is listed with "state":"device"; it is usually emulator-5554. In the timed run that took about 20 seconds. Leave out headless to see the emulator's window.

tomaccoctl call device.list '{"platform":"android"}'

4. Create the app#

tomaccoctl call project.create '{"path":"/absolute/FirstApp","kind":"compose","name":"FirstApp","organization":"dev.example"}'

This writes a Kotlin and Jetpack Compose app with a versions catalog: a counter with Add one and Reset, and one Compose UI test in app/src/androidTest. The result names its application ID, dev.example.firstapp.

5. Build and run it#

tomaccoctl call project.run '{"path":"/absolute/FirstApp","platform":"android","device":"emulator-5554"}' --wait

The job runs Gradle's installDebug and then launches the app. Its log goes to standard error while it runs; standard output gets the finished job, "state":"succeeded". Without --wait, as an agent over MCP would, keep result.id and repeat job.wait until the state is succeeded, failed, cancelled or timed_out:

tomaccoctl call job.wait '{"job_id":"JOB_ID","seconds":60}'

6. Take a screenshot#

tomaccoctl call device.capture '{"platform":"android","device":"emulator-5554","path":"/absolute/first.png"}'

The result has the file's path, size and SHA-256. This is the screenshot from the timed run:

The FirstApp counter on the Android Emulator: the title FirstApp, the count 0, an Add one button and a Reset button.

7. Tap it and check the result#

Find controls by their text instead of coordinates:

tomaccoctl call testing.action '{"device":"emulator-5554","selector":{"text":"Add one"},"action":"click"}'
tomaccoctl call testing.find '{"device":"emulator-5554","selector":{"text":"1"},"timeout":5}'

testing.find answers the matching elements under matches, with their bounds; an empty list after the timeout means the counter did not change.

8. Run the UI test#

tomaccoctl call project.execute '{"path":"/absolute/FirstApp","platform":"android","action":"instrumented-test"}' --wait

Gradle builds the test APK and runs CounterTest on the emulator. Then read the results as one summary, passing the test job's id so that results of an earlier run are not counted:

tomaccoctl call testing.results '{"path":"/absolute/FirstApp","job_id":"JOB_ID"}'

In the timed run it answered "passed":true with "tests":1,"passed":1,"failed":0. A failure lists the test, its message and its stack under failures.

9. Clean up#

Stop the emulator:

tomaccoctl call android.emulator '{"device":"emulator-5554","action":"stop"}'

With a separate TOMACCO_HOME, deleting that directory removes the emulator made in step 3 and the job records. The project is an ordinary Gradle project; open it with tomacco --project /absolute/FirstApp or in Android Studio.

Where next#

View this page as Markdown on GitHub