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

WillPopScope on Android Doesn't Work After Opening Keyboard on a Dialog #2752

Open
hilmiyafia opened this issue Sep 20, 2023 · 23 comments
Open
Labels

Comments

@hilmiyafia
Copy link

I have 2 overlay pages on my game.
There is a button on the first page that takes you to the second page.
On the second page there's a button that opens up a dialog that ask you to input a name.
To go back to the first page, I use WillPopScope to detect the back key being pressed.

Current bug behavior

After opening the keyboard to input a name, the WillPopScope doesn't work.
Only if I don't select the TextField (thus not opening the keyboard) the WillPopScope will work.
Just opening the dialog does not cause the error.

Expected behavior

WillPopScope should still work even after opening the keyboard.

Steps to reproduce

Run this program on Android:

import 'package:flutter/material.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';

void main() {
  runApp(MaterialApp(home: Scaffold(body: GameWidget<Engine>.controlled(
    gameFactory: Engine.new,
    initialActiveOverlays: const ["Page 1"],
    overlayBuilderMap: {
      "Page 1": (_, engine) => Page1(engine: engine),
      "Page 2": (_, engine) => Page2(engine: engine),
    },
  ))));
}

class Engine extends FlameGame with ScaleDetector {
  void changePage(String add, String remove) {
    overlays.remove(remove);
    overlays.add(add);
  }
}

class Page1 extends StatelessWidget {
  final Engine engine;
  const Page1({super.key, required this.engine});
  @override
  Widget build(BuildContext context) {
    return Center(child: ElevatedButton(
      onPressed: () => engine.changePage("Page 2", "Page 1"),
      child: const Text("Go to Page 2"),
    ));
  }
}

class Page2 extends StatelessWidget {
  final Engine engine;
  final TextEditingController field = TextEditingController();
  Page2({super.key, required this.engine});
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        engine.changePage("Page 1", "Page 2");
        return false;
      },
      child: Center(child: ElevatedButton(
        onPressed: () {
          showDialog(context: context, builder: (BuildContext context) {
            return AlertDialog(
              title: const Text("Input your name:"),
              content: TextField(controller: field),
              actions: <Widget>[TextButton(
                onPressed: () {
                  print("Hello ${field.text}!");
                  Navigator.pop(context);
                },
                child: const Text("OK"),
              )],
            );
          });
        },
        child: const Text("Show Dialog"),
      )),
    );
  }
}

Flutter doctor output

[√] Flutter (Channel stable, 3.13.0, on Microsoft Windows [Version 10.0.22621.2283], locale en-US)
    • Flutter version 3.13.0 on channel stable at C:\Users\MSI\Documents\Flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision efbf63d9c6 (5 weeks ago), 2023-08-15 21:05:06 -0500
    • Engine revision 1ac611c64e
    • Dart version 3.1.0
    • DevTools version 2.25.0

[√] 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\MSI\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.6+0-b2043.56-10027231)
    • All Android licenses accepted.

[X] Chrome - develop for the web (Cannot find Chrome executable at .\Google\Chrome\Application\chrome.exe)
    ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.

[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.6.5)
    • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community
    • Visual Studio Community 2022 version 17.6.33829.357
    • Windows 10 SDK version 10.0.22000.0

[√] Android Studio (version 2022.3)
    • 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.6+0-b2043.56-10027231)

[√] Connected device (2 available)
    • Windows (desktop) • windows • windows-x64    • Microsoft Windows [Version 10.0.22621.2283]
    • Edge (web)        • edge    • web-javascript • Microsoft Edge 117.0.2045.31

[√] Network resources
    • All expected network resources are available.

! Doctor found issues in 1 category.

More environment information

  • Flutter 3.13.0
  • Platform affected: Android
  • Platform version affected: Android 11
  • Flame: ^1.8.2

Log information

There's no error on the console

@hilmiyafia hilmiyafia added the bug label Sep 20, 2023
@spydon
Copy link
Member

spydon commented Sep 20, 2023

Can you try with Flutter 3.13.4?

@hilmiyafia
Copy link
Author

@spydon Still doesn't work 😞

@spydon
Copy link
Member

spydon commented Sep 20, 2023

Can you try to reproduce this in pure Flutter? This doesn't seem like a Flame issue.
What happens when you press back and have the keyboard open, does the keyboard close?

@hilmiyafia
Copy link
Author

hilmiyafia commented Sep 20, 2023

@spydon Yes, the keyboard closes, and the dialog closes too. But it won't go back to page 1.

It works fine in pure flutter:

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(home: Scaffold(body: Page1())));
}

class Page1 extends StatelessWidget {
  const Page1({super.key});
  @override
  Widget build(BuildContext context) {
    return Center(child: ElevatedButton(
      onPressed: () {
        Navigator.push(
          context,
          MaterialPageRoute(builder: (context) => Page2()),
        );
      },
      child: const Text("Go to Page 2"),
    ));
  }
}

class Page2 extends StatelessWidget {
  final TextEditingController field = TextEditingController();
  Page2({super.key});
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        Navigator.pop(context);
        return false;
      },
      child: Center(child: ElevatedButton(
        onPressed: () {
          showDialog(context: context, builder: (BuildContext context) {
            return AlertDialog(
              title: const Text("Input your name:"),
              content: TextField(controller: field),
              actions: <Widget>[TextButton(
                onPressed: () {
                  print("Hello ${field.text}!");
                  Navigator.pop(context);
                },
                child: const Text("OK"),
              )],
            );
          });
        },
        child: const Text("Show Dialog"),
      )),
    );
  }
}

@spydon
Copy link
Member

spydon commented Sep 20, 2023

@spydon Still doesn't work 😞

Thanks for testing in pure flutter too, @flame-engine/flame-admin any clue what the issue could be here?

@erickzanardo
Copy link
Member

humm, that is weird, at a first glance I thought this could be that common mistake of using the wrong context, but as I am looking on your code, you are indeed doing a pop using the correct one.

We will probably need to debug this to understand better, as I can't understand what in Flame could lead to this.

@hilmiyafia
Copy link
Author

@spydon No problem 😊

@erickzanardo I found a more minimal way to reproduce the error. Apparently it's not specific to WillPopScope.

Usually pressing the back button will close the app. But after opening a keyboard on a dialog box, the back is just not responding (the app won't close).

More minimal:

import 'package:flutter/material.dart';
import 'package:flame/game.dart';

void main() {
  runApp(MaterialApp(home: Scaffold(body: GameWidget<Engine>.controlled(
    gameFactory: Engine.new,
    initialActiveOverlays: const ["Page"],
    overlayBuilderMap: {
      "Page": (_, engine) => Page(engine: engine),
    },
  ))));
}

class Engine extends FlameGame {
  @override
  Color backgroundColor() => Colors.white;
}

class Page extends StatelessWidget {
  final Engine engine;
  final TextEditingController field = TextEditingController();
  Page({super.key, required this.engine});
  @override
  Widget build(BuildContext context) {
    return Center(child: ElevatedButton(
      onPressed: () {
        showDialog(context: context, builder: (BuildContext context) {
          return AlertDialog(
            title: const Text("Input your name:"),
            content: TextField(controller: field),
            actions: [ElevatedButton(
              onPressed: () => Navigator.pop(context),
              child: const Text("OK"),
            )],
          );
        });
      },
      child: const Text("Show Dialog"),
    ));
  }
}

@ufrshubham
Copy link
Collaborator

3.13.5 just dropped. Maybe give it a try there as well.

@hilmiyafia
Copy link
Author

@ufrshubham Still doesn't work 😞

@immadisairaj
Copy link
Contributor

I have just now tried it with the latest version of both Flutter 3.19 and Flame 1.16. And this issues seems to be fixed. I am not able to reproduce the issue.

Maybe you can check once if this issue still persists @hilmiyafia

@spydon
Copy link
Member

spydon commented Feb 24, 2024

I'll close this and then we can open it up if anyone is able to reproduce the issue again

@spydon spydon closed this as completed Feb 24, 2024
@hilmiyafia
Copy link
Author

@spydon @immadisairaj No, it's not fixed. Please reopen the issue. Here I made a video of the problem: YouTube

@immadisairaj
Copy link
Contributor

immadisairaj commented Feb 25, 2024

Ohh.. my bad. Sorry. I only saw the second example and thought the keyboard closure is happening correctly. Well, I checked it now and the issue still exists.

Adding to it, there is another which I saw.
I also changed the code a bit to check if the pop scope is working at all.
Changes: Add new pop scope before game widget, and add Keys to the page's

import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: PopScope(
          canPop: false,
          onPopInvoked: (_) {
            print('------ main pop scope called');
          },
          child: GameWidget<Engine>.controlled(
            gameFactory: Engine.new,
            initialActiveOverlays: const ["Page 1"],
            overlayBuilderMap: {
              "Page 1": (_, engine) =>
                  Page1(key: const ValueKey('JUST_TO_TEST_1'), engine: engine),
              "Page 2": (_, engine) =>
                  Page2(key: const ValueKey('JUST_TO_TEST_2'), engine: engine),
            },
          ),
        ),
      ),
    ),
  );
}

class Engine extends FlameGame with ScaleDetector {
  void changePage(String add, String remove) {
    overlays.remove(remove);
    overlays.add(add);
  }
}

class Page1 extends StatelessWidget {
  final Engine engine;
  const Page1({
    super.key,
    required this.engine,
  });
  @override
  Widget build(BuildContext context) {
    return Center(
        child: ElevatedButton(
      onPressed: () {
        engine.changePage("Page 2", "Page 1");
        // Navigator.of(context).push(MaterialPageRoute(builder: (_) => Page2()));
      },
      child: const Text("Go to Page 2"),
    ));
  }
}

class Page2 extends StatelessWidget {
  final Engine engine;
  final TextEditingController field = TextEditingController();
  Page2({
    super.key,
    required this.engine,
  });
  @override
  Widget build(BuildContext context) {
    return PopScope(
      // canPop: false,
      onPopInvoked: (_) {
        print('------pop invoked');
        engine.changePage("Page 1", "Page 2");
        // return false;
      },
      child: Center(
          child: ElevatedButton(
        onPressed: () {
          showDialog(
              context: context,
              builder: (BuildContext context) {
                return AlertDialog(
                  title: const Text("Input your name:"),
                  content: TextField(controller: field),
                  actions: <Widget>[
                    TextButton(
                      onPressed: () {
                        print("Hello ${field.text}!");
                        Navigator.pop(context);
                      },
                      child: const Text("OK"),
                    )
                  ],
                );
              });
        },
        child: const Text("Show Dialog"),
      )),
    );
  }
}

Output:

# when keyboard isn't opened at all
I/flutter ( 8431): ------ main pop scope called
I/flutter ( 8431): Hello !
I/flutter ( 8431): ------ main pop scope called
I/flutter ( 8431): ------pop invoked
# when keyboard is open and Ok is clicked
I/AssistStructure( 8431): Flattened final assist data: 388 bytes, containing 1 windows, 3 views
I/flutter ( 8431): Hello !/
# trying to press the back button
# the pop scope isn't getting called at all

The issue still exists. I see the pop scope isn't invoked at all when the keyboard comes up and closed.

Removing the Flame widgets, with only navigators, the pop scope works as expected.

Edit: After the keyboard not working, tried hot restarting the app, then the page doesn't come to 1 even if keyboard is not opened.

@hilmiyafia
Copy link
Author

@immadisairaj Thank you for checking it again 😊

@immadisairaj
Copy link
Contributor

immadisairaj commented Feb 25, 2024

After a bit more investigating, I am probably thinking that there is something wrong with Navigator pop invocation when back button pressed.

I tried to add another button (with Go to Page 1) in Page 2 which has

onPressed: () {
  Navigator.maybePop(context);
},

And guess what, the onPopInvoked works.. But, the back button still doesn't work.
I am still not sure if it's an issue from Flutter?

@spydon spydon reopened this Feb 25, 2024
@spydon
Copy link
Member

spydon commented Feb 25, 2024

Have you tried reproducing this in pure flutter? I doubt that this is a Flame bug.

@immadisairaj
Copy link
Contributor

I tried with pure Flutter. And it works fine there (both with navigation or a stateful widget and swap child). Yeah, seems to be a Flame bug.

Maybe related to some bindings? I am a bit confused on where to look..

@spydon
Copy link
Member

spydon commented Feb 25, 2024

I tried with pure Flutter. And it works fine there (both with navigation or a stateful widget and swap child). Yeah, seems to be a Flame bug.

Alright, thanks for checking! Maybe it has something to do with where the focus lays.

@immadisairaj
Copy link
Contributor

immadisairaj commented Feb 26, 2024

Looks like! I had did something in the example code something like this and the back button works after dialog is closed.

showDialog(...).then((_) => FocusScope.of(context).requestFocus(FocusNode()));

This requests the new focus node.

It doesn't seem to be a permanent fix. But, tells us that the issue is really with the Focus.

Edit:
This also works

showDialog(...).then((value) => focusNode.unfocus()); // focusNode is the one attached to GameWidget

As a work around, keeping focusNode.unfocus(); in the build method of Page2 would work. But, might be only for this example and is not a permanent fix.

PS: This can be dirty

@immadisairaj
Copy link
Contributor

Another update:
There is a way to make it work is by using this:

GameWidget(focusNode: FocusNode(canRequestFocus: false), .....),

Here we are making the GameWidget to not receive any focus. The problem I see is that when keyboard is opened and closed, the focus comes onto the GameWidget where it doesn't seem to have the back button handled (not sure)?

But, once we tell the GameWidget that don't request the focus scope, then the focus shifts to parent and works as expected.

@immadisairaj
Copy link
Contributor

Please let us know if it works on what you are working @hilmiyafia (if it is not affecting anything other in the game)

Also, how should we proceed with this? Any suggestions @spydon?

  1. I think we could keep this issue open so that if others have an issue, they could use this solution.
  2. Or, something is really wrong in the focus of GameWidget and we have to look into it
  3. Or, document about focus somewhere
  4. Or, something other?

@spydon
Copy link
Member

spydon commented Mar 6, 2024

I would go for number three, if anyone want to write a few line of docs for it. :)

@hilmiyafia
Copy link
Author

@immadisairaj Yes it works now, thank you so much 😊

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

No branches or pull requests

5 participants