Development modes

enaml-native provides several ways to speed up development without having to re-build the app every time.

The when creating the Application instance in your apps main.py you can set the dev parameter to:

  • Client mode, dev="<ipaddress>" (may be forwarded over adb)
  • Server mode, dev="server"
  • Remote mode dev="remote"

This article covers what each mode does.

Note: The recommended mode is client mode but server mode is the easiest to get started.

Client mode

In client mode, you run a forwarding server on your system and the app connects to it. This lets you make changes using whatever editor you like and they will be pushed to the app by the forwarding server.

Note: Only the src directory will be watched for changes watchdog.

To use client mode and the dev server:

  1. set dev="<dev server ip>"
  2. Rebuild the app
  3. Finally start the dev server by running enaml-native start in your project folder. If you get errors make sure you have watchdog and tornado installed.
  4. Now make changes to your source files and watch it reload.

You will see:

$ enaml-native start
Entering into src
Watching <path/to/you/app>/src
Tornado Dev server started on 8888
Client connected!

and in the device log

07-22 12:22:39.542 /app I/pybridge: Dev server connecting ws://192.168.34.103:8888/dev...
07-22 12:22:39.687 /app I/pybridge: Dev server connected

Note: If the device is not on the same network, you can use use adb reverse tcp:8888 tcp:8888 and dev="127.0.0.1:8888"

A youtube video of reloading is here:

Live Reloading

Server mode

In server mode, the app hosts a web page with an editor and uses a websocket server to process changes to the code. This is how the playground app works.

To enable server mode:

  1. Set dev = "server"
  2. Rebuild the app
  3. Open http://<ip of device>:8888 in your browser (when using a simulator run adb forward tcp:8888 tcp:8888 and use localhost)
  4. Edit code in the browser.
  5. Press play

Remote mode

In this mode the app will start a server that waits for commands from the bridge protocol allowing the apps's python code to be executed remotely while still controlling the app.

You can then use an IDE with a debugger like KDevelop or PyCharm (or android studio with the Python plugins) to breakpoint and step through code.

Remote debugging in enaml native

Using

Start the forwarding service

#: In one shell run
enaml-native start --remote-debugging

Modify your apps enaml-native build.gradle (or edit venv/android/enaml-native/src/build.gradle) to use remote-debugging.

Make sure to set the DEV_REMOTE_DEBUG config var to true and the update the DEV_SERVER address to your system's address.

// Open the android folder in android-studio 
// Edit the build.gradle for enaml-native 
debug {
    // Set dev remote to true to use the remote debugger
    // must run `enaml-native start` then run your app locally
    buildConfigField "boolean", "DEV_REMOTE_DEBUG", "true"
    buildConfigField "String", "DEV_SERVER", "\"ws://<your-pc-ip>:8888/dev\""
}

Now build in run the app. It will simply sit at the loading screen.

#: Build the app in remote-debugging mode 
enaml-native run-android

Modify your apps main.py to use dev='remote' as shown below.

def main():
    """ Called by PyBridge.start()
    """
    from enamlnative.android.app import AndroidApplication
    app = AndroidApplication(
        debug=True,
        dev='remote',  
        activity=MainActivity()
    )
    app.start()

# And setup a run hook
if __name__ == '__main__':
    #: This is used when remote debugging
    sys.path.append(os.path.abspath('.'))

    #: Init remote nativehooks implementation
    from enamlnative.core import remotehooks
    remotehooks.init()
    main()

Finally execute the main.py script locally (or use an IDE like PyCharm)!

python main.py

The app runs off your PC but controls the phone as if it were running on the device!

Profiling

You can profile using standard cProfile builtin to python. Usage is the same. See https://docs.python.org/2/library/profile.html

Debugging the bridge

One of the great things about using the bridge is being able to get a complete trace of everything that was happening. To enable this set app.debug = True and rebuild the app. It will generate a nice trace of all bridge methods and callbacks.

07-22 12:01:55.014 /app I/pybridge: ======== Py <-- Native ======
07-22 12:01:55.015 /app I/pybridge: ['event', [0, 111, 'onPageScrollStateChanged', [['java.lang.Integer', 2]]]]
07-22 12:01:55.015 /app I/pybridge: ['event', [0, 111, 'onPageSelected', [['java.lang.Integer', 0]]]]
07-22 12:01:55.015 /app I/pybridge: ['event', [0, 114, 'onTabUnselected', [['android.support.design.widget.TabLayout.Tab', 'android.support.design.widget.TabLayout$Tab@6c6e5d4']]]]
07-22 12:01:55.015 /app I/pybridge: ['event', [45, 804, 'onCreateView', []]]
07-22 12:01:55.015 /app I/pybridge: ===========================
07-22 12:01:55.046 /app I/pybridge: ======== Py --> Native ======
07-22 12:01:55.047 /app I/pybridge: ('c', (838, u'android.widget.ScrollView', [('android.content.Context', ExtType(code=1, data='\xff'))]))
07-22 12:01:55.047 /app I/pybridge: ('c', (839, u'android.widget.LinearLayout', [('android.content.Context', ExtType(code=1, data='\xff'))]))
07-22 12:01:55.047 /app I/pybridge: ('m', (839, 0, 'setOrientation', [('int', 1)]))
07-22 12:01:55.048 /app I/pybridge: ('c', (840, u'android.support.v7.widget.CardView', [('android.content.Context', ExtType(code=1, data='\xff'))]))
07-22 12:01:55.048 /app I/pybridge: ('m', (840, 0, 'setPadding', [('int', 60), ('int', 60), ('int', 60), ('int', 60)]))
07-22 12:01:55.048 /app I/pybridge: ('c', (841, u'android.view.ViewGroup$MarginLayoutParams', [('int', -1), ('int', -1)]))
07-22 12:01:55.048 /app I/pybridge: ('m', (840, 0, 'setLayoutParams', [('android.view.ViewGroup$LayoutParams', ExtType(code=1, data='\xcd\x03I'))]))
07-22 12:01:55.049 /app I/pybridge: ('m', (841, 0, 'setMargins', [('int', 30), ('int', 30), ('int', 30), ('int', 30)]))
07-22 12:01:55.049 /app I/pybridge: ('m', (840, 0, 'setContentPadding', [('int', 30), ('int', 30), ('int', 30), ('int', 30)]))
07-22 12:01:55.049 /app I/pybridge: ('c', (842, u'android.widget.LinearLayout', [('android.content.Context', ExtType(code=1, data='\xff'))]))
07-22 12:01:55.049 /app I/pybridge: ('m', (842, 0, 'setOrientation', [('int', 1)]))
07-22 12:01:55.049 /app I/pybridge: ('c', (843, u'android.widget.TextView', [('android.content.Context', ExtType(code=1, data='\xff'))]))
07-22 12:01:55.049 /app I/pybridge: ('m', (843, 0, 'setTextKeepState', [('java.lang.CharSequence', u'Chapter - 1')]))
07-22 12:01:55.049 /app I/pybridge: ('m', (843, 0, 'setTypeface', [('android.graphics.Typeface', u'sans-serif-condensed-light'), ('int', 0)]))
07-22 12:01:55.049 /app I/pybridge: ('m', (843, 0, 'setTextSize', [('float', 18.0)]))
...
07-22 12:01:55.051 /app I/pybridge: ('r', (45, ('android.view.View', ExtType(code=1, data='\xcd\x03F'))))
07-22 12:01:55.051 /app I/pybridge: ===========================
07-22 12:01:55.087 /app I/pybridge: ======== Py <-- Native ======
07-22 12:01:55.087 /app I/pybridge: ['event', [46, 805, 'onCreateView', []]]
07-22 12:01:55.087 /app I/pybridge: ===========================

Developing packages

If you're working on enaml-native itself or an lib package (eg enaml-native-icons) you can create a symlink for your package's src folder to the app's site-packages.

For example

ln -s /abs/path/to/your-custom-lib/src/yourpackage path/to/apps/venv/android/x86_64/python/site-packages/yourpackage

The build system will then copy it in without having to re-generate and re-install a package