Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blank screen on initializing UnityWidget, iOS #540

Open
jafitz26 opened this issue Feb 16, 2022 · 20 comments
Open

Blank screen on initializing UnityWidget, iOS #540

jafitz26 opened this issue Feb 16, 2022 · 20 comments

Comments

@jafitz26
Copy link

jafitz26 commented Feb 16, 2022

On first running the UnityWidget, the Unity Game's sound plays, but a blank screen is pulled up, on iOS only. This screen is the same color as my main app's loading screen.

If I hot restart the app from this screen, the Unity game's sound continues playing behind the main app, and after navigating back to the UnityWidget, the Unity game runs as intended.

The only work around I've found is automating a pop back to the main app, then rerunning the UnityWidget, which is not a good user experience.

--
Error output:

CrashReporter: initialized
Built from '2021.2/staging' branch, Version '2021.2.11f1 (e50cafbb4399)', Build type 'Release', Scripting Backend 'il2cpp'
MemoryManager: Using 'Default' Allocator.
[UnityMemory] Configuration Parameters - Can be set up in boot.config
"memorysetup-bucket-allocator-granularity=16"
"memorysetup-bucket-allocator-bucket-count=8"
"memorysetup-bucket-allocator-block-size=4194304"
"memorysetup-bucket-allocator-block-count=1"
"memorysetup-main-allocator-block-size=16777216"
"memorysetup-thread-allocator-block-size=16777216"
"memorysetup-gfx-main-allocator-block-size=16777216"
"memorysetup-gfx-thread-allocator-block-size=16777216"
"memorysetup-cache-allocator-block-size=4194304"
"memorysetup-typetree-allocator-block-size=2097152"
"memorysetup-profiler-bucket-allocator-granularity=16"
"memorysetup-profiler-bucket-allocator-bucket-count=8"
"memorysetup-profiler-bucket-allocator-block-size=4194304"
"memorysetup-profiler-bucket-allocator-block-count=1"
"memorysetup-profiler-allocator-block-size=16777216"
"memorysetup-profiler-editor-allocator-block-size=1048576"
"memorysetup-temp-allocator-size-main=4194304"
"memorysetup-job-temp-allocator-block-size=2097152"
"memorysetup-job-temp-allocator-block-size-background=1048576"
"memorysetup-job-temp-allocator-reduction-small-platforms=262144"
"memorysetup-temp-allocator-size-background-worker=32768"
"memorysetup-temp-allocator-size-job-worker=262144"
"memorysetup-temp-allocator-size-preload-manager=262144"
"memorysetup-temp-allocator-size-nav-mesh-worker=65536"
"memorysetup-temp-allocator-size-audio-worker=65536"
"memorysetup-temp-allocator-size-cloud-worker=32768"
"memorysetup-temp-allocator-size-gfx=262144"
-> applicationDidFinishLaunching()
-> applicationWillEnterForeground()
-> applicationDidBecomeActive()
Unexpected node type.
Unexpected node type.
GfxDevice: creating device client; threaded=1; jobified=0
Initializing Metal device caps: Apple A9 GPU
Initialize engine version: 2021.2.11f1 (e50cafbb4399)
flutter: in onUnityCreated
CrashReporter: No pending report exists at /var/mobile/Containers/Data/Application/D17E09AB-1621-4810-8A8E-EDBB1BE93C4C/Library/Caches/CrashReports/crash-pending.plcrash

@Drarox
Copy link

Drarox commented Feb 16, 2022

Exact same issue here, on iOS only, the UnityWidget is blank but the sound of the Unity game is playing.

I tried to pop back the screen after x seconds and then rerunning the UnityWidget and it's working then like you said, but it's not user friendly.

It was perfectly working a few weeks ago, I'm trying to figure out since when it's not working anymore because I did many upgrades.

What I have tried so far without any results :

  • Updating flutter_unity_widget from 4.2.4 to 2020.3.25
  • Using an older version of Unity (2020.3.20 which was working fine in the past) to be sure it's not related, I'm currently with the latest 2020.3.28

I have also recently updated Flutter from 2.8.1 to 2.10.1, I have not tried yet if this could be related to the issue.

@Drarox
Copy link

Drarox commented Feb 16, 2022

As a workaround for the moment for iOS, I pop the UnityWidget on the first opening in the onUnityCreated and then relaunch it 1 second after it's closed (don't relaunch it immediately or the same issue will still be there). This causes for the user only a blinking the first time opening, but it's working then.

@steven-voon
Copy link

@Drarox i ran into the same problem, would you mind to share with me your solution on “pop the widget and relaunch it after 1 sec” part ?
I’m from Unity and still new to flutter, I will be very appreciate if you are willing to share with me.

@jafitz26
Copy link
Author

There may be a better way to do this, but my solution to popping the widget and relaunching it was to use two Future.delayed() in onUnityCreated. So something like:

widget.isInit ? Future.delayed(
Duration(milliseconds: 300),
() async {
Navigator.of(context).pop();
Future.delayed(
Duration(milliseconds: 500),
() async {
widget.initCallback();
},
);
},
) : null;

The initCallback() in the enclosing Class pushes the Class with the UnityWidget in it again:

Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => UnityClass(isInit: false, initCallback: initCallback,),
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
));

I also added a loading screen at the top of a Stack that pops after another Future.delayed duration.

@steven-voon
Copy link

@jafitz26 thanks for the sharing, I don’t quite understand the part on widget.isinit and widget.initCallback is it possible for you to share the whole code for me to learn about it?

@jafitz26
Copy link
Author

Sure, here is the code within the context of the two classes I'm using:

class FlutterPage extends StatefulWidget {
  const FlutterPage({Key? key}) : super(key: key);

  @override
  _FlutterPageState createState() => _FlutterPageState();
}

class _FlutterPageState extends State<FlutterPage> {

  void initCallback(){
    Navigator.push(
        context,
        PageRouteBuilder(
          pageBuilder: (context, animation1, animation2) => UnityPage(isInit: false, initCallback: (){},),
          transitionDuration: Duration.zero,
          reverseTransitionDuration: Duration.zero,
        ));
  }


  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: (){
        Navigator.push(
            context,
            PageRouteBuilder(
              pageBuilder: (context, animation1, animation2) => UnityPage(isInit: true, initCallback: initCallback,),
              transitionDuration: Duration.zero,
              reverseTransitionDuration: Duration.zero,
            ));

      },
      child: Text('Launch'),
    );
  }
}

class UnityPage extends StatefulWidget {
  UnityPage({required this.initCallback, required this.isInit,});

  VoidCallback initCallback;
  bool isInit;

  @override
  _UnityPageState createState() => _UnityPageState();
}

class _UnityPageState extends State<UnityPage> {

  late UnityWidgetController unityWidgetController;

  void _onUnityCreated(controller) async {

    this.unityWidgetController = await controller;

    widget.isInit ? Future.delayed(
      Duration(milliseconds: 300),
          () async {
            await unityWidgetController.pause();
            Navigator.of(context).pop();
        Future.delayed(
          Duration(milliseconds: 500),
              () async {
            widget.initCallback();
          },
        );
      },
    ) : null;
  }

  @override
  Widget build(BuildContext context) {
    return   UnityWidget(
      onUnityCreated: _onUnityCreated,
    );
  }
}

@WellingtonBipo
Copy link

@jafitz26 thanks for the sharing, I don’t quite understand the part on widget.isinit and widget.initCallback is it possible for you to share the whole code for me to learn about it?

Other approach that works for me is remove the UnityWidget for the widget tree.

You can use the same time intervals and instead of navigate between pages you can call setState and with a conditional, remove from the widget tree.

Like this:

bool _maintainUnity = false;

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          if (_maintainUnity) _unityWidget,
        ],
      ),
    );
  }

@steven-voon
Copy link

@jafitz26 thanks for the sharing your code, I had a quick implementation just now and it does work on iPad but something went wrong when it comes to iPhone, i will check on it myself and share it here if I managed to found the cause of it.

@WellingtonBipo thanks for the generous sharing ! Will try your suggestion today,

@Holosynthetic
Copy link

This is an issue related to the flutter_unity_widget iOS framework. If you take a look at the UnityPlayerUtils.swift file, you'll notice the createPlayer method never invokes the callback with the UnityFramework's rootView on first load. It's expecting the UnityReady notification to perform the callback, but it's never posted.

For now, I've changed the method body to the following and it works:

if unityIsInitiallized() && _isUnityReady {
    completed(controller?.rootView)
    return
}

DispatchQueue.main.async {
    // Always keep Flutter window on top
    let flutterLevel = UIWindow.Level.normal.rawValue + 1.0
    UIApplication.shared.keyWindow?.windowLevel = UIWindow.Level(flutterLevel)

    self.initUnity()
    self._isUnityReady = true
    completed(controller?.rootView)
    self.listenAppState()
}

@WellingtonBipo
Copy link

@Holosynthetic thanks very much. It's works fine. The only problem is that this file is inside flutter_unity_widget and if you delete the package and run flutter pub get you have to change the code again.

And to make easy to find the file, follow the path ios/.symlinks/plugins/flutter_unity_widget/ios/Classes/UnityPlayerUtils.swift (flutter_unity_widget folder is a link)
or
.pub-cach/host/pub.dartlang.org/flutter_unity_widget/ios/Classes/UnityPlayerUtils.swift

@bonfry
Copy link

bonfry commented Mar 25, 2022

Hi, I've a similar problem but with Android. I would like understand if solutions writed in this issue work also on Android. Thanks and sorry for my english

@jamesncl
Copy link

jamesncl commented May 28, 2022

@Holosynthetic thanks for the fix, had the exact same issue and this resolved it for me. The code has changed a bit in the latest version, so it's now:

// Create new unity player
    func createPlayer(completed: @escaping (_ view: UIView?) -> Void) {
        if self.unityIsInitiallized() && self._isUnityReady {
            completed(controller?.rootView)
        }
        
        NotificationCenter.default.addObserver(forName: NSNotification.Name("UnityReady"), object: nil, queue: OperationQueue.main, using: { note in
            self._isUnityReady = true
            completed(controller?.rootView)
        })
        
        DispatchQueue.main.async {
            if (sharedApplication == nil) {
                sharedApplication = UIApplication.shared
            }

            // Always keep Flutter window on top
            let flutterUIWindow = sharedApplication?.keyWindow
            flutterUIWindow?.windowLevel = UIWindow.Level(UIWindow.Level.normal.rawValue + 1) // Always keep Flutter window in top
            sharedApplication?.keyWindow?.windowLevel = UIWindow.Level(UIWindow.Level.normal.rawValue + 1)
            
            self.initUnity()
            unity_warmed_up = true
            completed(controller?.rootView)    <--------- ADD THIS LINE
            self.listenAppState()
        }
        
        if unity_warmed_up == true {
            self._isUnityReady = true
            self._isUnityLoaded = true
            completed(controller?.rootView)
        }
    }

@WellingtonBipo I've done a pull request for this change so hopefully @juicycleff can merge it into master and publish a new version, so that you don't have to manually edit the files each time you pub get. Alternatively, just create your own fork and make the change there, then point to your fork in pubspec.yaml. Or you could just use my fork until the PR is merged:

  # TODO switch back to the main flutter_unity_widget repository when a new version published with issue #540 fixed
  # Use fork which includes fix issue #540 (iOS widget not showing on first load)
  flutter_unity_widget:
    git:
      url: https://github.com/jamesncl/flutter-unity-view-widget.git
      # Pin the commit which fixes issue #540 to prevent tracking master which may introduce unexpected changes
      ref: 3acea6c42afb2663c1e306693458d96141587aba

@jamesncl
Copy link

Hi, I've a similar problem but with Android. I would like understand if solutions writed in this issue work also on Android. Thanks and sorry for my english

No this is a fix for iOS only. I suggest making sure you're using the latest version of the package, use Unity 2022.1.0 and double-check you have followed all the setup steps. Also, try using the runImmediately: true flag on UnityWidget, and if you're using a placeholder, try not using a placeholder (I had a problem on Android where the placeholder wasn't removed after load). If problem still persists, open a separate issue with details.

@VivianKuKu
Copy link

VivianKuKu commented Jun 4, 2022

Hello @jamesncl & @WellingtonBipo ,
Can I understand where the UnityPlayerUtils.swift is? I have the same error but couldn't find this file inside my Flutter and Unity folders. Thank you.

Screenshot 2022-06-04 at 23 08 55

@timbotimbo
Copy link
Collaborator

I know this has been fixed in package version 2022.1.1 but I noticed something interesting.

This bug popped up for me on changing a minor Unity version for an older project.

  • Unity 2019.4.23 and package 4.2.5+1 does NOT have this bug.
  • Unity 2019.4.40 and package 4.2.5+1 does have this bug.

Given that this issue was known when using Unity 2020 or 2021, I guess some change got backported to Unity 2019 LTS.
So this bug was actually caused by a Unity change.

@VivianKuKu
UnityPlayerUtils.swift is in the cache for installed packages (where changes will be overridden by pub get, pub clean etc. ).

FLUTTER-INSTALL-LOCATION\.pub-cache\hosted\pub.dartlang.org\flutter_unity_widget-XXX\ios\classes

@AmazeFPig
Copy link

Is this fixed? I still have this issue after I tried 2022.1.7f1, 2022.1.1, 2022.1.1-v2 unitypackages

@AmazeFPig
Copy link

Ok...I checked UnityPlayerUtils.swift file in my project. Somehow some lines are commented and
I edited it exact the same as @jamesncl did, and everything works fine.

@alicanyimaz
Copy link

alicanyimaz commented Dec 15, 2022

Here is my solution

change UnityPlayerUtils.swift -> createPlayer function

func createPlayer(completed: @escaping (_ view: UIView?) -> Void) {

    if self.unityIsInitiallized() && self._isUnityReady {
      
        completed(controller?.rootView)
        
        return
    }

    
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        
        DispatchQueue.main.async {
            // Always keep Flutter window on top
            let flutterLevel = UIWindow.Level.normal.rawValue + 1.0
            UIApplication.shared.keyWindow?.windowLevel = UIWindow.Level(flutterLevel)

            self.initUnity()
            self._isUnityReady = true
            completed(controller?.rootView)
            self.listenAppState()
        }
        
        
        NotificationCenter.default.addObserver(forName: NSNotification.Name("UnityReady"), object: nil, queue: OperationQueue.main, using: { note in
            self._isUnityReady = true
            completed(controller?.rootView)
        })
    } 

}

@kamami
Copy link

kamami commented Jan 10, 2023

Here is my solution

change UnityPlayerUtils.swift -> createPlayer function

func createPlayer(completed: @escaping (_ view: UIView?) -> Void) {

    if self.unityIsInitiallized() && self._isUnityReady {
      
        completed(controller?.rootView)
        
        return
    }

    
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        
        DispatchQueue.main.async {
            // Always keep Flutter window on top
            let flutterLevel = UIWindow.Level.normal.rawValue + 1.0
            UIApplication.shared.keyWindow?.windowLevel = UIWindow.Level(flutterLevel)

            self.initUnity()
            self._isUnityReady = true
            completed(controller?.rootView)
            self.listenAppState()
        }
        
        
        NotificationCenter.default.addObserver(forName: NSNotification.Name("UnityReady"), object: nil, queue: OperationQueue.main, using: { note in
            self._isUnityReady = true
            completed(controller?.rootView)
        })
    } 

}

This works, but the widget seems to overlap the AppBar of the Scaffold now. The back button is missing. Without the fix, the back button is there, but also the white screen.

@J-Dark
Copy link

J-Dark commented Jun 10, 2024

I'm still experiencing this bug with version 2022.2.1 ( tried with unity 2020, 2021 and 2022).
My unity app has multiple scenes:

  • user selects the scene in the Flutter app
  • the page with the widget is pushed with an init scene
  • assets are downloaded and the correct scene is loaded by posting a message

Now, if I load a normal scene, everything works; but if I push an AR scene, I get the blank screen.

The AR scene is actually running: I can hear the correct audio when I point to an AR marker.
If I pop and re-push the page without reverting to the init scene, everything starts working fine.

I tried @jamesncl solution, but as @kamami said in the last message, Flutter UI is now below the unity widget and there's no way to go back.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests