From 90c76893e8836f168e84cbeb3fe853d7775fd3e3 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 26 Jun 2026 16:51:26 -0700 Subject: [PATCH] fix(android): guard getLongVersionCode() for API < 28 to prevent launch crash getAppVersion runs on every startup and called PackageInfo.getLongVersionCode() (API 28+) unconditionally. R8 outlines the call into a synthetic class it may merge with other API 28+ outlines (e.g. Flutter 3.41's ImageDecoder-based image decoder); invoking that class on API < 28 fails verification with NoClassDefFoundError (android.graphics.ImageDecoder$OnHeaderDecodedListener) and crashes the app on launch on Android 8.1 and below. Guard the call with Build.VERSION.SDK_INT and fall back to the deprecated versionCode int field on older devices. --- src/serious_python_android/CHANGELOG.md | 4 ++++ .../flet/serious_python_android/AndroidPlugin.java | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/serious_python_android/CHANGELOG.md b/src/serious_python_android/CHANGELOG.md index 54eafbf5..c8c6a99c 100644 --- a/src/serious_python_android/CHANGELOG.md +++ b/src/serious_python_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## Unreleased + +* Fix app crashing on launch on Android 8.1 and below (API < 28). The `getAppVersion` method-channel handler, called on every startup, used `PackageInfo.getLongVersionCode()` (API 28+) unconditionally. R8 outlines this call into a synthetic class that it can merge with other API 28+ outlines — notably Flutter 3.41's `ImageDecoder`-based image decoder — and invoking that merged class on API < 28 fails verification with `NoClassDefFoundError: android.graphics.ImageDecoder$OnHeaderDecodedListener`. The call is now guarded with a `Build.VERSION.SDK_INT` check, falling back to the deprecated `versionCode` int field on older devices. + ## 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. diff --git a/src/serious_python_android/android/src/main/java/com/flet/serious_python_android/AndroidPlugin.java b/src/serious_python_android/android/src/main/java/com/flet/serious_python_android/AndroidPlugin.java index bb9c636b..238401af 100644 --- a/src/serious_python_android/android/src/main/java/com/flet/serious_python_android/AndroidPlugin.java +++ b/src/serious_python_android/android/src/main/java/com/flet/serious_python_android/AndroidPlugin.java @@ -122,7 +122,16 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { android.content.pm.PackageManager pm = context.getPackageManager(); android.content.pm.PackageInfo info = pm.getPackageInfo(packageName, 0); String versionName = info.versionName; - long versionCode = info.getLongVersionCode(); + // PackageInfo.getLongVersionCode() is API 28+. Calling it unconditionally + // makes the Android Gradle plugin (R8) outline the call into a synthetic + // class that it may merge with other API 28+ outlines (e.g. Flutter's + // ImageDecoder-based image decoder). Invoking that merged class on + // API < 28 fails verification with NoClassDefFoundError and crashes the + // app on launch, because getAppVersion runs on every startup. Guard the + // call so older devices use the deprecated int field instead. + long versionCode = (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) + ? info.getLongVersionCode() + : (long) info.versionCode; result.success(versionName + "+" + versionCode); } catch (Exception e) { result.error("Error", e.getMessage(), null);