Skip to content

Instantly share code, notes, and snippets.

@drd
Last active April 15, 2019 07:57
Show Gist options
  • Save drd/cf45dccd481bb1f8ccc85160fdf8be83 to your computer and use it in GitHub Desktop.
Save drd/cf45dccd481bb1f8ccc85160fdf8be83 to your computer and use it in GitHub Desktop.
A tangled miasma of hacks that somehow manage to launch stetho

So I got annoyed at always having to reconnect to Stetho, and I figured there would be some better way to do this in Android Studio ... and maybe there is, but this is the best I've gotten so far. Putting this together in a gist was inspired by a question on reddit: https://www.reddit.com/r/androiddev/comments/7hz3xy/stetho_anyone_know_of_a_convenient_way_to/

How does this work?

  • Use Android Studio custom Run configuration to call a new gradle task :app:launchStetho before the app launches
  • The gradle task uses launchctl (docs) to start an out-of-band AppleScript automation open-stetho.applescript
  • The AppleScript waits a few seconds, then controls Chrome to open chrome://inspect and loops until the "Inspect" action is available, and then executes a click() against that DOM element

Setup

  1. Choose a location for the applescript and ensure that it's executable chmod +x launch-stetho.applescript
  2. Create the launchctl plist in ~/Library/LaunchAgents
  3. run launchctl load ~/Library/LaunchAgents/com.whatever.example.launchStetho.plist
  4. test that it's working by running launchctl start com.whatever.example.launchStetho ... switch to Chrome and stetho should open.
  5. Create a new configuration in AndroidStudio called "Run + Stetho" or whatever, and at the bottom of the "General" tab, click the + button below the "Before Launch" section, and then click "Run Gradle Task" and specify the module (called project here, probably :app if you click the button) and task name :app:launchStetho
  6. Select this new config and run

Troubleshooting

  • make sure that the launchtl plist is loaded (it should show up if you run launchctl list)
  • make sure that the path in the <ProgramArguments> array is where you have placed your applescript, and that it's executable
  • make sure that the command in the gradle task is correct (the final argument should be the value of the Label key in the LaunchAgent plist)
  • make sure that the selector in the applescript matches the inspect link in http://chrome/inspect
  • if all else fails, try to run any of the individual pieces, starting with the simplest (the applescript), then the gradle task, then the Android Studio config
  • if the applescript fails, open the developer inspector of your chrome tab and see if there are any log messages, this may be helpful
  • comment on this gist or ping me on twitter https://twitter.com/compassing

Whyyyyy?

Why all the complexity? Basically it comes down to needing a way to let the gradle task to launch Chrome automation complete so it doesn't block the build. I tried various more vanilla ways to re-parent the AppleScript but none of them worked for me. (though https://stackoverflow.com/questions/20338162/how-can-i-launch-a-new-process-that-is-not-a-child-of-the-original-process seems to have some promising candidates.. :) So, launchctl being an independent daemon that will start processes at will seemed like a clear choice.

TODO

  • support other platforms
  • figure out a way to know which emulator port is being used
  • actually send the package of the current app/variant and turn that into the selector in open-stetho.applescript
  • make this easier to set up/install
  • i'm sure many other things
// your application build.gradle
// gradle-y-stuff
// this task will be called by your new Run configuration in AndroidStudio
task launchStetho(type: Exec) {
println "Attempting to launch Stetho"
commandLine = ['launchctl', 'start', 'com.whatever.example.launchStetho']
}
// more-gradle-y-stuff
<!--
Put this in ~/Library/LaunchAgents/something.something.plist
Why use launchctl? Because the gradle task needs to delegate process creation to something else,
or gradle will wait for the entire process tree to complete execution, blocking the build from ever
completing. I tried various combinations of nohup and similar, maybe there is a more standard/universal
unix-y way to do this?
-->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<!-- IMPORTANT: this must exactly match the last argument in the commandLine array in your gradle task -->
<string>com.whatever.example.launchStetho</string>
<key>ProgramArguments</key>
<array>
<!-- ALSO IMPORTANT: this path must lead to an executable script at a location accessible by your user -->
<string>/Users/YOURACCOUNT/path/to/open-stetho.applescript</string>
</array>
<!-- uncomment these lines to see what the output of the command is, it might be useful
<key>StandardOutPath</key>
<string>/Users/YOURACCOUNT/launchStetho.out</string>
<key>StandardErrorPath</key>
<string>/Users/YOURACCOUNT/launchStetho.err</string>
-->
</dict>
</plist>
#!/usr/bin/osascript
# This is some leet Javascript-in-AppleScript Chrome automation
delay 6
tell application "Google Chrome"
tell active tab of window 1
open location "chrome://inspect"
-- you may be tempted to make this selector a variable, if you can get it to work by all means, but i ran into hellish/bizarre escaping problems
set isPresent to execute javascript "document.querySelector('div[id*=emulator-5554][id*=stetho][id*=whatever][id*=example][id*=debug] .action')"
repeat while isPresent = missing value
set isPresent to execute javascript "document.querySelector('div[id*=emulator-5554][id*=stetho][id*=whatever][id*=example][id*=debug] .action')"
end repeat
execute javascript "document.querySelector('div[id*=emulator-5554][id*=stetho][id*=whatever][id*=example][id*=debug] .action').click()"
end tell
end tell
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment