Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .fvmrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"flutter": "3.44.2"
"flutter": "3.44.3"
}
4 changes: 4 additions & 0 deletions src/serious_python/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.1.0

* **Android:** run first-launch asset unpacking and native library loading off the platform main thread so they no longer block vsync — boot-time animations (e.g. a splash / boot screen spinner) stay smooth while the app starts. Also ship consumer ProGuard rules that keep the pyjnius bootstrap classes, fixing pyjnius in release (minified) Android builds. See `serious_python_android` 4.1.0.

## 4.0.0

* **App packaging lifted into serious_python.** Your Python app now ships **unpacked inside the application bundle**, next to the Python stdlib and site-packages, on macOS / iOS / Windows / Linux — no first-launch `app.zip` extraction. On **Android** the app ships as a *stored* `app.zip` asset inside the APK and is unpacked once (version-keyed) to the app-support files dir on the first launch after an install/update, like the existing `extract.zip`. Web (Pyodide) is unchanged. The `package` command stages the processed app into **`SERIOUS_PYTHON_APP`** (symmetric with `SERIOUS_PYTHON_SITE_PACKAGES`); each platform's native build copies it into the bundle (Android zips it as a stored asset).
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python
description: A cross-platform plugin for adding embedded Python runtime to your Flutter apps.
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 4.0.0
version: 4.1.0

platforms:
ios:
Expand Down
6 changes: 6 additions & 0 deletions src/serious_python_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 4.1.0

* Run the `extractAsset` / `unzipAsset` / `loadLibrary` method-channel handlers on a background `Executor` (posting the `Result` back on the main looper) instead of inline on the platform main thread. The first-launch asset unpack and the pyjnius native-library load no longer block Android's `Choreographer`, so Flutter's vsync isn't starved and on-screen animations (e.g. a boot/splash spinner) stay smooth while the app starts.
* Ship consumer ProGuard/R8 keep rules (`-keep class com.flet.serious_python_android.** { *; }`) so release (minified) builds don't obfuscate or strip the classes pyjnius resolves by name at runtime. Without them R8 renamed `PythonActivity` and dropped its static `mActivity` field, breaking pyjnius in release builds with `type object 'C.f' has no attribute 'mActivity'` (debug builds were unaffected).
* Version bump aligning with the `serious_python_*` 4.1.0 release.

## 4.0.0

* Ship the app as a *stored* `app.zip` asset in the APK and unpack it once (version-keyed) to `<application-support>/flet/app` on the first launch after install/update, via the new `prepareApp()`. The version-keyed unpack moved out of `run()`; user data in the sibling `<application-support>/data` is preserved across updates.
Expand Down
6 changes: 5 additions & 1 deletion src/serious_python_android/android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ buildscript {
}

group = "com.flet.serious_python_android"
version = "4.0.0"
version = "4.1.0"

rootProject.allprojects {
repositories {
Expand Down Expand Up @@ -78,6 +78,10 @@ configure<LibraryExtension> {
ndk {
abiFilters.addAll(abis)
}
// Keep rules for classes pyjnius / the native runtime resolve by name at
// runtime (e.g. PythonActivity.mActivity); merged into the consuming app's
// R8 pass so release builds don't obfuscate/strip them.
consumerProguardFiles("consumer-rules.pro")
}

// No jniLibs packaging config needed: the native modules are real ELF .so that
Expand Down
10 changes: 10 additions & 0 deletions src/serious_python_android/android/consumer-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Consumer ProGuard/R8 rules — automatically applied to apps that depend on
# serious_python_android.
#
# pyjnius and the native runtime look these classes (and their members) up by
# name via JNI/reflection at runtime. Without these keep rules, release-mode R8
# minification renames/strips them — e.g. PythonActivity -> "C.f" and its static
# `mActivity` field is dropped — which breaks pyjnius with:
# pyjnius: not available on this platform -
# type object 'C.f' has no attribute 'mActivity'
-keep class com.flet.serious_python_android.** { *; }
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
import androidx.annotation.NonNull;
import android.system.Os;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
Expand All @@ -31,6 +36,25 @@ public class AndroidPlugin implements FlutterPlugin, MethodCallHandler, Activity
private MethodChannel channel;
private Context context;

// Heavy native work (asset extraction/unzipping, native library loading) must
// NOT run on the platform main thread: it would block Android's Choreographer
// and starve Flutter's vsync, freezing on-screen animations (e.g. the boot
// spinner). Run it on a background executor and post the MethodChannel result
// back on the main thread (Flutter requires result callbacks there).
private final ExecutorService ioExecutor = Executors.newSingleThreadExecutor();
private final Handler mainHandler = new Handler(Looper.getMainLooper());

private void runAsync(@NonNull Result result, String errorCode, Callable<Object> work) {
ioExecutor.execute(() -> {
try {
Object value = work.call();
mainHandler.post(() -> result.success(value));
} catch (Throwable e) {
mainHandler.post(() -> result.error(errorCode, e.getMessage(), null));
}
});
}

@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(),
Expand Down Expand Up @@ -107,19 +131,30 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
// Load a native library by name via Java's System.loadLibrary(), which —
// unlike dart:ffi's dlopen-based DynamicLibrary.open used for
// libdart_bridge — runs the library's JNI_OnLoad. That's how pyjnius's
// helper (libpyjni.so) captures the JavaVM + app ClassLoader. Called from
// app code here, so JNI_OnLoad sees the app's class loader.
try {
System.loadLibrary((String) call.argument("libname"));
result.success(null);
} catch (Throwable e) {
result.error("loadLibrary", e.getMessage(), null);
}
// helper (libpyjni.so) captures the JavaVM + app ClassLoader.
//
// Run off the main thread (dlopen + JNI_OnLoad can be slow). System.loadLibrary
// resolves the .so via the calling class's loader (AndroidPlugin -> app loader)
// regardless of thread, and JNI_OnLoad's FindClass uses that same loader; we
// also pin the worker's context loader to the app loader so JNI_OnLoad sees it
// if it reads the thread context loader.
final String libname = call.argument("libname");
runAsync(result, "loadLibrary", () -> {
Thread t = Thread.currentThread();
ClassLoader prev = t.getContextClassLoader();
t.setContextClassLoader(context.getClassLoader());
try {
System.loadLibrary(libname);
} finally {
t.setContextClassLoader(prev);
}
return null;
});
} else if (call.method.equals("extractAsset")) {
// Stream an APK asset to disk as one whole file (e.g. stdlib.zip).
try {
String asset = call.argument("asset");
String dest = call.argument("dest");
final String asset = call.argument("asset");
final String dest = call.argument("dest");
runAsync(result, "extractAsset", () -> {
java.io.File destFile = new java.io.File(dest);
if (destFile.getParentFile() != null) destFile.getParentFile().mkdirs();
byte[] buf = new byte[1 << 16];
Expand All @@ -128,15 +163,13 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
int n;
while ((n = in.read(buf)) > 0) out.write(buf, 0, n);
}
result.success(dest);
} catch (Exception e) {
result.error("extractAsset", e.getMessage(), null);
}
return dest;
});
} else if (call.method.equals("unzipAsset")) {
// Unpack an APK asset zip (e.g. extract.zip) into a directory tree.
try {
String asset = call.argument("asset");
String destDir = call.argument("dest");
final String asset = call.argument("asset");
final String destDir = call.argument("dest");
runAsync(result, "unzipAsset", () -> {
java.io.File root = new java.io.File(destDir);
byte[] buf = new byte[1 << 16];
try (java.io.InputStream in = context.getAssets().open(asset);
Expand All @@ -155,10 +188,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
}
}
}
result.success(destDir);
} catch (Exception e) {
result.error("unzipAsset", e.getMessage(), null);
}
return destDir;
});
} else {
result.notImplemented();
}
Expand All @@ -167,6 +198,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
ioExecutor.shutdown();
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python_android
description: Android implementation of the serious_python plugin
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 4.0.0
version: 4.1.0

environment:
sdk: ">=3.0.0 <4.0.0"
Expand Down
4 changes: 4 additions & 0 deletions src/serious_python_darwin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.1.0

* Version bump aligning with the `serious_python_*` 4.1.0 release.

## 4.0.0

* **Swift Package Manager support (dual with CocoaPods).** The plugin now builds under SPM as well as CocoaPods, so apps can use either integration (CocoaPods goes read-only in December 2026; Flutter ships SPM on by default since 3.44). A new `darwin/serious_python_darwin/Package.swift` builds the same Swift source as the podspec, with `getResourcePath` resolving `Bundle.module` under SPM (`#if SWIFT_PACKAGE`) and the framework `python.bundle` under CocoaPods.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
Pod::Spec.new do |s|
s.name = 'serious_python_darwin'
s.version = '4.0.0'
s.version = '4.1.0'
s.summary = 'A cross-platform plugin for adding embedded Python runtime to your Flutter apps.'
s.description = <<-DESC
A cross-platform plugin for adding embedded Python runtime to your Flutter apps.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_darwin/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python_darwin
description: iOS and macOS implementations of the serious_python plugin
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 4.0.0
version: 4.1.0

environment:
# The Swift Package Manager build path needs Flutter 3.44 / Dart 3.11 (the
Expand Down
4 changes: 4 additions & 0 deletions src/serious_python_linux/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.1.0

* Version bump aligning with the `serious_python_*` 4.1.0 release.

## 4.0.0

* `prepareApp()` returns `<exeDir>/app`; the app's Python sources ship unpacked next to the bundled stdlib + site-packages (no first-launch extraction). The CMakeLists copies `SERIOUS_PYTHON_APP` into the bundle.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_linux/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python_linux
description: Linux implementations of the serious_python plugin
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 4.0.0
version: 4.1.0

environment:
sdk: '>=3.1.3 <4.0.0'
Expand Down
4 changes: 4 additions & 0 deletions src/serious_python_platform_interface/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.1.0

* Version bump aligning with the `serious_python_*` 4.1.0 release.

## 4.0.0

* Add `prepareApp()` to `SeriousPythonPlatform` — materializes the packaged app on disk (if needed) and returns the directory containing its entry point.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_platform_interface/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python_platform_interface
description: A common platform interface for the serious_python plugin.
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 4.0.0
version: 4.1.0

environment:
sdk: ">=3.0.0 <4.0.0"
Expand Down
4 changes: 4 additions & 0 deletions src/serious_python_windows/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.1.0

* Version bump aligning with the `serious_python_*` 4.1.0 release.

## 4.0.0

* `prepareApp()` returns `<exeDir>/app`; the app's Python sources ship unpacked next to the bundled CPython `Lib`/`DLLs`/`site-packages` (no first-launch extraction). The CMakeLists copies `SERIOUS_PYTHON_APP` into the runner output.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_windows/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python_windows
description: Windows implementations of the serious_python plugin
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 4.0.0
version: 4.1.0

environment:
sdk: '>=3.1.3 <4.0.0'
Expand Down
Loading