juicycleff / flutter-unity-view-widget

Embeddable unity game engine view for Flutter. Advance demo here https://github.com/juicycleff/flutter-unity-arkit-demo
BSD 3-Clause "New" or "Revised" License
2.13k stars 518 forks source link

AndroidJavaProxy breaks with Unity 2022.2.0+ when using fuw-2022.2.0 #836

Closed Jinl21 closed 7 months ago

Jinl21 commented 1 year ago

Describe the bug There was a change in Unity 2022.1.7 to 2022.2.0+ in AndroidJNI, which now requires a reference to mUnityPlayer when creating an AndroidJavaProxy:

public static IntPtr CreateJavaProxy (AndroidJavaProxy proxy)
{
    GCHandle value = GCHandle.Alloc (proxy);
    try {
        return _AndroidJNIHelper.CreateJavaProxy (Permission.GetActivity ().Get<AndroidJavaObject> ("mUnityPlayer").GetRawObject (), GCHandle.ToIntPtr (value), proxy);
    } catch {
        value.Free ();
        throw;
    }
}

Note that Permissions.GetActivity() will return the currentActivity from a UnityPlayer object. When the FlutterUnityWidget is used to launch Unity (see CustomUnityPlayer.kt) it will initialise UnityPlayer with MainActivity. Hence, CreateJavaProxy will fail to find a reference to mUnityPlayer and log the following error:

Non-fatal Exception: java.lang.Exception: AndroidJavaException : java.lang.NoSuchFieldError: no "Ljava/lang/Object;" field "mUnityPlayer" in class "Lcom/example/app/MainActivity;" or its superclasses
       at com.unity3d.player.UnityPlayer.nativeRender(com.unity3d.player.UnityPlayer)
       at com.unity3d.player.UnityPlayer.-$$Nest$mnativeRender(com.unity3d.player.UnityPlayer)
       at com.unity3d.player.UnityPlayer$C$a.handleMessage(com.unity3d.player.UnityPlayer$C$a)
       at android.os.Handler.dispatchMessage(android.os.Handler)
       at android.os.Looper.loopOnce(android.os.Looper)
       at android.os.Looper.loop(android.os.Looper)
       at com.unity3d.player.UnityPlayer$C.run(com.unity3d.player.UnityPlayer$C)
       at UnityEngine.AndroidJNISafe.CheckException(AndroidJNISafe.cs:24)
       at UnityEngine.AndroidJNISafe.GetFieldID(AndroidJNISafe.cs:87)
       at UnityEngine._AndroidJNIHelper.GetFieldID(AndroidJava.cs:1614)
       at UnityEngine.AndroidJNIHelper.GetFieldID(AndroidJNI.bindings.cs:91)
       at UnityEngine._AndroidJNIHelper.GetFieldID[ReturnType](AndroidJava.cs:1534)
       at UnityEngine.AndroidJNIHelper.GetFieldID[FieldType](AndroidJNI.bindings.cs:198)
       at UnityEngine.AndroidJavaObject._Get[FieldType](AndroidJava.cs:630)
       at UnityEngine.AndroidJavaObject.Get[FieldType](AndroidJava.cs:345)
       at UnityEngine.AndroidJNIHelper.CreateJavaProxy(AndroidJNI.bindings.cs:106)

Many Unity plugins use an AndroidJavaProxy interface to callback to C# methods from Java. There are two proposed workarounds to this problem both with caveats:

To Reproduce Steps to reproduce the behavior:

  1. Create an empty Unity 2022.2.0 project, consume fuw-2022.2.0
  2. add an AndroidJavaProxy to the project
  3. initialise the proxy on a Method exposed to Flutter
  4. Create an empty Flutter 3.7.12 project, consume FUW 2022.2.0
  5. call the Method in Flutter using sendMessage
  6. run the project and observe

Expected behavior No error logs should be thrown when any Native code in unity attempts to access mUnityPlayer. Unity assumes this reference is always available.

Unity (please complete the following information):

timbotimbo commented 1 year ago

This is the first i've seen of this, but apparently you already found someone on Stackoverflow having the same issue. Thanks for getting it into an issue here with all the included info

You mention

Unity 2022.1.7 to 2022.2.0+

Do you mean somewhere between these versions, or in all these versions? The stackoverflow post even mentions 2021.3.19.

The changelog for 2022.2.0 is the only one that I can find that contains something that looks like it might be the cause.

Android: Added: Classes AndroidJNI, AndroidJNIHelper, AndroidJavaObject, AndroidJavaClass, AndroidJavaProxy have new methods for more efficient low-level interop with Java. See API docs for full list.

The only other mentions of AndroidJavaProxy that I see are bugfixes, one applied in (2022.2.0a4, 2022.1.7, 2021.3.6, 2020.3.3 and 2019.4.39) and another in (2020.3.46 and 2022.2.4)

Do you have an example of any Unity plugins that you've used with FUW that run into this issue?

I help to somewhat maintain this project, but I'm not very experienced in native Android development. Any existing plugin that triggers this could simplify the reproduction.

Jinl21 commented 1 year ago

Hi, Thanks for getting back to me so quickly. Unity 2022.1.7 does not present this issue when used with fuw-2022.1.7+. I have only experienced this issue in 2022.2.0+ and have not tested 2021.3.19. However, if 2021.3.0+ is on LTS then they may have applied this change. Checking UnityCsReference 2021.3.19 presents the same offending line with Permission.GetActivity().Get<AndroidJavaObject>("mUnityPlayer"). Hence, unity have moved towards relying upon the mUnityPlayer reference in some of their AndroidJNI codebase.

I don't currently have an example project at hand as the plugin I am using is a proprietary bluetooth plugin. However, a plugin is not required to reproduce this issue. Simply calling Permission.GetActivity().Get<AndroidJavaObject>("mUnityPlayer") will cause the aforementioned error log to be printed in an empty unity project on 2022.2.0 exported to flutter.

Im pretty well versed in Android, Unity, Flutter and open to discuss further to help solve the issue. I have tested working around the issue by holding a reference mUnityPlayer in MainActivity retrieved from UnityPlayerUtils.kt. This works but it's rather hacky in it's current form.

timbotimbo commented 1 year ago

Did some more digging in the CsReference to find the scope of this.

The change was applied in

With the offending line you mentioned

- return _AndroidJNIHelper.CreateJavaProxy(GCHandle.ToIntPtr(handle), proxy);
+ return _AndroidJNIHelper.CreateJavaProxy(Permission.GetActivity().Get<AndroidJavaObject>("mUnityPlayer").GetRawObject(), GCHandle.ToIntPtr(handle), proxy);

Unity 2023 received a different change.

However Unity 2023 breaks compatibiity with this plugin, which makes things harder to test.

I also see the same issue popping up in more places.

Jinl21 commented 1 year ago

Interesting, I did happen to stumble across the Ad Mobile one but forgot to link this ticket to it. Im unable to locate the implementation of the extern method for Unity 2023.1.0b3 and am uncertain if it will present the same issue.

I suspect that the fix to this problem will be to add an mUnityPlayer reference to the MainActivity and add @JvmStatic (for java access) to the unityPlayer property in UnityPlayerUtils.kt. This could then be used to populate mUnityPlayer prior to launching Unity. The best place I can think of to perform this would be the Build script export to flutter action (ie. DoAndroidBuild). Implementations for Kotlin or Java should be added to introduce the correct syntax for mUnityPlayer in MainActivity depending upon the extension type.

TonyHoyleRps commented 1 year ago

"I have tested working around the issue by holding a reference mUnityPlayer in MainActivity retrieved from UnityPlayerUtils.kt. This works but it's rather hacky in it's current form."

Can you post the code for this? I tried but got nowhere. A hacky fix will do for now.. have to actually release something..

Jinl21 commented 1 year ago

Fair, I can post the solution but think it can be cleanup quite a bit if it's implemented at the plugin level rather than the application level. For context, our MainActivity is in java so my solution will present some additional steps that kotlin applications can skip. These additional steps are to add kotlin/java mixin so that you can expose the unityPlayer property in UnityPlayerUtils.kt to java methods. For reference, our gradle files use new plugin block provided by Gradle 7.2.

In android/app/build.gradle add the kotlin plugin, kotlin source sets, unity-classes and flutter-unity-widget native classes:

plugins {
+    id 'kotlin-android'
    id "com.android.application"
}

android {
+     sourceSets {
+         main {
+             java.srcDirs += 'src/main/java'
+             kotlin.srcDirs += 'src/main/kotlin'
+        }
+    } 
}

dependencies {
    implementation project(':unityLibrary')

+    implementation(name: 'unity-classes', ext:'jar')
+    implementation project(':flutter_unity_widget')
}

Then you will need to add the following file android/app/src/main/kotlin/UnityUtils.kt with:

+ package com.company.app
+ 
+ import com.unity3d.player.UnityPlayer
+ import com.xraph.plugin.flutter_unity_widget.UnityPlayerUtils;
+ 
+ class UnityUtils {
+     companion object {
+         @JvmStatic
+         fun getUnityPlayer(): UnityPlayer {
+             return UnityPlayerUtils.unityPlayer as UnityPlayer
+         }
+     }
+ }

This file exposes unityPlayer to java with @JvmStatic annotation.

Then add the following to android/src/main/java/MainActivity.java:

+ import com.unity3d.player.UnityPlayer;

Public class MainActivity extends FlutterActivity {
+     public UnityPlayer mUnityPlayer;    // referenced by Unity native

+    // expose to flutter with MethodChannel
+    void setupUnityPlayerReference(MethodChannel.Result result) {
+        mUnityPlayer = UnityUtils.getUnityPlayer();
+        result.success(true);
+    }

The last step is to call setupUnityPlayerReference() using flutter MethodChannel in onUnityCreated() callback within Flutter, ie:

void onUnityCreated(controller) async {
    await controller.isReady();
+    await MethodChannelService.setupUnityPlayerReference();

I have not included the MethodChannel boilerplate call to setupUnityPlayerReference() as this is plugin implementation dependent. I would also add that you should either add this method to iOS in AppDelegate.swift and make it do nothing OR use Platform.isAndroid within flutter to check if the setupUnityPlayerReference() should be called or not. This will prevent breaking iOS.

You will need to ensure you have a android/settings.gradle file with pluginManagement block, ie:

pluginManagement {
    repositories {
        gradlePluginPortal()
        jcenter()
        google()
        mavenCentral()
    }

Or modify the solution for early Gradle versions by using apply plugin instead of the plugin { block.

Hope this workaround makes sense but feel free to pry for more information is required.

TonyHoyleRps commented 1 year ago

After some hacking and headscratching I got a kotlin solution similar to yours.

I found you have to define the object as java.lang.Object otherwise the native code simply can't find it, even though it's exported properly (checked with a class decompiler).

MainActivity.kt

import com.xraph.plugin.flutter_unity_widget.UnityPlayerUtils
import com.unity3d.player.UnityPlayer
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
...

    @JvmField
    var mUnityPlayer: java.lang.Object? = null

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "unity.hack").setMethodCallHandler {
                call, result ->
            if (call.method == "init") {
                mUnityPlayer = UnityPlayerUtils.unityPlayer as java.lang.Object?
                result.success(0);
            }
        }
    }

main.dart

  void onUnityCreated(UnityWidgetController? controller) async {
...
    if(Platform.isAndroid) {
      const MethodChannel('unity.hack').invokeMethod("init");
    }
...
  }

build.gradle

dependencies {
...
    implementation(name: 'unity-classes', ext:'jar')
...
}
Jinl21 commented 1 year ago

Type definition make sense as Kotlin is weird and wonderful when it comes to object decompilation. I know that Unity expects the object to be of type java.lang.Object. Other than that, the Kotlin only solution looks great.

I think perhaps the Build script for Unity could handle adding the reference to MainActivity in DoAndroidBuild(). Simple enough to add the right syntax for the reference based on the extension it finds on MainActivity. There may need to be search paths (ie. src/main/java/ and src/main/kotlin/) and Im not sure if this need to be configurable for the developer.

The FlutterUnityWidget could then populate this reference once the Unity controller has been created, which Im hoping that this returns prior to any User Awake() or Start() calls occur.

TonyHoyleRps commented 1 year ago

I wonder if it might be easier just to make a FlutterUnityActivity base class.. then it's just changing one line of code in MainActivity and that can be documented, rather than have the DoAndroidBuild modify things outside its normal build path.

Jinl21 commented 1 year ago

You would have to sub-class the FlutterUnityActivity in MainActivity. This may prevent other developers from sub-classing their own MainActivity implementations. However, it would make the assign operation of the mUnityPlayer reference cleaner and it also makes sense that there's an expectation to rely on a UnityActivity base-class.

timbotimbo commented 1 year ago

I've just answered my own request for a known plugin that reproduces this. This triggered while looking for an ARFoundation issue, using the ARFoundation samples project.

I/Unity   (24343): UnityEngine.XR.ARSubsystems.XRSessionSubsystem:Update(XRSessionUpdateParams) (at .\Library\PackageCache\com.unity.xr.arfoundation@5.0.6\Runtime\ARSubsystems\SessionSubsystem\XRSessionSubsystem.cs:128)
I/Unity   (24343): UnityEngine.XR.ARFoundation.ARSession:Update() (at .\Library\PackageCache\com.unity.xr.arfoundation@5.0.6\Runtime\ARFoundation\ARSession.cs:416)
I/Unity   (24343):
E/Unity   (24343): AndroidJavaException: java.lang.NoSuchFieldError: no "Ljava/lang/Object;" field "mUnityPlayer" in class "Lcom/xraph/plugin/flutter_unity_widget_example/MainActivity;" or its superclasses
timbotimbo commented 1 year ago

@Jinl21 @TonyHoyleRps Can you try this branch for a possbile fix on the plugin side?

It took quite some stackoverflow pages to figure out how Kotlin works, but ARFoundation works again on my end.

I'd love it if you could test this and possibly add suggetions for improvements.

Changes: I added the property in MainActivity like mentioned above, and assign it from the plugin using reflection.

MainActivity.kt

@JvmField
    var mUnityPlayer: java.lang.Object? = null

UnityPlayerUtils.kt

val unityActivityProperty = activity!!::class.memberProperties.firstOrNull {it.name == "mUnityPlayer"}
if (unityActivityProperty !== null && unityActivityProperty is KMutableProperty<*>) {
  (unityActivityProperty as KMutableProperty<*>)?.setter?.call(activity!!, (unityPlayer as java.lang.Object?))
}

TODO:

TonyHoyleRps commented 1 year ago

It seems mUnityPlayer is optimised out in release builds, so it got through all my testing then failed when we tried to push it out.

Seems this works in build.cs (might not be build.cs any more, I'm using a modified version of fuw & I note there have been filename changes)..

        proguardText += "\n-keepclassmembers class **.MainActivity { *; }";

'*' is too much really.. I couldn't work out how to successfully just add mUnityPlayer in that line though.

timbotimbo commented 1 year ago

This proguard rule worked for me.

-keep class * extends io.flutter.embedding.android.FlutterActivity {
    public java.lang.Object mUnityPlayer;
}

A java MainActivity seems to work as well with public Object mUnityPlayer;, which won't need any import statements.

[Update]

Never mind the proguard rules. Adding @Keep in the activity seems to do the trick, which avoids possible issues with the build script being outdated.

package com.xraph.plugin.flutter_unity_widget_example

import io.flutter.embedding.android.FlutterActivity
import androidx.annotation.Keep;

class MainActivity: FlutterActivity() {

    @Keep @JvmField 
    var mUnityPlayer: java.lang.Object? = null 
}
Jinl21 commented 1 year ago

@timbotimbo Happy to report that the fix works perfectly when including @Keep annotation. In my scenario, I tested this by adding the following to my MainActivity.java:

package com.example.app;

Public class MainActivity extends FlutterActivity {

     /* note: set by reflections from FUW */
     @Keep
     public Object mUnityPlayer;

Tested for debug/release build variants. Seems just docs and general bug testing is left.

One interesting thing to note. Reflections is slow, if you don't have a view present in flutter while loading unity a white screen will appear for a few seconds.

timbotimbo commented 1 year ago

@Jinl21

One interesting thing to note. Reflections is slow, if you don't have a view present in flutter while loading unity a white screen will appear for a few seconds.

Can you give an example of how you initialize unity that causes this?

Maybe moving the reflection call to be after the onReady callback might make a difference.

Jinl21 commented 1 year ago

@timbotimbo, this is how im loading unity:

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      body: UnityWidget(
        fullscreen: true,
        hideStatus: true,
        unloadOnDispose: false,
        onUnityCreated: onUnityCreated,
        onUnityMessage: onUnityMessage,
        onUnitySceneLoaded: onUnitySceneLoaded,
        useAndroidViewSurface: true,
      ),
    );
  }

with the following on create function:

  void onUnityCreated(controller) async {
    this._unityWidgetController = controller;

    await _unityWidgetController.isReady();
    _isExiting = false;

    // unfreeze hack (https://github.com/juicycleff/flutter-unity-view-widget/issues/538)
    if (Platform.isAndroid) {
      _unityWidgetController.postMessage("GameLoader", "ReloadScene", "");
    } else {
      initializeGame();
    }
}

  void onUnitySceneLoaded(SceneLoaded? scene) async {
    print('Received scene loaded from unity: ${scene?.name}');
    print('Received scene loaded from unity buildIndex: ${scene?.buildIndex}');

    if (Platform.isAndroid && scene?.buildIndex == 0 && !_isExiting) {
      // unfreeze hack (https://github.com/juicycleff/flutter-unity-view-widget/issues/538)
      _unityWidgetController.pause()?.then((_) {
        _unityWidgetController.resume();
      });

      initializeGame();
    }
  }

I'm using the unfreeze hack posted in issue 538 to reload Unity instead of disposing/creating again as I recall there is an issue when disposing.

This method did not show a white screen for 3 seconds prior to the change onto this branch.

I think it's best to keep the refections call following unity creation as there may be use-cases of mUnityPlayer in Awake() methods.

akhil-tlm-sg commented 1 year ago

FATAL EXCEPTION: main Process: com.xraph.plugin.flutter_unity_widget_example, PID: 31477 java.lang.Error: FATAL EXCEPTION [main] Unity version : 2022.3.3f1 Device model : OnePlus EB2101 Device fingerprint: OnePlus/OnePlusNordCE_IND/OnePlusNordCE:12/RKQ1.211119.001/R.202301101727:user/release-keys CPU supported ABI : [arm64-v8a, armeabi-v7a, armeabi] Build Type : Release Scripting Backend : IL2CPP Libs loaded from : lib/arm64 Strip Engine Code : true Caused by: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback; at java.lang.Class.getDeclaredFields(Native Method) at kotlin.reflect.jvm.internal.impl.descriptors.runtime.structure.ReflectJavaClass.getFields(ReflectJavaClass.kt:87) at kotlin.reflect.jvm.internal.impl.descriptors.runtime.structure.ReflectJavaClass.getFields(ReflectJavaClass.kt:29) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.ClassDeclaredMemberIndex.(DeclaredMemberIndex.kt:53) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaClassMemberScope.computeMemberIndex(LazyJavaClassMemberScope.kt:71) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaClassMemberScope.computeMemberIndex(LazyJavaClassMemberScope.kt:63) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope$declaredMemberIndex$1.invoke(LazyJavaScope.kt:72) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope$declaredMemberIndex$1.invoke(LazyJavaScope.kt:72) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaClassMemberScope.computePropertyNames(LazyJavaClassMemberScope.kt:861) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope$propertyNamesLazy$2.invoke(LazyJavaScope.kt:260) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope$propertyNamesLazy$2.invoke(LazyJavaScope.kt:260) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527) at kotlin.reflect.jvm.internal.impl.storage.StorageKt.getValue(storage.kt:42) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope.getPropertyNamesLazy(LazyJavaScope.kt:260) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope.getVariableNames(LazyJavaScope.kt:264) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope.getNonDeclaredVariableNames(DeserializedClassDescriptor.kt:348) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation$variableNames$2.invoke(DeserializedMemberScope.kt:262) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation$variableNames$2.invoke(DeserializedMemberScope.kt:261) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527) at kotlin.reflect.jvm.internal.impl.storage.StorageKt.getValue(storage.kt:42) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation.getVariableNames(DeserializedMemberScope.kt:261) 2023-08-14 19:43:22.260 31477-31477 AndroidRuntime com....flutter_unity_widget_example E at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation.addFunctionsAndPropertiesTo(DeserializedMemberScope.kt:349) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope.computeDescriptors(DeserializedMemberScope.kt:115) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope$allDescriptors$1.invoke(DeserializedClassDescriptor.kt:268) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope$allDescriptors$1.invoke(DeserializedClassDescriptor.kt:267) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope.getContributedDescriptors(DeserializedClassDescriptor.kt:278) at kotlin.reflect.jvm.internal.impl.resolve.scopes.ResolutionScope$DefaultImpls.getContributedDescriptors$default(ResolutionScope.kt:50) at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.getMembers(KDeclarationContainerImpl.kt:56) at kotlin.reflect.jvm.internal.KClassImpl$Data$declaredNonStaticMembers$2.invoke(KClassImpl.kt:162) at kotlin.reflect.jvm.internal.KClassImpl$Data$declaredNonStaticMembers$2.invoke(KClassImpl.kt:162) at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:93) at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) at kotlin.reflect.jvm.internal.KClassImpl$Data.getDeclaredNonStaticMembers(KClassImpl.kt:162) at kotlin.reflect.jvm.internal.KClassImpl$Data$allNonStaticMembers$2.invoke(KClassImpl.kt:171) at kotlin.reflect.jvm.internal.KClassImpl$Data$allNonStaticMembers$2.invoke(KClassImpl.kt:171) at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:93) at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) at kotlin.reflect.jvm.internal.KClassImpl$Data.getAllNonStaticMembers(KClassImpl.kt:171) at kotlin.reflect.full.KClasses.getMemberProperties(KClasses.kt:148) at com.xraph.plugin.flutter_unity_widget.UnityPlayerUtils$Companion.createUnityPlayer(UnityPlayerUtils.kt:74) at com.xraph.plugin.flutter_unity_widget.FlutterUnityWidgetController.createPlayer(FlutterUnityWidgetController.kt:288) at com.xraph.plugin.flutter_unity_widget.FlutterUnityWidgetController.(FlutterUnityWidgetController.kt:70) at com.xraph.plugin.flutter_unity_widget.FlutterUnityWidgetBuilder.build(FlutterUnityWidgetBuilder.kt:16) at com.xraph.plugin.flutter_unity_widget.FlutterUnityWidgetFactory.create(FlutterUnityWidgetFactory.kt:34) at io.flutter.plugin.platform.PlatformViewsController.createPlatformView(PlatformViewsController.java:510) at io.flutter.plugin.platform.PlatformViewsController$1.createForTextureLayer(PlatformViewsController.java:191) at io.flutter.embedding.engine.systemchannels.PlatformViewsChannel$1.create(PlatformViewsChannel.java:128) at io.flutter.embedding.engine.systemchannels.PlatformViewsChannel$1.onMethodCall(PlatformViewsChannel.java:55) at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:258) at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295) at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:322) at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(Unknown Source:12) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) 2023-08-14 19:43:22.260 31477-31477 AndroidRuntime com....flutter_unity_widget_example E at android.os.Looper.loopOnce(Looper.java:233) at android.os.Looper.loop(Looper.java:344) at android.app.ActivityThread.main(ActivityThread.java:8212) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:584) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1034) Caused by: java.lang.ClassNotFoundException: Didn't find class "android.window.OnBackInvokedCallback" on path: DexPathList[[zip file "/data/app/CfTw7WWSgtM9oKIHtpgJXA==/com.xraph.plugin.flutter_unity_widget_example-VB_YXcb5GijQ-t6z2hASZA==/base.apk"],nativeLibraryDirectories=[/data/app/CfTw7WWSgtM9oKIHtpgJXA==/com.xraph.plugin.flutter_unity_widget_example-VB_YXcb5GijQ-t6z2hASZA==/lib/arm64, /data/app/~~CfTw7WWSgtM9oKIHtpgJXA==/com.xraph.plugin.flutter_unity_widget_example-VB_YXcb5GijQ-t6z2hASZA==/base.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64]] at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:259) at java.lang.ClassLoader.loadClass(ClassLoader.java:379) at java.lang.ClassLoader.loadClass(ClassLoader.java:312)

@timbotimbo I am running into this error : Caused by: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback; And not able to access the classmemberproperties with reflection from the main activity.

Did you encounter this ? I was getting the same error on my project, so i cloned your branch and still have the same problem. Can you please help debug ?

timbotimbo commented 1 year ago

This branch is an alternative approach because I wasn't satisfied with the reflection performance.
This one is activity based like what was suggested before in this issue.

I defined both an Activity and an Interface: FlutterUnityActivity and IFlutterUnityAcitivity. UnityPlayerUtils checks if the activity implements either of these and tries to set the unityplayer property.

People that use the default MainActivity can simply inherit it.

// MainActivity.kt

//  Needs implementation project(':flutter_unity_widget') in app/build.gradle
+ import com.xraph.plugin.flutter_unity_widget.FlutterUnityActivity;

+ class MainActivity: FlutterUnityActivity() {
- class MainActivity: FlutterActivity() {

}

Anyone that already inherits a different activity can use the interface

//  Needs implementation project(':flutter_unity_widget') in app/build.gradle
import com.xraph.plugin.flutter_unity_widget.IFlutterUnityActivity;

class MainActivity: CustomActivity(), IFlutterUnityActivity {
    @JvmField 
    var mUnityPlayer: java.lang.Object? = null;

    override fun setUnityPlayer(unityPlayer: java.lang.Object?) {
        mUnityPlayer = unityPlayer;
    }
}

Most users will only need the first one, which is a simple change to the MainActivity. Without reflection this also survives release builds without any need for keep annotations or proguard rules.

I only need to test if a Java MainActivity can inherit this without any problems.

@akhil-tlm-sg I haven't seen your error before and I can't reproduce it either. Maybe you can try this new branch instead.

Jinl21 commented 1 year ago

@timbotimbo able to reproduce @akhil-tlm-sg bug when running on an emulator:

E/AndroidRuntime( 9420): FATAL EXCEPTION: main
E/AndroidRuntime( 9420): Process: com.company.app.dev, PID: 9420
E/AndroidRuntime( 9420): java.lang.Error: FATAL EXCEPTION [main]
E/AndroidRuntime( 9420): Unity version     : 2022.2.21f1
E/AndroidRuntime( 9420): Device model      : Google sdk_gphone64_arm64
E/AndroidRuntime( 9420): Device fingerprint: google/sdk_gphone64_arm64/emulator64_arm64:12/S2B1.211112.006/7934767:userdebug/dev-keys
E/AndroidRuntime( 9420): CPU supported ABI : [arm64-v8a]
E/AndroidRuntime( 9420): Build Type        : Release
E/AndroidRuntime( 9420): Scripting Backend : IL2CPP
E/AndroidRuntime( 9420): Libs loaded from  : lib/arm64
E/AndroidRuntime( 9420): Strip Engine Code : true
E/AndroidRuntime( 9420): 
E/AndroidRuntime( 9420): Caused by: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;
E/AndroidRuntime( 9420):    at java.lang.Class.getDeclaredFields(Native Method)
E/AndroidRuntime( 9420):    at kotlin.reflect.jvm.internal.impl.descriptors.runtime.structure.ReflectJavaClass.getFields(ReflectJavaClass.kt:87)
E/AndroidRuntime( 9420):    at kotlin.reflect.jvm.internal.impl.descriptors.runtime.structure.ReflectJavaClass.getFields(ReflectJavaClass.kt:29)
//........
com.xraph.plugin.flutter_unity_widget.UnityPlayerUtils$Companion.createUnityPlayer(UnityPlayerUtils.kt:74)

I imagine using FlutterUnityActivity instead of reflections will probably solve this bug. Will try and test either today or tomorrow.

Jinl21 commented 1 year ago

@timbotimbo Can confirm that android_java_proxy_2 branch solves the reflections bug reported by @akhil-tlm-sg during my test on the emulator with Google sdk_gphone64_arm64.

timbotimbo commented 7 months ago

An update on some Unity changes.
They modified the same line again in some newer Unity versions.

2021.3.21

- return _AndroidJNIHelper.CreateJavaProxy(Permission.GetActivity().Get<AndroidJavaObject>("mUnityPlayer").GetRawObject(), GCHandle.ToIntPtr(handle), proxy);
+ return _AndroidJNIHelper.CreateJavaProxy(Common.GetActivity().Get<AndroidJavaObject>("mUnityPlayer").GetRawObject(), GCHandle.ToIntPtr(handle), proxy);

2022.3.19

- return _AndroidJNIHelper.CreateJavaProxy(Permission.GetActivity().Get<AndroidJavaObject>("mUnityPlayer").GetRawObject(), GCHandle.ToIntPtr(handle), proxy);
+ return _AndroidJNIHelper.CreateJavaProxy(AndroidApp.UnityPlayerRaw, GCHandle.ToIntPtr(handle), proxy);

I tried running ARFoundation on these versions and I no longer get the error.

In short this bug now affects:

Jinl21 commented 7 months ago

@timbotimbo This bug fix should solve those refactors assuming in both cases the name of the object is mUnityPlayer. We have have extensively tested this bug fix and been running it in production for approximately 6 months. It seems fairly stable. Curious to know the timeline to merge this fix to release?

timbotimbo commented 7 months ago

I merged it today.
Although I have no clue when the next plugin update is released.

Because a lack of active contributers, I usually have to wait months for a PR to get approved.

ArthurTelles commented 7 months ago

This proguard rule worked for me.

-keep class * extends io.flutter.embedding.android.FlutterActivity {
    public java.lang.Object mUnityPlayer;
}

A java MainActivity seems to work as well with public Object mUnityPlayer;, which won't need any import statements.

[Update]

Never mind the proguard rules. Adding @Keep in the activity seems to do the trick, which avoids possible issues with the build script being outdated.

package com.xraph.plugin.flutter_unity_widget_example

import io.flutter.embedding.android.FlutterActivity
import androidx.annotation.Keep;

class MainActivity: FlutterActivity() {

    @Keep @JvmField 
    var mUnityPlayer: java.lang.Object? = null 
}

Hey @timbotimbo I'm currently trying to implemented the updated solution in one of my projects. Can you explain how and where can I add this class to my current project?

timbotimbo commented 7 months ago

@ArthurTelles

That quote looks like the first version that I didn't end uo using.

Can you check the readme in the current master branch of this repo?

Android setup step 3 should explain it.
Alternatively the MainActivity.kt in the example folder also implements it.

ArthurTelles commented 7 months ago

@ArthurTelles

That quote looks like the first version that I didn't end uo using.

Can you check the readme in the current master branch of this repo?

Android setup step 3 should explain it. Alternatively the MainActivity.kt in the example folder also implements it.

Hey @timbotimbo, thank you so much for the quick response! I tried following the steps you suggested, but unfortunately, I'm encountering an error when running flutter run.

...android/app/src/main/kotlin/com/example/flutter_unity/MainActivity.kt: (1, 46): Unresolved reference: FlutterUnityActivity

...

FAILURE: Build failed with an exception.

What went wrong:
Execution failed for task ':flutter_unity_widget:compileDebugKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction
   > Compilation error. See log for more details

do you have any further advice?

Jinl21 commented 7 months ago

@ArthurTelles Double check you have implementation project(':flutter_unity_widget') in your app's android/app/build.gradle. Then make sure you have the latest ref for flutter_unity_widget in pubspec.lock when calling flutter pub get. You would need to use master as the ref in your pubspec.yaml for flutter_unity_widget. The class should be available by this point as it's in the latest ref of master: https://github.com/juicycleff/flutter-unity-view-widget/blob/master/android/src/main/kotlin/com/xraph/plugin/flutter_unity_widget/FlutterUnityActivity.kt

ArthurTelles commented 7 months ago

@Jinl21 Using the master as the ref did the trick! Thanks a lot for the help!

hafizramiz commented 7 months ago

Hello i have created a project with flutter and unity. Added to github. After from other machine pulled and tried to run. It is not working. How can i run it from other computer. By the way in this computer doesnt have unity. Only have flutter

timbotimbo commented 7 months ago

@hafizramiz this doesnt look related to this issue. Please make a new one for new questions.

If you run it on a different pc, you still need the correct ndk installed and setup in local.properties.

hafizramiz commented 7 months ago

It is not related to ndk. I have tried correct ndk. There are a lot of Unith/Hub/ ndk path In unityLibrary . Should i change all of them?

Jinl21 commented 7 months ago

@hafizramiz this is defiantly off topic but it's possible to achieve what you want. Note: unity flutter widget doesnt build well cross-platform because UaaL doesnt build well cross-platform (just incase this is what you are doing). You need to manage NDK paths which are typically exported as absolute file paths in the unityLibrary android gradle project. If your compiling cross-platform you need to substitute the IL2CPP backend for the platform you are compiling on. There are many other nitty gritty decisions when sharing builds between computers depending upon your project.

dibenedetto commented 2 months ago

@timbotimbo thank you for your explainations, but i am still experiencing errors. i downgraded to flutter 3.19.6, and i am using unity 2022.3.4f1 i added the mUnityPlayer patch, but getting those errors:

E/Unity ( 2976): NullReferenceException: Object reference not set to an instance of an object. E/Unity ( 2976): at UnityEngine.AndroidJNIHelper.CreateJavaProxy (UnityEngine.AndroidJavaProxy proxy) [0x00018] in \home\bokken\build\output\unity\unity\Modules\AndroidJNI\AndroidJNI.bindings.cs:106 E/Unity ( 2976): at UnityEngine.AndroidJavaProxy.GetRawProxy () [0x00062] in \home\bokken\build\output\unity\unity\Modules\AndroidJNI\AndroidJava.cs:223 E/Unity ( 2976): at UnityEngine._AndroidJNIHelper.CreateJNIArgArray (System.Object[] args, System.Span1[T] ret) [0x00280] in \home\bokken\build\output\unity\unity\Modules\AndroidJNI\AndroidJava.cs:1203 E/Unity ( 2976): at UnityEngine.AndroidJNIHelper.CreateJNIArgArray (System.Object[] args, System.Span1[T] jniArgs) [0x0003a] in \home\bokken\build\output\unity\unity\Modules\AndroidJNI\AndroidJNI.bindings.cs:133 E/Unity ( 2976): at UnityEngine.AndroidJavaObject._Call (System.IntPtr methodID, System.Object[] args) [0x0002e] in \home\bokken\build\output\unity\unity\Modules\AndroidJNI\AndroidJava.cs:550 E/Unity ( 2976): at UnityEngine.AndroidJavaObject._Call (System.String

can you figure out what's going on here?

everything seems to be so frustrating...

timbotimbo commented 2 months ago

Update your Unity version.

The problems in this issue are fixed since Unity 2022.3.18, and the current version is already at 2022.3.39. The mUnityplayer fix isn't needed anymore with these versions either.

If you still run into errors, open a new issue with a full description of the problem.

dibenedetto commented 2 months ago

Update your Unity version.

The problems in this issue are fixed since Unity 2022.3.18, and the current version is already at 2022.3.39. The mUnityplayer fix isn't needed anymore with these versions either.

If you still run into errors, open a new issue with a full description of the problem.

it worked! thanks a lot!