Skip to main content
Kolibri can be deployed as an Android application, enabling mobile access to educational content on tablets and phones. The Android app is built using Python-for-Android and distributed as an APK.

Android Platform Detection

Kolibri includes platform detection for Android environments:
# From kolibri/utils/android.py

ANDROID_PLATFORM_SYSTEM_VALUE = "Android"

def on_android():
    """Check if running on Android platform."""
    return hasattr(sys, "getandroidapilevel")
This detection is used throughout Kolibri to handle Android-specific behavior:
  • Security constraints: Android restricts access to /proc, requiring different system information gathering
  • File system limitations: Different paths and permissions compared to desktop Linux
  • API level awareness: The getandroidapilevel() method returns the minimum Android API level the Python build targets
The Android API level returned by sys.getandroidapilevel() represents the minimum API level Python was compiled against, not the current runtime Android version.

Build Pipeline

The Android APK is built from the kolibri-static PyPI package:
Git release branch
        |
        v
  kolibri-static
  (PyPI package)
        |
        v
   Android APK

Prerequisites

1

Install Build Tools

Required tools for building Android APK:
  • Python 3.6+ (matching Kolibri’s requirements)
  • Android SDK and NDK
  • Python-for-Android (p4a)
  • Buildozer (optional, for simplified builds)
2

Install Kolibri Static

pip install kolibri-static
This package includes all dependencies bundled, ideal for offline environments.

Configuration

Android-Specific Settings

When running on Android, Kolibri uses platform-specific configurations:
# Android detection affects:
# - File descriptor calculations (no /proc access)
# - Thread pool sizing (limited by Android constraints)
# - Storage paths (Android-specific directories)
# - System info gathering (different APIs)

KOLIBRI_HOME on Android

On Android, KOLIBRI_HOME is typically set to:
/data/data/<package_name>/files/.kolibri
This location provides:
  • App-private storage
  • Persistent across app updates
  • Automatic backup support (if enabled)
  • Protected from other apps

Environment Variables

Recommended environment variables for Android deployment:
export KOLIBRI_HOME=/data/data/org.learningequality.kolibri/files/.kolibri
export KOLIBRI_HTTP_PORT=5000
export KOLIBRI_RUN_MODE=android
export KOLIBRI_NO_FILE_BASED_LOGGING=1  # Log to logcat instead

Building the APK

Using Python-for-Android

Basic build process:
1

Create Build Recipe

Create a Python-for-Android recipe for Kolibri:
# recipe.py
from pythonforandroid.recipe import PythonRecipe

class KolibriRecipe(PythonRecipe):
    name = 'kolibri'
    version = '0.17.0'  # Update to match release
    url = 'https://pypi.org/packages/source/k/kolibri-static/kolibri-static-{version}.tar.gz'
    depends = ['python3', 'sqlite3', 'openssl']
    site_packages_name = 'kolibri'
2

Build with P4A

p4a apk \
  --private /path/to/kolibri \
  --package org.learningequality.kolibri \
  --name "Kolibri" \
  --version 0.17.0 \
  --bootstrap sdl2 \
  --requirements python3,kolibri-static \
  --permission INTERNET \
  --permission WRITE_EXTERNAL_STORAGE \
  --permission READ_EXTERNAL_STORAGE \
  --orientation sensor

Using Buildozer

Buildozer simplifies the build process:
1

Install Buildozer

pip install buildozer
2

Create buildozer.spec

[app]
title = Kolibri
package.name = kolibri
package.domain = org.learningequality

source.dir = .
source.include_exts = py,png,jpg,kv,atlas,json

version = 0.17.0
requirements = python3,kolibri-static==0.17.0

permissions = INTERNET,WRITE_EXTERNAL_STORAGE,READ_EXTERNAL_STORAGE

orientation = sensor

[buildozer]
log_level = 2
warn_on_root = 1

android.api = 28
android.minapi = 21
android.ndk = 23b
android.sdk = 30
3

Build APK

buildozer android debug
Release build:
buildozer android release

Application Lifecycle

Starting Kolibri on Android

The Android app needs a service wrapper to run Kolibri:
# main.py
import os
import sys
from android.runnable import run_on_ui_thread
from jnius import autoclass

# Set environment
os.environ['KOLIBRI_HOME'] = '/data/data/org.learningequality.kolibri/files/.kolibri'
os.environ['KOLIBRI_HTTP_PORT'] = '5000'
os.environ['KOLIBRI_RUN_MODE'] = 'android'

# Start Kolibri
from kolibri.utils.cli import main

if __name__ == '__main__':
    # Start in background
    sys.argv = ['kolibri', 'start', '--foreground']
    main()

Background Service

Create a persistent background service:
from android.service import Service
from android import AndroidService

class KolibriService(Service):
    def onCreate(self):
        # Initialize Kolibri
        pass

    def onStartCommand(self, intent, flags, startId):
        # Start Kolibri server
        return AndroidService.START_STICKY

    def onDestroy(self):
        # Stop Kolibri server
        pass

App Configuration

Permissions

Required Android permissions:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Optional permissions:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

Database Considerations

On Android, Kolibri uses SQLite (PostgreSQL is not available):
# Automatically configured for Android
DATABASES = {
    "default": {
        "ENGINE": "kolibri.deployment.default.db.backends.sqlite3",
        "NAME": os.path.join(KOLIBRI_HOME, "db.sqlite3"),
        "OPTIONS": {"timeout": 100},
    },
}
Android has strict file descriptor limits. The thread pool is automatically constrained to prevent exceeding these limits.

Content Pre-loading

For offline deployment, pre-load content into the APK:
1

Prepare Content

Export content channels on a desktop system:
# Export channel
kolibri manage exportchannel <channel_id> /path/to/export

# Export content
kolibri manage exportcontent <channel_id> /path/to/export
2

Include in APK

Add content to the APK assets:
# Copy to assets directory
mkdir -p assets/content
cp -r /path/to/export/* assets/content/
Update build configuration to include assets.
3

Import on First Run

Import content from assets on first launch:
import shutil
from android.storage import app_storage_path

assets_path = os.path.join(app_storage_path(), 'content')
if os.path.exists(assets_path):
    # Copy to KOLIBRI_HOME
    shutil.copytree(
        assets_path,
        os.path.join(os.environ['KOLIBRI_HOME'], 'content')
    )
See Offline Setup for more details on content pre-loading.

Distribution

APK Signing

For production distribution, sign the APK:
# Generate keystore (first time only)
keytool -genkey -v \
  -keystore kolibri-release.keystore \
  -alias kolibri \
  -keyalg RSA \
  -keysize 2048 \
  -validity 10000

# Sign APK
jarsigner -verbose \
  -sigalg SHA1withRSA \
  -digestalg SHA1 \
  -keystore kolibri-release.keystore \
  bin/Kolibri-0.17.0-release-unsigned.apk \
  kolibri

# Align APK
zipalign -v 4 \
  bin/Kolibri-0.17.0-release-unsigned.apk \
  bin/Kolibri-0.17.0-release.apk

Distribution Methods

Direct Download

Host APK on a web server for direct download

USB Transfer

Copy APK to devices via USB for offline distribution

Local Network

Share APK over local network using FTP/HTTP server

SD Card

Pre-load APK on SD cards for bulk distribution
For Google Play Store distribution, additional compliance and testing requirements apply. Consult Google Play policies.

Testing

Android Testing Features

Kolibri includes Android-specific integration tests:
# From integration_testing/.../android-app-bar.feature
Feature: Android App Bar
  The app bar on Android devices has specific behaviors

  Scenario: App bar displays on Android
    Given I am on an Android device
    When I open Kolibri
    Then I should see the Android app bar

Testing Checklist

  • APK installs successfully
  • App requests appropriate permissions
  • First launch completes setup wizard
  • Database initialization succeeds
  • Server starts and binds to port
  • Web UI loads in WebView/browser
  • Content import/export works
  • User authentication functions
  • Offline mode operates correctly
  • App doesn’t crash under memory pressure
  • Thread pool stays within FD limits
  • Database operations complete successfully
  • Content playback is smooth
  • Test on different Android versions (API 21+)
  • Test on different device sizes (phone/tablet)
  • Test on different screen orientations
  • Test on devices with limited storage

Debugging

Debug the Android app using logcat:
# View all logs
adb logcat

# Filter Python logs
adb logcat | grep python

# Filter Kolibri logs
adb logcat | grep kolibri

# Clear logs and follow
adb logcat -c && adb logcat

Troubleshooting

Common Issues

NDK version mismatch
# Specify NDK version explicitly
buildozer android debug \n      android.ndk = 23b
Missing dependencies
# Install build dependencies
sudo apt-get install python3-dev build-essential \
  libsqlite3-dev libffi-dev libssl-dev
Port already in use
# Use different port
os.environ['KOLIBRI_HTTP_PORT'] = '5001'
Storage permission denied
# Request permissions at runtime (Android 6+)
from android.permissions import request_permissions, Permission
request_permissions([
    Permission.WRITE_EXTERNAL_STORAGE,
    Permission.READ_EXTERNAL_STORAGE
])
Too many open files
# Reduce thread pool size
os.environ['KOLIBRI_SERVER_CHERRYPY_THREAD_POOL'] = '30'
Out of memory
# Enable file logging to reduce memory overhead
os.environ['KOLIBRI_NO_FILE_BASED_LOGGING'] = '0'

Best Practices

Minimize APK Size

  • Use kolibri-static package
  • Exclude unnecessary dependencies
  • Use APK split or Android App Bundle
  • Compress assets

Optimize Performance

  • Constrain thread pool for mobile
  • Use appropriate cache sizes
  • Implement proper lifecycle management
  • Handle background/foreground transitions

Plan for Offline

  • Pre-load essential content
  • Test without network connectivity
  • Implement proper error handling
  • Provide offline documentation

Test Thoroughly

  • Test on multiple devices
  • Test different Android versions
  • Test with limited resources
  • Test update scenarios

Next Steps

Offline Setup

Pre-load content for network-free operation

Production Deployment

Learn about server-based deployment

Build docs developers (and LLMs) love