AppForShare

Shake to report a bug in a test build, without an SDK

· 9 min read

Shake to report a bug in a test build, without an SDK

A tester hits a bug on the checkout screen. If reporting it means leaving the app, opening a chat, finding the right thread and typing which build they are on, half of them will not bother, and the other half will forget the build number. Shake to report fixes the first part: the tester shakes the phone where the bug is, and a feedback form opens with the app version, the build number and the screen already filled in.

Most products that offer this sell it as an SDK. This guide shows how to do it without one, with a short shake handler for Swift, Kotlin, React Native and Flutter that opens a feedback URL. It also covers what an SDK gives you that a URL cannot, and why the shake belongs in test builds only.

Why shake to report works for testers

  • It happens on the broken screen. The report carries the screen the tester was on, not the one they remember an hour later.
  • Nothing to find. Testers do not need to know where a debug menu lives. You tell them once: "shake the phone to send feedback".
  • The build is exact. The app knows its own version and build number, so the report is tied to the build the tester is really running. That one detail removes the most common question in beta testing: "which build was that?"
  • It works for people who are not developers. Clients and field testers will shake a phone. They rarely open a ticket tracker.

Two weak points are worth knowing. On iPhone, shaking is also the system gesture for Undo while typing, so a shake in a text field can trigger both. And some testers cannot shake comfortably, or have turned Shake to Undo off in the accessibility settings. Keep a "Send feedback" item in a debug menu as a second way in.

Keep it out of store builds

A shake handler that opens a tester feedback page has no place in an App Store or Google Play build. Real users would land on a form meant for your team, and anyone could send reports into your inbox. Put the handler behind a build flag that only your test configuration sets: a Swift compilation condition such as BETA in your ad hoc configuration, a BuildConfig field or flavor check on Android, and a build time flag in React Native and Flutter. A runtime switch alone is not enough, because the code still ships. Before each store release, confirm the release build does not contain it.

What the shake should open

The shake itself does one thing: it calls a function that opens a feedback URL with four values the app already knows.

https://appforshare.io/feedback/YOUR_TOKEN?platform=android&version=2.4.0&build=118&screen=Checkout

platform is ios or android, version is the marketing version, build is CFBundleVersion on iOS or versionCode on Android, and screen is whatever name your team uses for that screen. The function that builds this URL is about ten lines per platform. It is shown in full in in-app tester feedback without an SDK, and the dashboard card gives it for all four platforms. The snippets below call it openFeedback.

The shake handlers

iOS (Swift)

UIKit reports a shake as a motion event, so there is no sensor code to write. Override motionEnded and check for .motionShake.

// In the root view controller of the test build only (inside #if BETA).
// If the shake never arrives, use a UIWindow subclass instead.
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
    guard motion == .motionShake else { return super.motionEnded(motion, with: event) }
    openFeedback(screen: String(describing: type(of: self)))
}

Motion events go to the first responder and up the responder chain. If your view controllers do not receive them, override the same method in a UIWindow subclass and pass the top screen's name.

Android (Kotlin)

Android has no shake event, so the app listens to the accelerometer. The sample below treats a reading above about 2.7 times gravity as a shake, and waits 2 seconds before it reacts again, so one shake does not open the form three times.

// Test builds only: create it when BuildConfig.FLAVOR == "beta" (or your own flag).
// start() in onResume, stop() in onPause.
class ShakeToFeedback(private val activity: Activity) : SensorEventListener {
    private val sensors = activity.getSystemService(Context.SENSOR_SERVICE) as SensorManager
    private var last = 0L

    fun start() {
        sensors.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)?.let {
            sensors.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
        }
    }

    fun stop() = sensors.unregisterListener(this)

    override fun onSensorChanged(e: SensorEvent) {
        val (x, y, z) = e.values
        val force = sqrt(x * x + y * y + z * z) / SensorManager.GRAVITY_EARTH
        val now = SystemClock.elapsedRealtime()
        if (force > 2.7f && now - last > 2000) {
            last = now
            openFeedback(activity, activity.javaClass.simpleName)
        }
    }

    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
}

Unregister in onPause so the listener does not run in the background. If testers report that the form opens while they walk or drive, raise the threshold a little.

React Native

The react-native-shake package wraps both platforms. addListener returns a subscription; remove it when the component unmounts.

// react-native-shake. Call the hook only in test builds (behind your own flag).
import { useEffect } from 'react';
import RNShake from 'react-native-shake';

export function useShakeToFeedback(screen: string) {
  useEffect(() => {
    const subscription = RNShake.addListener(() => openFeedback(screen));
    return () => subscription.remove();
  }, [screen]);
}

Flutter

The shake package (version 3.x) does the same for Flutter. Pass a function that returns the current screen name, for example from your router.

// shake package 3.x, test builds only.
import 'package:shake/shake.dart';

ShakeDetector? detector;

void startShakeToFeedback(String Function() currentScreen) {
  detector = ShakeDetector.autoStart(
    onPhoneShake: (ShakeEvent event) => openFeedback(currentScreen()),
  );
}

void stopShakeToFeedback() => detector?.stopListening();

Screenshots: what changes without an SDK

An SDK can grab the current screen the moment the phone is shaken. A URL cannot. The form opens in the browser, and the browser cannot see your app. So the order matters:

  1. The tester takes a screenshot with the phone buttons while the bug is on screen.
  2. The tester shakes the phone. The feedback form opens with the build and screen filled in.
  3. The tester attaches the screenshot from the photo library, marks the problem with a pen if the form allows it, and sends.

Put that order in the message you send with the build. It takes one line, and it is the difference between a report with the broken screen and a report with the form itself.

What a feedback SDK adds, and what it costs

SDK products are a real option, and for some teams the right one. AppsOnAir (by Logicwind) sells AppRemark, an in-app feedback SDK for iOS, Android, Flutter and React Native that captures a screenshot on shake, lets the tester draw on it and attaches extra metadata; its docs describe it as beta and currently free (read November 3, 2026). Firebase App Distribution has an Android feedback SDK, also beta, that takes a screenshot and asks the tester to sign in with a Google account.

What an SDK gives you:

  • An automatic screenshot of the current screen.
  • Often logs, app state or custom data attached for you.
  • A form inside the app, so the tester never leaves it.

What it costs:

  • A dependency to update with every OS and framework release, and to review for the data it collects.
  • Possibly new permissions, and lines in your privacy texts if the SDK sends data to a vendor.
  • Build flag work to keep it out of store builds, as with any test only code.
  • Lock in: reports live in that vendor's dashboard, and removing the SDK is a code change.

If the automatic screenshot is the feature your testers need most, an SDK earns its place. If your testers can press two buttons first, the URL gets you most of the value with ten lines of code you own.

Doing it with the AppForShare in-app feedback link

AppForShare shares iOS and Android test builds through install links. Each project can turn on an in-app feedback link from its Testing page, on every account plan including Free. The card shows the URL and code samples for Swift, Kotlin, React Native and Flutter, with an "Open the link" tab and a "Shake to send" tab that match the snippets above. There is no SDK and no automatic screenshot.

  • Parameters: platform, version, build, screen (up to 80 characters) and info (up to 500) for anything else, such as a test account id. Never put passwords or tokens in it.
  • Build match: the same platform and build number first, then the same version, then the newest build of that platform.
  • The form: Bug, Idea or Other, up to 5 screenshots with drawing on every plan, one screen recording per report on Pro and Team. No sign in for the tester.
  • Your side: reports arrive in the Feedback inbox marked as sent from inside the app, with the screen. The account owner gets an email on every plan. Pro and Team add push, Slack, Microsoft Teams, Discord and webhook alerts, and one click sending to Linear or Jira.
  • Control: "New link" replaces the token, so older builds stop opening the form; "Turn off" makes the link answer that it is not active.

It works next to the Send feedback card on install pages, so testers who never shake can still report from the page they installed from. For crashes, use a crash reporting tool; on Pro and Team you can add your crash dashboard link to the project so it shows next to each report.

A short rollout plan

  1. Turn on the feedback link for the project and copy the URL into the test configuration only.
  2. Add openFeedback and the shake handler behind your beta flag, plus a debug menu item.
  3. Use real screen names. "Checkout" in a report should match "Checkout" in your code and tickets.
  4. Ship one build to yourself, shake on two screens and check the reports land on the right build.
  5. Tell testers the order: screenshot, shake, attach, send.
  6. Check a release build to confirm the handler is not in it.

Common questions

Can I add shake to report without an SDK?

Yes. A shake handler of a few lines opens a feedback URL with the version, build and screen. You give up the automatic screenshot; the tester attaches one taken with the phone buttons.

Does shake to report work the same on Android and iPhone?

iOS reports a shake as a motion event. Android has none, so the app reads the accelerometer and decides what counts as a shake. React Native and Flutter packages hide that difference.

Will a shake handler get my app rejected?

Keep it in test builds only, behind a build flag, and it never reaches a store build. A tester feedback form has no place in an App Store or Google Play release.

Is the AppForShare feedback link free?

Yes, on every account plan, including Free. Screen recordings and Slack, Teams, Discord, Linear and Jira need Pro or Team. See pricing.

Keep reading

App Distribution Platform · · 5 min read

AppForShare File Size and Upload Limits

AppForShare limits the size of each build you upload by plan: 50 MB without an account, 150 MB on the Free plan, 2 GB on Pro and 4 GB on Team. Accounts also have a storage total, and every plan...