Project structure

An enaml-native app is organized similar to a react-native app. When you create a new app with the enaml-native-cli. The project directory consists of the basic structure:

android/      #: Android project using gradle
ios/          #: iOS xcode project with cocoapods
src/          #: Python source for your app 
venv/         #: Symlink to the conda env for enaml-native packages and cross compiled libraries
environment.yml  #: Project config

Your actual apps are in the android and ios folders. The build scripts are configured to run enaml-native commands that build and package your python source files as required for the app based on the dependencies.

The src folder contains the python code that the build system will copy into the root of your python app. It does a recursive copy so any files, folders, subfolders, and files within will be added into your will be available on the actual app.

Note: The location of the root folder of these files can be obtained from the ASSETS environment variable.

Configuring the project

The environment.yml file is your project config. It is a conda environment file with some extra sections. If you open it you see the following.

# Name
name: <app name>
bundle_id: com.example.app

# Channels to look for any specific libraries
channels:
  - local
  - codelv

# App dependencies to be installed
dependencies:
  - python=3.10
  - pip
  - pip-tornado
  - android-python * py310*
  - enaml-native
  - pip:
    - enaml-native-cli


# Exclude unused packages and libs using glob patterns here.
# Only the ones required by enaml-native are left in by default.
# If you need to use a module (ex json) then remove lib._json.so
# from this list so it does not get exlcuded. You can also add specific
# exclusions under ios and android.
excluded:
  # Packages
  - idlelib
  - ensurepip
  - distutils
  - lib2to3
  - pydoc_data
  - hotshot
  - turtledemo
  - venv
  - site-packages/enaml/qt
  - site-packages/enaml/workbench
  - site-packages/enaml/*lib
  - site-packages/enaml/scint*
  # ...
# Android specific configuration
android:
  sdk: /home/jrm/Android/Sdk/
  ndk: /home/jrm/Android/Sdk/ndk/24.0.7956693
  targets:
    - x86_64
    - x86
    - arm
    - arm64
  excluded:
    - lib._ctypes.so

# iOS specific configuration
ios:
  targets:
    - iphoneos
    - iphonesimulator

As you can see there's a few shared properties such as name, bundle_id, and version which are
self explanatory, sources, and separate configs for ios and android.

Each platform config (ios and android) has arches, dependencies, and excluded.
The rest are specfic for the platform.

As you may have guessed, targets defines which platforms to compile python and extensions modules
for and dependencies is a list of requirements to install on the app.

Note: All dependencies must have a package made specifically for using on android or ios.

The excluded list is a list of patterns
that you can use to exclude unused python modules from your app to reduce the app size.

For android there's sdk and ndk which are the paths used for building. You MUST update these to
point to wherever your SDK and NDK are installed.

For ios there's the project which defines the name of the <project>.xcworkspace that will be
built.

Release your app

It's easiest to just make release builds using android-studio or xcode. It will prompt you to
do any configuration necessary.

Android

See https://developer.android.com/studio/publish/index.html

  • Open the android folder in android-studio
  • From the menu choose Build -> Generate signed APK
  • Create or choose a certificate to sign the app
  • Select release and make sure that V1 and V2 are checked
  • Press finish
  • When the build completes click locate to open the location (android/app/release)
  • Then you can publish this apk to the Play Store

Enaml-native - Creating a android release build

Once that is done you can then do release builds with the enaml-native cli using
enaml-native build-android --release or enaml-native build-ios --release.
The --release flag tells it to do a release build (it's debug by default).

For help see the documentation for each platform, enaml-native does nothing special here.

Reducing app size

Since apps must include both the python interpreter (as native libraries) and all the python
and app sources, the installed apps can get large if care is not taken to remove unused modules.

Excluding unused packages and modules

The excluded list can be used to remove unnecessary packages and files from the python build (located under build/python/python.tar.gz in your app
directory). Add glob patterns to tell the build system to ignore copying files into the python bundle.

excluded:
  # Packages
  - idlelib
  - ensurepip
  - distutils
  - lib2to3
  - pydoc_data
  - hotshot
  - turtledemo
  - venv
  - site-packages/enaml/qt
  - site-packages/enaml/workbench
  - site-packages/enaml/*lib
  - site-packages/enaml/scint*

You can also use the apk analyzer in android-studio which nicely graphs which files are using space
within an apk (it even shows within the python.tar.gz!) so use that as well!

Enaml-native packages

With the release of the enaml-native-cli it is now
possible to create a single pip package that includes android and ios libraries along
with the python source to use them. These are called enaml native packages for lack of a better
name and can be installed with either pip or the enaml-native cli.

Why?

Because enaml-native is growing and apps will typically want to only include the native
dependencies they need. The project was redesigned and broken down into smaller installable
EnamlPackages each containing their own with separate native and python requirements.

These packages will allow any user to create, maintain, and share their own versions of
pluggable libraries as needed. There is no need to have your code merged in by some "core" group
of maintainers.

Concept

The concept of the package is pretty simple.

  1. Each "app" project now has it's own venv with the enaml-native-cli installed.
  2. You install your apps packages and recipes in the venv using either pip or the cli
  3. Once installed, they are available for the build process to use as an app requirement
  4. Define which of these are needed by your app in your apps requirements

Package format

A package is simply a directory with the following subdirectories and files.

android/          #: Android library using gradle (if applicable)
ios/              #: iOS xcode library using cocoapods (if applicable)
src/              #: Python source for your app 
src/setup.py      #: Setupfile for your package's source (this is what is installed on the app)
setup.py          #: Pip setup file for the enaml-native package

To make an "enaml-package" that follows this format use:
enaml-native init-package <some-package-name> <destination/folder>

Linking native libraries

If your package requires native dependencies (ex the enaml-native-maps package
requires native android GoogleMaps) the android or ios project can be "linked"
to your library when its installed by the user. This is done by the
enaml-native link command.

"Linking" is automatically adding the necessary changes to the users android and ios projects
(such as adding your library as a project to compile build.gradle) so they can simply install
and use it right away. This makes it easier for new users to quickly get started with your code
without having to read through how to configure it all manually.

An entry point enaml_native_linker was added to the cli that lets you define a custom function
to link the users project where required.

Note: Currently only linking has been implemented for android (you can use your an entry point)

Unlinking is the reverse of linking and unlinking our package from a users project should remove
any changes made during linking. This is required so upgrading or switching dependencies is
seamless and error free.

Android specifics

You can open the android folder in Android studio and it will load like any normal android project.
This way you can easily modify any native java code and get all the highlighting and error
checking, etc. all android documentation applies here. The project uses the gradle build system.

Building for Android

enaml-native hooks itself into the gradle build process to include your python source and libraries.
This hook is in android/build.grade.

It simply runs enaml-native bundle-assets which packages all the pre-compiled packages along with your apps src code into a file python.tar.gz and copies it to android/app/src/main/assets/python/python.zip.

Building python and compiled extensions for Android

enaml-native builds python and any dependencies that have compiled components (c, c++, cython)
using conda-mobile.

These compiled modules are simply added to your apps environment file so they are included in the bundling process.

At runtime extension libraries imported using a custom import hook, see import_hooks.py .

Adding libraries with Gradle

To add custom libraries:

  1. Open the project in android-studio
  2. Modify the android/app/build.gradle as needed.
  3. Run gradle sync (should prompt you when you make a change) and it will collect your new libraries

You can see there's a few already being used. Once a library is added with gradle you can use it
via making a wrapper Proxy and Toolkit component (see the
native component docs) .

iOS specifics

Note: iOS is not yet supported

You can open the project.xcworkspace within the ios folder in xcode and work in your app normally. All iOS
docs apply here.

Adding libraries with CocoaPods

To add custom libraries:

  1. Modify the ios/<app>/Podfile as needed
  2. cd to ios/<app> and run pod install or pod update
  3. Rebuild your xcode project

You can see there's a few already being used. Once a library is added with cocopods you can use it via making
a wrapper Proxy and Toolkit component (see the native component docs) .

That's all for now! Thanks for reading! Please suggest more docs if something is confusing.