Implementing Shake-to-Report (Bug Submission by Shaking) in Mobile Apps
Shake-to-Report is a pattern where a user or tester shakes their device and receives a bug submission form with an automatically captured screenshot and diagnostic information. For internal teams—it's more convenient than TestFlight feedback. For beta testers—the barrier to entry is much lower than filling out a form manually.
Shake Detection
iOS
// UIWindow subclass to intercept shake events
class FeedbackWindow: UIWindow {
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
FeedbackManager.shared.presentFeedbackForm()
}
super.motionEnded(motion, with: event)
}
}
// Usage in SceneDelegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
window = FeedbackWindow(windowScene: windowScene)
window?.rootViewController = UIHostingController(rootView: ContentView())
window?.makeKeyAndVisible()
}
}
Overriding UIWindow is the standard approach, requires no SwiftUI-specific code, and works with any architecture.
Android—Accelerometer
Android doesn't have a built-in "shake" event—detect it via the accelerometer:
class ShakeDetector(private val onShake: () -> Unit) : SensorEventListener {
private val SHAKE_THRESHOLD_GRAVITY = 2.7f
private val SHAKE_SLOP_TIME_MS = 500
private var lastShakeMs: Long = 0
override fun onSensorChanged(event: SensorEvent) {
val gX = event.values[0] / SensorManager.GRAVITY_EARTH
val gY = event.values[1] / SensorManager.GRAVITY_EARTH
val gZ = event.values[2] / SensorManager.GRAVITY_EARTH
val gForce = sqrt(gX * gX + gY * gY + gZ * gZ.toDouble()).toFloat()
if (gForce > SHAKE_THRESHOLD_GRAVITY) {
val now = System.currentTimeMillis()
if (lastShakeMs + SHAKE_SLOP_TIME_MS > now) return
lastShakeMs = now
onShake()
}
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
// Registration in Activity/Fragment
val sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
val shakeDetector = ShakeDetector { showFeedbackDialog() }
sensorManager.registerListener(
shakeDetector,
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_UI
)
SHAKE_SLOP_TIME_MS = 500 prevents multiple triggers from a single shake. A threshold of 2.7g is a compromise between sensitivity and false positives from walking.
Automatically Collected Context
Upon shake, before showing the UI:
data class BugReport(
val screenshot: Bitmap,
val appVersion: String = BuildConfig.VERSION_NAME,
val buildNumber: String = BuildConfig.VERSION_CODE.toString(),
val osVersion: String = "Android ${Build.VERSION.RELEASE}",
val device: String = "${Build.MANUFACTURER} ${Build.MODEL}",
val currentScreen: String = screenTracker.currentScreenName,
val recentLogs: List<String> = LogBuffer.getLast(50), // last 50 log lines
val memoryInfo: String = getMemoryInfo(),
val networkType: String = getNetworkType()
)
recentLogs—if your app has an in-memory log buffer (Timber tree writing to a ring buffer), the bug report immediately contains the last events. The developer sees everything that happened 30 seconds before the shake.
Accessibility Alternatives
Shake is inconvenient for users with tremor or those who keep their device on a desk. For QA and beta programs, add alternative triggers:
- Long-press on logo or version in "About"
- Hidden menu via triple-tap on empty screen area
- Two-finger gesture (3 fingers, 3 taps)
Shake is usually disabled in production or made optional via developer settings—so regular users don't accidentally open the form.
Ready-Made Tools
| Tool | Platforms | Features |
|---|---|---|
| Instabug | iOS, Android, Flutter, RN | Shake + screen recording, Jira/Slack integrations |
| Shake.io | iOS, Android | Built-in discussion thread model |
| BugShaker | iOS (open source) | Simple, email-only |
| Custom implementation | Any | Full control, no external dependencies |
Instabug is the industry standard for mobile QA teams. Custom implementation is justified if you need to control what data leaves the device.
Timeline Estimates
Custom shake detector implementation with screenshot capture and Jira submission—3–5 days. Instabug integration with custom theme and report routing configuration—1–2 days.







