Qaid
ARTICLE

Thumbs in iOS and Android Apps

Add thumbs, a marked-up screenshot and screen recordings to a native app with real SwiftUI and Compose views.

Qaid Team
TL;DR

Add the QaidThumbs Swift package or dev.qaid:thumbs, configure it with your embed key, and call QaidThumbs.present from a Send feedback button. The person marks up a screenshot or records the screen in a native sheet, and the report lands in your inbox with the app version, device and recent errors.

App reports and site reports share one inbox, one key and one plan. The sheet, the markup editor and the record button are drawn with SwiftUI on iOS and Jetpack Compose on Android.

What you need

iOS Android
Package https://github.com/qaiddev/thumbs-native, product QaidThumbs dev.qaid:thumbs:0.3.0 from Maven Central
Minimum iOS 15 minSdk 26, compileSdk 35, the Compose compiler plugin
Key Your project’s embed key, under Settings → API Keys Same key

The key only sends feedback, so it is safe inside an app.

Add the package

  1. iOS: add the Swift package

    In Xcode choose File → Add Package Dependencies, paste https://github.com/qaiddev/thumbs-native, and add the QaidThumbs product to your app target. Screen recording uses ReplayKit, which needs no permission entry.

  2. Android: add the dependency

    Add implementation("dev.qaid:thumbs:0.3.0") to your app module. The library merges a recording service and the permissions it needs into your manifest. Google Play asks apps that declare FOREGROUND_SERVICE_MEDIA_PROJECTION to explain it in the Play Console, so say it records the screen for bug reports.

  3. Configure once at launch

    Call QaidThumbs.configure in your app delegate, App init or Application.onCreate. Android also takes the application, so reports saved offline can send at the next launch.

  4. Call present from a button

    Put a Send feedback item in a menu or settings screen. Pass screen to say where the person was.

import QaidThumbs

QaidThumbs.configure(QaidThumbsConfiguration(
    apiKey: "YOUR_EMBED_KEY",
    appName: "My App"
))

QaidThumbs.present(screen: "Settings")
QaidThumbs.configure(this, QaidThumbsConfig(
    apiKey = "YOUR_EMBED_KEY",
    appName = "My App",
))

QaidThumbs.present(activity, screen = "Settings")

The project’s Customize page prints both with your key in place, under the iOS and Android tabs of THUMBS CODE.

What the person sees

The SDK takes the screenshot before the sheet opens, so the sheet is never in it. The sheet holds the screenshot, the thumbs, a message box and Send. Mark up opens an editor with box, arrow, pen and redact tools, undo and clear. Redact paints solid black onto the image’s own pixels, on the phone, before anything is sent.

Record screen closes the sheet and records the app. When the person stops, the sheet comes back with the clip and whatever they had typed.

Pro

Recordings need Pro, as on the web. On Free the sheet says screen recordings aren’t available and offers a screenshot instead.

What reaches your inbox

Open a report and Details lists the app as CinemaCrew 1.4 (812), the device as iPhone16,1 · iOS 18.2, the screen and the locale. Tell it who is signed in and anything else you want beside each report:

QaidThumbs.setUser(id: "u_42", email: "sam@example.com", name: "Sam")
QaidThumbs.setMetadata(key: "plan", value: "pro")
QaidThumbs.log("Sync failed", level: .error)

Console Errors holds recent warnings and errors from the app’s own log plus your log lines. Network Errors holds failed calls you report. Only the method, the address without its query string, and the status are kept.

val client = OkHttpClient.Builder()
    .addInterceptor(QaidThumbs.networkInterceptor())
    .build()

On iOS, call QaidThumbs.record(request:response:error:) where your requests finish.

Hide private screens

Mark a view as sensitive and it is blacked out in screenshots and covered while recording. Password fields are covered without being marked.

cardNumberField.qaidSensitive = true

Open a quest after a report

Link quests to the thumbs with quests: QaidQuestLinks(up:, down:, video:). Once qaid accepts the report, the sheet closes and onLinkedQuest hands you the quest. Show it with the quests SDK, passing the feedback id so the answers are joined to the report:

QaidThumbs.onLinkedQuest = { link in
    QaidQuests.present(questId: link.questId, metadata: ["feedbackId": link.feedbackId])
}

The two SDKs don’t depend on each other; an app that never sets the hook just shows “Sent”.

Domain Restriction

A project with a Domain Restriction checks app reports too. By default the SDK names the app by its bundle id, and qaid reads the id back to front: com.example.myapp passes a restriction of example.com. If your app’s id doesn’t match your domain, give the SDK an address on the domain instead:

QaidThumbsConfiguration(apiKey: "YOUR_EMBED_KEY",
                        pageUrl: URL(string: "https://example.com/app/ios"),
                        appName: "My App")

Offline, shake and your own words

A report sent with no connection is saved and sent later: at the next launch, when the app comes back to the front, or when the network returns. QaidThumbs.enableShakeToReport() opens the sheet on a shake, and setScreen names the screen it came from.

Every string on the sheet and in the editor can be replaced through text, so the sheet can match your app’s language. Coming from 0.2? QaidFeedback is now QaidThumbs, and reports queued by 0.2 still send after the upgrade. The full API is in the SDK repository.

Back to all articles