flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
164.25k stars 27.1k forks source link

[Android] Flutter 3.22 - Predictive back implementation causes exception in platform view on Android < 13 (api level 33) #151733

Closed timbotimbo closed 1 month ago

timbotimbo commented 1 month ago

Description

Since updating to Flutter 3.22 I'm running into an Android exception when embedding Unity into Flutter using either the flutter_unity_widget or flutter_embed_unity plugin. This only happens on Android < 13.

Even though the exception happens in the native Unity code, I believe this is at least partially a Flutter issue as I can pinpoint the exact Flutter commit that introduces it.

This triggers when Augmented Reality is enabled in Unity, and has nothing to do with performing the Predicitve back gesture itself.

It looks like a function with return type OnBackInvokedCallback is called from the Flutter internals.

E/Unity   (26660): AndroidJavaException: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;
E/Unity   (26660): java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;
E/Unity   (26660):  at java.lang.reflect.Executable.getMethodReturnTypeInternal(Native Method)
E/Unity   (26660):  at java.lang.reflect.Method.getReturnType(Method.java:148)
E/Unity   (26660):  at java.lang.Class.getDeclaredMethods(Class.java:1880)
E/Unity   (26660):  at com.unity3d.player.ReflectionHelper.getMethodID(Unknown Source:0)
E/Unity   (26660):  at com.unity3d.player.UnityPlayer.nativeRender(Native Method)
E/Unity   (26660):  at com.unity3d.player.UnityPlayer.-$$Nest$mnativeRender(Unknown Source:0)
E/Unity   (26660):  at com.unity3d.player.UnityPlayer$F$a.handleMessage(Unknown Source:0)
E/Unity   (26660):  at android.os.Handler.dispatchMessage(Handler.java:102)
E/Unity   (26660):  at android.os.Looper.loop(Looper.java:214)
E/Unity   (26660):  at com.unity3d.player.UnityPlayer$F.run(Unknown Source:0)
E/Unity   (26660): Caused by: java.lang.ClassNotFoundException: Didn't find class "android.window.OnBackInvokedCallback" on path: DexPathList[[zip file "/system/framework/org.apache.http.legacy.boot.jar"

Looking up OnBackInvokedCallback, it has only been added since API 33 for the predicitive back functionality, and doesn't seem to be backwards compatible.

Supporting the predictive back gesture requires updating your app, using the backward compatible OnBackPressedCallback AppCompat 1.6.0-alpha05 (AndroidX) or higher API, or using the new OnBackInvokedCallback platform API. Most apps will use the backward compatible AndroidX API.

I already tried enabling and disabling predicitve back by adding android:enableOnBackInvokedCallback="false" (and "true") to all the androidManifest files in the project.

Flutter commit with regression

The crash is introduced on master with Flutter commit commit https://github.com/flutter/flutter/commit/66d1872cd5cd3f4693a0e95a8fd6fadab5801b6b, when The engine PR Platform channel for predictive back in route transitions on android got merged into master. If I checkout Flutter master at the commit before this, the exception doesn't occur.

Reproduction

This

I can only trigger this when enabling Augmented Reality functionality in Unity. If the build does not include AR, or the android device does not support AR, the exception doesn't happen. I assume Google Arcore starts a new Activity or Thread or something that triggers a response from the new Flutter platform channel.

Reproduction devices

Devices that I tested this on:

Device Android Works
Samsung galaxy S8 9
Nokia 8.1 11
Lenovo tab M10 FHD plus 10
Samsung Galaxy S10 12
Xiaomi redmi note 10 pro 13 ✔️
Samsung galaxy S20FE 13 ✔️
emulator Pixel 8 pro Arm 64 API 32 12
emulator Pixel 8 Pro Arm 64 API 34 14 ✔️

All devices functioned normally on older Flutter versions like 3.16.9. This only reproduces on 3.22.x and master.

Reproduction steps

  1. Download or clone the code sample.
  2. Run the project on a real Android device. (for emulator see section below)
  3. You'll see Unity starting up with a splash screen and after that a rotating Flutter logo. If the crash reproduces it should freeze in a couple of seconds.
  4. On unaffected devices the logo keeps spinning, and you can now enable ar with the toggle at the bottom.

Using a combination of Flutter >= 3.22 and Android <13 you will see the platformview freeze at step 2. Any older Flutter version should just work.

If you test this on Flutter 3.16 or 3.19, you need to use Android >= 10, otherwise a Flutter bug causes the platformview to be invisible.

Reproduction on Emulators

I had issues getting AR support to be detected on emulators, which is needed to reproduce this exception.

The attached screen recording shows 2 emulators on mac.

In some cases you might get a purple screen instead of seeing Unity, this is a compaitiblity issue with Unity as it doesn't officially support running on emulators.

Expected result

No exception that Freezes the platformview, just like on Android 13+ or Flutter < 3.22.

Actual result

Unity inside the platform view receives a message that triggers an exception, which crashes Unity.

Code sample

This reproduction project uses the flutter_embed_unity plugin and comes with a prebuilt unity project so that you only need Flutter to run it. This was built using Unity LTS 2022.3.37 released on 09-07-2024, the latest LTS version.

https://github.com/timbotimbo/flutter_embed_unity_repro/tree/ar_crash

Screenshots or Video

The exception crashes Unity, so in the screen recording you will just see Unity freeze.

APi 32 vs API 34 emulator on mac https://github.com/user-attachments/assets/a9f4f2db-d20a-464a-b29f-2de5329e1a56

Logs

Running from Visual Studio code on Mac, on an API 32 Arm64 emulator.

Debug console ``` Launching lib/main.dart on sdk gphone64 arm64 in debug mode... You are applying Flutter's app_plugin_loader Gradle plugin imperatively using the apply script method, which is deprecated and will be removed in a future release. Migrate to applying Gradle plugins with the declarative plugins block: https://flutter.dev/go/flutter-gradle-plugin-apply You are applying Flutter's main Gradle plugin imperatively using the apply script method, which is deprecated and will be removed in a future release. Migrate to applying Gradle plugins with the declarative plugins block: https://flutter.dev/go/flutter-gradle-plugin-apply ✓ Built build/app/outputs/flutter-apk/app-debug.apk D/FlutterEmbedLog(15782): onAttachedToEngine D/FlutterEmbedLog(15782): onAttachedToActivity D/FlutterEmbedLog(15782): Activity resumed, resuming Unity Connecting to VM Service at ws://127.0.0.1:58582/mka4ULEzDh8=/ws D/com.learntoflutter.flutter_embed_unity_android.platformView.UnityViewFactory@7c30631(15782): UnityViewFactory creating view 0 I/IL2CPP (15782): JNI_OnLoad I/FlutterEmbedLog(15782): Attached Unity to view I/FlutterEmbedLog(15782): Attached Unity to new view D/FlutterEmbedLog(15782): UnityPlatformView onFlutterViewAttached I/PlatformViewsController(15782): Hosting view in a virtual display for platform view: 0 I/PlatformViewsController(15782): PlatformView is using SurfaceProducer backend I/flutter (15782): FlutterEmbed: onPlatformViewCreated(0) D/FlutterEmbedLog(15782): UnityPlayerSingleton onWindowVisibilityChanged 0 D/FlutterEmbedLog(15782): UnityPlayerSingleton became visible, so pausing and resuming Unity I/Unity (15782): MemoryManager: Using 'Dynamic Heap' Allocator. W/d_unity_exampl(15782): Accessing hidden method Ljava/lang/invoke/MethodHandles$Lookup;->(Ljava/lang/Class;I)V (unsupported, reflection, allowed) W/ImageReaderSurfaceProducer(15782): ImageTextureEntry can't wait on the fence on Android < 33 I/Unity (15782): SystemInfo CPU = ARM64 FP ASIMD AES, Cores = 4, Memory = 1966mb I/Unity (15782): SystemInfo ARM big.LITTLE configuration: 4 big (mask: 0xf), 0 little (mask: 0x0) I/Unity (15782): ApplicationInfo com.learntoflutter.flutter_embed_unity_example version 1.0 I/Unity (15782): Built from '2022.3/staging' branch, Version '2022.3.37f1 (340ba89e4c23)', Build type 'Release', Scripting Backend 'il2cpp', CPU 'arm64-v8a', Stripping 'Enabled' E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted E/libprocessgroup(15782): set_timerslack_ns write failed: Operation not permitted I/Unity (15782): Company Name: DefaultCompany I/Unity (15782): Product Name: FlutterEmbedUnityExample D/HostConnection(15782): HostComposition ext ANDROID_EMU_CHECKSUM_HELPER_v1 ANDROID_EMU_native_sync_v2 ANDROID_EMU_native_sync_v3 ANDROID_EMU_native_sync_v4 ANDROID_EMU_dma_v1 ANDROID_EMU_direct_mem ANDROID_EMU_host_composition_v1 ANDROID_EMU_host_composition_v2 ANDROID_EMU_vulkan ANDROID_EMU_deferred_vulkan_commands ANDROID_EMU_vulkan_null_optional_strings ANDROID_EMU_vulkan_create_resources_with_requirements ANDROID_EMU_YUV_Cache ANDROID_EMU_vulkan_ignored_handles ANDROID_EMU_has_shared_slots_host_memory_allocator ANDROID_EMU_vulkan_free_memory_sync ANDROID_EMU_vulkan_shader_float16_int8 ANDROID_EMU_vulkan_async_queue_submit ANDROID_EMU_vulkan_queue_submit_with_commands ANDROID_EMU_sync_buffer_data ANDROID_EMU_vulkan_async_qsri ANDROID_EMU_read_color_buffer_dma GL_OES_EGL_image_external_essl3 GL_OES_vertex_array_object ANDROID_EMU_host_side_tracing ANDROID_EMU_gles_max_version_3_0 E/EGL_emulation(15782): eglCreateContext: EGL_BAD_CONFIG: no ES 3.2 support E/EGL_emulation(15782): tid 15882: eglCreateContext(1849): error 0x3005 (EGL_BAD_CONFIG) E/EGL_emulation(15782): eglCreateContext: EGL_BAD_CONFIG: no ES 3.1 support E/EGL_emulation(15782): tid 15882: eglCreateContext(1843): error 0x3005 (EGL_BAD_CONFIG) E/EGL_emulation(15782): [getAttribValue] Bad attribute idx D/EGL_emulation(15782): eglGetConfigAttrib: bad attrib 0x1 E/EGL_emulation(15782): tid 15882: eglGetConfigAttrib(1292): error 0x3004 (EGL_BAD_ATTRIBUTE) D/EGL_emulation(15782): eglCreateContext: 0xb400007bc80dd910: maj 3 min 0 rcv 3 D/EGL_emulation(15782): eglCreateContext: 0xb400007bc80dd910: maj 3 min 0 rcv 3 D/EGL_emulation(15782): eglCreateContext: 0xb400007bc80dd910: maj 3 min 0 rcv 3 D/EGL_emulation(15782): eglMakeCurrent: 0xb400007bc80dd910: ver 3 0 (tinfo 0x7ddea78280) (first time) D/Unity (15782): GL_EXT_debug_marker GL_EXT_robustness GL_OES_EGL_sync GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_depth24 GL_OES_depth32 GL_OES_element_index_uint GL_OES_texture_float GL_OES_texture_float_linear GL_OES_compressed_paletted_texture GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth_texture GL_OES_texture_npot GL_OES_rgb8_rgba8 GL_EXT_color_buffer_float GL_EXT_color_buffer_half_float GL_EXT_texture_format_BGRA8888 GL_APPLE_texture_format_BGRA8888 ANDROID_EMU_CHECKSUM_HELPER_v1 ANDROID_EMU_native_sync_v2 ANDROID_EMU_native_sync_v3 ANDROID_EMU_native_sync_v4 ANDROID_EMU_dma_v1 ANDROID_EMU_direct_mem ANDROID_EMU_host_composition_v1 ANDROID_EMU_host_composition_v2 ANDROID_EMU_vulkan ANDROID_EMU_deferred_vulkan_commands ANDROID_EMU_vulkan_null_optional_strings ANDROID_EMU_vulkan_create_resources_with_requirements ANDROID_EMU_YUV_Cache ANDROID_EMU_vulkan_ignored_handles ANDROID_EMU_has_shared_slots_host_memory_allocator ANDROID_EMU_vulkan_free_memory_sync ANDROID_EMU_vulkan_shader_float16_int8 ANDROID_EMU_vulka D/Unity (15782): n_async_queue_submit ANDROID_EMU_vulkan_queue_submit_with_commands ANDROID_EMU_sync_buffer_data ANDROID_EMU_vulkan_async_qsri ANDROID_EMU_read_color_buffer_dma GL_OES_EGL_image_external_essl3 GL_OES_vertex_array_object ANDROID_EMU_host_side_tracing ANDROID_EMU_gles_max_version_3_0 D/HostConnection(15782): HostComposition ext ANDROID_EMU_CHECKSUM_HELPER_v1 ANDROID_EMU_native_sync_v2 ANDROID_EMU_native_sync_v3 ANDROID_EMU_native_sync_v4 ANDROID_EMU_dma_v1 ANDROID_EMU_direct_mem ANDROID_EMU_host_composition_v1 ANDROID_EMU_host_composition_v2 ANDROID_EMU_vulkan ANDROID_EMU_deferred_vulkan_commands ANDROID_EMU_vulkan_null_optional_strings ANDROID_EMU_vulkan_create_resources_with_requirements ANDROID_EMU_YUV_Cache ANDROID_EMU_vulkan_ignored_handles ANDROID_EMU_has_shared_slots_host_memory_allocator ANDROID_EMU_vulkan_free_memory_sync ANDROID_EMU_vulkan_shader_float16_int8 ANDROID_EMU_vulkan_async_queue_submit ANDROID_EMU_vulkan_queue_submit_with_commands ANDROID_EMU_sync_buffer_data ANDROID_EMU_vulkan_async_qsri ANDROID_EMU_read_color_buffer_dma GL_OES_EGL_image_external_essl3 GL_OES_vertex_array_object ANDROID_EMU_host_side_tracing ANDROID_EMU_gles_max_version_3_0 D/EGL_emulation(15782): eglMakeCurrent: 0xb400007bc80dd910: ver 3 0 (tinfo 0x7ddea78300) (first time) W/AudioTrack(15782): Use of stream types is deprecated for operations other than volume control W/AudioTrack(15782): See the documentation of AudioTrack() for what to use instead with android.media.AudioAttributes to qualify your playback use case W/Unity (15782): The referenced script (Unknown) on this Behaviour is missing! W/Unity (15782): The referenced script on this Behaviour (Game Object '') is missing! I/Unity (15782): XRGeneral Settings awakening... I/Unity (15782): UnityEngine.XR.Management.XRGeneralSettings:Awake() I/Unity (15782): I/IL2CPP (15782): Locale en-US I/native (15782): I0000 00:00:1720970874.765734 15882 arpresto_api.cc:36] ArPresto::ArPresto_initialize V/Unity-ARCore(15782): Initializing ARCore Pause Handler V/Unity-ARCore(15782): PauseHandler: JNI not attached to thread. Attempting to attach. V/Unity-ARCore(15782): PauseHandler: Attached JNI to thread. V/MediaRouter(15782): Selecting route: RouteInfo{ name=Phone, description=null, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO , presentationDisplay=null } I/MediaRouter(15782): Skip setBluetoothA2dpOn(): types=8388615, isPlaybackActive()=true, BT route=null D/EGL_emulation(15782): app_time_stats: avg=22.38ms min=3.98ms max=141.94ms count=44 D/EGL_emulation(15782): app_time_stats: avg=3.57ms min=2.77ms max=9.98ms count=62 D/EGL_emulation(15782): app_time_stats: avg=16.61ms min=13.07ms max=19.75ms count=61 I/native (15782): I0000 00:00:1720970876.856223 15882 arpresto_api.cc:111] ArPresto::ArPresto_checkApkAvailability I/native (15782): I0000 00:00:1720970876.873243 15882 arpresto_api.cc:70] ArPresto::ArPresto_handleActivityResume I/Unity (15782): ARCheckAvailability: check I/Unity (15782): Assets.Scripts.d__2:MoveNext() I/Unity (15782): UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr) I/Unity (15782): I/Unity (15782): ARCheckAvailability: checking... I/Unity (15782): Assets.Scripts.d__2:MoveNext() I/Unity (15782): UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr) I/Unity (15782): D/EGL_emulation(15782): eglCreateContext: 0xb400007bc80dbed0: maj 3 min 0 rcv 3 I/native (15782): I0000 00:00:1720970876.969729 15882 arpresto_api.cc:193] ArPresto::ArPresto_setCameraTextureNames I/native (15782): I0000 00:00:1720970876.969763 15882 arpresto_api.cc:212] ArPresto::ArPresto_setEnabled W/Unity (15782): Additional Lights Cookie Format (GrayscaleHigh) is not supported by the platform. Falling back to 32-bit format (RGBA8 UNorm) W/Unity (15782): UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset:get_additionalLightsCookieFormat() W/Unity (15782): UnityEngine.Rendering.Universal.UniversalRenderer:.ctor(UniversalRendererData) W/Unity (15782): UnityEngine.Rendering.Universal.UniversalRendererData:Create() W/Unity (15782): UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset:CreateRenderers() W/Unity (15782): UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset:CreatePipeline() W/Unity (15782): UnityEngine.Rendering.RenderPipelineAsset:InternalCreatePipeline() W/Unity (15782): UnityEngine.Rendering.RenderPipelineManager:PrepareRenderPipeline(RenderPipelineAsset) W/Unity (15782): UnityEngine.Rendering.RenderPipelineManager:DoRenderLoop_Internal(RenderPipelineAsset, IntPtr, Object) W/Unity (15782): D/EGL_emulation(15782): app_time_stats: avg=7.34ms min=2.66ms max=199.94ms count=59 E/Unity (15782): AndroidJavaException: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback; E/Unity (15782): java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback; E/Unity (15782): at java.lang.reflect.Executable.getMethodReturnTypeInternal(Native Method) E/Unity (15782): at java.lang.reflect.Method.getReturnType(Method.java:148) E/Unity (15782): at java.lang.Class.getDeclaredMethods(Class.java:1914) E/Unity (15782): at com.unity3d.player.ReflectionHelper.getMethodID(Unknown Source:26) E/Unity (15782): at com.unity3d.player.UnityPlayer.nativeRender(Native Method) E/Unity (15782): at com.unity3d.player.UnityPlayer.-$$Nest$mnativeRender(Unknown Source:0) E/Unity (15782): at com.unity3d.player.UnityPlayer$F$a.handleMessage(Unknown Source:122) E/Unity (15782): at android.os.Handler.dispatchMessage(Handler.java:102) E/Unity (15782): at android.os.Looper.loopOnce(Looper.java:201) E/Unity (15782): at android.os.Looper.loop(Looper.java:288) E/Unity (15782): at com.unity3d.player.UnityPlayer$F.run(Unknown Source:24) E/Unity (15782): Caused by: java.lang.ClassNotFoundException: Didn't find class "android.window.OnBackInvokedCallback" on path: DexPathList[[zip file I/Unity (15782): ARCheckAvailability: available I/Unity (15782): Assets.Scripts.d__2:MoveNext() I/Unity (15782): UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr) I/Unity (15782): Application finished. Exited. ```

Flutter Doctor output

Windows ( Flutter master) ``` [√] Flutter (Channel master, 3.24.0-1.0.pre.69, on Microsoft Windows [Version 10.0.19045.4529], locale en-GB) • Flutter version 3.24.0-1.0.pre.69 on channel master at C:\Flutter\flutter_windows_default\flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 515c52679b (2 hours ago), 2024-07-10 03:40:26 -0400 • Engine revision db2b45bea2 • Dart version 3.6.0 (build 3.6.0-22.0.dev) • DevTools version 2.37.1 [√] Windows Version (Installed version of Windows is version 10 or higher) [√] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at C:\Users\XXXXXX\AppData\Local\Android\sdk • Platform android-34, build-tools 34.0.0 • Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java • Java version OpenJDK Runtime Environment (build 17.0.10+0--11609105) • All Android licenses accepted. [√] Chrome - develop for the web • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe [√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.9.0) • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community • Visual Studio Community 2022 version 17.9.34607.119 • Windows 10 SDK version 10.0.22000.0 [√] Android Studio (version 2024.1) • Android Studio at C:\Program Files\Android\Android Studio • Flutter plugin can be installed from: https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.10+0--11609105) [√] VS Code (version 1.91.0) • VS Code at C:\Users\XXXXXX\AppData\Local\Programs\Microsoft VS Code • Flutter extension version 3.92.0 [√] Connected device (5 available) • SM G780G (mobile) • RF8R8021WSR • android-arm64 • Android 13 (API 33) • sdk gphone64 x86 64 (mobile) • emulator-5554 • android-x64 • Android 14 (API 34) (emulator) • Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.19045.4529] • Chrome (web) • chrome • web-javascript • Google Chrome 126.0.6478.127 • Edge (web) • edge • web-javascript • Microsoft Edge 126.0.2592.87 [√] Network resources • All expected network resources are available. • No issues found! ```
Mac (Flutter 3.22.2) ``` [✓] Flutter (Channel stable, 3.22.2, on macOS 14.5 23F79 darwin-arm64, locale en-GB) • Flutter version 3.22.2 on channel stable at /Users/XXXXXX/Development/Flutter/3.22.2-stable/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 761747bfc5 (6 weeks ago), 2024-06-05 22:15:13 +0200 • Engine revision edd8546116 • Dart version 3.4.3 • DevTools version 2.34.3 [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/XXXXXX/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.10+0-17.0.10b1087.21-11609105) • All Android licenses accepted. [!] Xcode - develop for iOS and macOS ✗ Xcode installation is incomplete; a full installation is necessary for iOS and macOS development. Download at: https://developer.apple.com/xcode/ Or install Xcode via the App Store. Once installed, run: sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer sudo xcodebuild -runFirstLaunch • CocoaPods version 1.15.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2024.1) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.10+0-17.0.10b1087.21-11609105) [✓] VS Code (version 1.87.2) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.84.0 [✓] Connected device (4 available) • sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 14 (API 34) (emulator) • sdk gphone64 arm64 (mobile) • emulator-5556 • android-arm64 • Android 12 (API 32) (emulator) • macOS (desktop) • macos • darwin-arm64 • macOS 14.5 23F79 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 126.0.6478.127 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 1 category. ```
maRci002 commented 1 month ago

I am not familiar with Flutter unity, but this might help, just make sure targetSdkVersion / compileSdkVersion hit at least 33.

Additional Unity configuration for Android

  • In Unity, go to File -> Build Settings, and enable the Export project tickbox
  • In Unity, go to File -> Build Settings -> Player Settings -> Other settings, and make the d following changes:
    • Find Target API level: it's not required, but recommended, to set this to the same target API level of your Flutter project's android app (this is usually set in <your flutter project>/android/app/build.grade as targetSdkVersion)
timbotimbo commented 1 month ago

Target and compile SDK are at 34. Recent Unity versions that have a predictive back support toggle in the settings don't help either.

But this only happens on Android 12 and lower, which don't even support predictive back.

Given that it doesn't happen on earlier flutter versions, something new in the platform channel somehow ends up creating a message that Unity catches and tries to resolve.

maRci002 commented 1 month ago

Based on your logs:

E/Unity   (26660): AndroidJavaException: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;
E/Unity   (26660): java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;
E/Unity   (26660):  at java.lang.reflect.Executable.getMethodReturnTypeInternal(Native Method)
E/Unity   (26660):  at java.lang.reflect.Method.getReturnType(Method.java:148)
E/Unity   (26660):  at java.lang.Class.getDeclaredMethods(Class.java:1880)
E/Unity   (26660):  at com.unity3d.player.ReflectionHelper.getMethodID(Unknown Source:0)

This code is the problem:

@VisibleForTesting
protected OnBackInvokedCallback getOnBackInvokedCallback() {
  return onBackInvokedCallback;
}

Since the getOnBackInvokedCallback method compiles into the codebase, and Unity sees its return type, which is not available under SDK 33, annotating it with @TargetApi / @RequiresApi might solve the problem. Alternatively, changing private final OnBackInvokedCallback onBackInvokedCallback to public and removing getOnBackInvokedCallback might help.

Anyway flutter_unity_widget uses a very old unity dependecy.

Which method could be patched like this:

protected static Method getMethodID(Class class_, String string, String string2, boolean bl) {
    Method method = null;
    a a2 = new a(class_, string, string2);
    if (ReflectionHelper.a(a2)) {
        method = (Method)a2.a;
    } else {
        Class[] arrclass = ReflectionHelper.a(string2);
        float f2 = 0.0f;
        while (class_ != null) {
+            try {
                for (Method method2 : class_.getDeclaredMethods()) {
                    float f3;
                    if (bl != Modifier.isStatic(method2.getModifiers()) || method2.getName().compareTo(string) != 0 || (f3 = ReflectionHelper.a(method2.getReturnType(), method2.getParameterTypes(), arrclass)) <= f2) continue;
                    method = method2;
                    f2 = f3;
                    if (f2 == 1.0f) break;
                }
+            } catch (NoClassDefFoundError e) {
+                // Log the error or handle it as needed
+                break;
            }
            if (f2 == 1.0f || class_.isPrimitive() || class_.isInterface() || class_.equals(Object.class) || class_.equals(Void.TYPE)) break;
            class_ = class_.getSuperclass();
        }
        ReflectionHelper.a(a2, method);
    }
    if (method == null) {
        Object[] arrobject = new Object[4];
        arrobject[0] = bl ? "non-static" : "static";
        arrobject[1] = string;
        arrobject[2] = string2;
        arrobject[3] = class_.getName();
        throw new NoSuchMethodError(String.format("no %s method with name='%s' signature='%s' in class L%s;", arrobject));
    }
    return method;
}
timbotimbo commented 1 month ago

Thanks for the hint of what to try.

I haven't used custom engine changes with the flutter repo yet, so that will take some time to figure out.

My sample code uses the alternative embed plugin, which is a lot newer and cleaner.
As far as I know the unity-classes.jar file gets replaced by the export of the unity project that actually gets included in the project. (android/unityLibrary/libs/unity-classes.jar in the sample code)

However that means it gets recreated on any change in the Unity project. And the internal (compiled) classes aren't a public API so they might change in any Unity update. Which is why I prefer to avoid editing it.

There is a much higher chance of getting Flutter to incorporate a possible bugfix than Unity, so the engine annotations are the next step.

darshankawar commented 1 month ago

Thanks for the detailed report @timbotimbo Can you try below to see if they help or not ?

Also, if below issues along with underlying comments help ?

https://github.com/flutter/flutter/issues/109513 https://github.com/flutter/flutter/issues/117061

timbotimbo commented 1 month ago

@darshankawar

The problem is not with the predictive back action or gesture itself. Just the code added to support it ends up including a class that doesn't exist on Android < 13. And only in this edge case where Unity receives a message and tries to call it using reflection does this exception show up.

Like MaRci002 mentioned above, it is likely something like a missing api annotations such as@TargetApi and @RequiresApi.

justinmc commented 1 month ago

@timbotimbo Check out Setting up the engine development environment and Compiling the engine and let me know how it goes. If you can confirm that the change suggested above fixes your problem then I'm definitely happy to help us land the change in the engine!

Thanks so much for including all of this detail in the issue, and thanks @maRci002 for jumping on this.

maRci002 commented 1 month ago

I can reproduce it.

Minimal flutter code:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  static const platform = MethodChannel('samples.flutter.dev/battery');

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: ElevatedButton(
            onPressed: () => platform.invokeMethod<int>('getBatteryLevel'),
            child: const Text('Get batter level'),
          ),
        ),
      ),
    );
  }
}

Minimal android code:

package com.example.back;

import android.annotation.TargetApi;
import android.window.OnBackInvokedCallback;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;

import java.lang.reflect.Method;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;

public class MainActivity extends FlutterActivity {
    private static final String CHANNEL = "samples.flutter.dev/battery";

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
                .setMethodCallHandler(
                        (call, result) -> {
                            getBatteryLevel();
                            result.success(null);
                        }
                );
    }

    @TargetApi(33)
    @RequiresApi(33)
    @VisibleForTesting
    protected OnBackInvokedCallback getOnBackInvokedCallback2() {
        return null;
    }

    private void getBatteryLevel() {
        try {
            final Method method = MainActivity.class.getDeclaredMethod("getOnBackInvokedCallback2");
            System.out.println(method.getName() + " -> " + method.getReturnType().getName());
        } catch (NoClassDefFoundError e) {
            System.err.println("getOnBackInvokedCallback2\n" + e);
        } catch (NoSuchMethodException e) {
            System.err.println("An error occurred:\n" + e);
        }

        try {
            final Method method = FlutterActivity.class.getDeclaredMethod("getOnBackInvokedCallback");
            System.out.println(method.getName() + " -> " + method.getReturnType().getName());
        } catch (NoClassDefFoundError e) {
            System.err.println("getOnBackInvokedCallback\n" + e);
        } catch (NoSuchMethodException e) {
            System.err.println("An error occurred:\n" + e);
        }
    }
}

Log:

getOnBackInvokedCallback2
java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;
getOnBackInvokedCallback
java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;

As you can see, I added an extra getOnBackInvokedCallback2 method and it compiles into the codebase even though I used the annotations @TargetApi(33) and @RequiresApi(33). This means that when using java.lang.reflect.Method.getReturnType, the caller is responsible for guarding it with an SDK version check or using a try-catch block. Therefore, I believe @justinmc, you can remove the regression label.

Why It Happens:

Class Loading Mechanics: Basically, when we use reflection to dig into methods and their return types, the JVM (or ART on Android) tries to load up every class that's tied to those methods. If the class, like a return type, was added in a newer version of Android and we're running the code on an older version that doesn't have this class, the JVM just can't find it and freaks out, throwing a NoClassDefFoundError or ClassNotFoundException.

timbotimbo commented 1 month ago

@maRci002 As a confirmation I just tested this with Unity using my linked sample code.

  1. I ran this It using Flutter 3.13.9, which doesn't (and shouldn't) produce any errors,

  2. Then I added a function like yours to the MainAcitivty.kt

    @TargetApi(33)
    @RequiresApi(33)
    @VisibleForTesting
    protected fun getOnBackInvokedCallback2() : OnBackInvokedCallback? {
        return null;
    }
  3. Unity now triggers AndroidJavaException: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;.

This confirms that Unity always triggered reflection at this point, it just didn't run into this specific error on older Flutter versions.

Since it uses getDeclaredMethods, just having a function defined can cause this, it doesn't even need to be explicitly called.

darshankawar commented 1 month ago
stable, master flutter doctor -v ``` [!] Flutter (Channel stable, 3.22.2, on macOS 12.2.1 21D62 darwin-x64, locale en-GB) • Flutter version 3.22.2 on channel stable at /Users/dhs/documents/fluttersdk/flutter ! Warning: `flutter` on your path resolves to /Users/dhs/Documents/Fluttersdk/flutter/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/dhs/documents/fluttersdk/flutter. Consider adding /Users/dhs/documents/fluttersdk/flutter/bin to the front of your path. ! Warning: `dart` on your path resolves to /Users/dhs/Documents/Fluttersdk/flutter/bin/dart, which is not inside your current Flutter SDK checkout at /Users/dhs/documents/fluttersdk/flutter. Consider adding /Users/dhs/documents/fluttersdk/flutter/bin to the front of your path. • Upstream repository https://github.com/flutter/flutter.git • Framework revision 761747bfc5 (33 hours ago), 2024-06-05 22:15:13 +0200 • Engine revision edd8546116 • Dart version 3.4.3 • DevTools version 2.34.3 • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades. [!] Xcode - develop for iOS and macOS (Xcode 12.3) • Xcode at /Applications/Xcode.app/Contents/Developer ! Flutter recommends a minimum Xcode version of 13. Download the latest version or update via the Mac App Store. • CocoaPods version 1.11.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] VS Code (version 1.62.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.21.0 [✓] Connected device (5 available) • SM G975F (mobile) • RZ8M802WY0X • android-arm64 • Android 11 (API 30) • Darshan's iphone (mobile) • 21150b119064aecc249dfcfe05e259197461ce23 • ios • iOS 14.4.1 18D61 • iPhone 12 Pro Max (mobile) • A5473606-0213-4FD8-BA16-553433949729 • ios • com.apple.CoreSimulator.SimRuntime.iOS-14-3 (simulator) • macOS (desktop) • macos • darwin-x64 • Mac OS X 10.15.4 19E2269 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 98.0.4758.80 [✓] HTTP Host Availability • All required HTTP hosts are available ! Doctor found issues in 1 category. [!] Flutter (Channel master, 3.24.0-1.0.pre.129, on macOS 12.2.1 21D62 darwin-x64, locale en-GB) • Flutter version 3.24.0-1.0.pre.129 on channel master at /Users/dhs/documents/fluttersdk/flutter ! Warning: `flutter` on your path resolves to /Users/dhs/Documents/Fluttersdk/flutter/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/dhs/documents/fluttersdk/flutter. Consider adding /Users/dhs/documents/fluttersdk/flutter/bin to the front of your path. ! Warning: `dart` on your path resolves to /Users/dhs/Documents/Fluttersdk/flutter/bin/dart, which is not inside your current Flutter SDK checkout at /Users/dhs/documents/fluttersdk/flutter. Consider adding /Users/dhs/documents/fluttersdk/flutter/bin to the front of your path. • Upstream repository https://github.com/flutter/flutter.git • Framework revision 0a99e1f9a9 (12 minutes ago), 2024-07-16 00:57:11 -0400 • Engine revision 2e4401545c • Dart version 3.6.0 (build 3.6.0-36.0.dev) • DevTools version 2.37.1 • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades. [!] Android toolchain - develop for Android devices (Android SDK version 30.0.3) • Android SDK at /Users/dhs/Library/Android/sdk ✗ cmdline-tools component is missing Run `path/to/sdkmanager --install "cmdline-tools;latest"` See https://developer.android.com/studio/command-line for more details. ✗ Android license status unknown. Run `flutter doctor --android-licenses` to accept the SDK licenses. See https://flutter.dev/docs/get-started/install/macos#android-setup for more details. [✓] Xcode - develop for iOS and macOS (Xcode 13.2.1) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 13C100 • CocoaPods version 1.11.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] IntelliJ IDEA Ultimate Edition (version 2021.3.2) • IntelliJ at /Applications/IntelliJ IDEA.app • Flutter plugin version 65.1.4 • Dart plugin version 213.7228 [✓] VS Code (version 1.62.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.29.0 [✓] Connected device (3 available) • Darshan's iphone (mobile) • 21150b119064aecc249dfcfe05e259197461ce23 • ios • iOS 15.3.1 19D52 • macOS (desktop) • macos • darwin-x64 • macOS 12.2.1 21D62 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 109.0.5414.119 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 1 category. [!] Xcode - develop for iOS and macOS (Xcode 12.3) • Xcode at /Applications/Xcode.app/Contents/Developer ! Flutter recommends a minimum Xcode version of 13. Download the latest version or update via the Mac App Store. • CocoaPods version 1.11.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] VS Code (version 1.62.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.21.0 [✓] Connected device (5 available) • SM G975F (mobile) • RZ8M802WY0X • android-arm64 • Android 11 (API 30) • Darshan's iphone (mobile) • 21150b119064aecc249dfcfe05e259197461ce23 • ios • iOS 14.4.1 18D61 • iPhone 12 Pro Max (mobile) • A5473606-0213-4FD8-BA16-553433949729 • ios • com.apple.CoreSimulator.SimRuntime.iOS-14-3 (simulator) • macOS (desktop) • macos • darwin-x64 • Mac OS X 10.15.4 19E2269 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 98.0.4758.80 [✓] HTTP Host Availability • All required HTTP hosts are available ! Doctor found issues in 1 category. ```
maRci002 commented 1 month ago

If Unity were to call FlutterActivity.class.getDeclaredFields(); on older Flutter versions (for instace 3.16), it would also throw a java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;. This error occurs because private final OnBackInvokedCallback onBackInvokedCallback is available since 3.14.0-7.0.pre.

https://github.com/flutter/engine/pull/49093/files#diff-3bb7e325ebb94c08ed1cf05465269b4da25203d3686c2c623c0e267701401812L680

So, the conclusion is that when we declare a field or method with a type that is not available in the SDK, it will throw an error if we try to inspect it via reflection.

yaakovschectman commented 1 month ago

From triage: This sounds like a problem that arises from the use of reflection by Unity. If we are mistaken, please reopen and explain.

github-actions[bot] commented 3 weeks ago

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.