Skip to content

Widget Lifecycle and Scope Disposal Navigation Issue #247

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

Closed
masterwok opened this issue Jan 20, 2022 · 3 comments
Closed

Widget Lifecycle and Scope Disposal Navigation Issue #247

masterwok opened this issue Jan 20, 2022 · 3 comments

Comments

@masterwok
Copy link

masterwok commented Jan 20, 2022

I'm having trouble disposing of scopes when poping and pushing from one screen to another.

In my application, I have two screens and each screen has an associated module. The module is responsible for registering the scope and disposing of it.

The following use-case is where I'm running into the issue:

  1. User is on FirstScreen
  • Scope stack: ["baseScope", "scope_a"]
  1. User taps button and Navigator.popAndPushNamed(..) is invoked to navigate to SecondScreen
  2. The initState method is invoked on SecondScreen
  3. The register method is invoked on SecondModule which pushes a new scope and registers dependencies
  • Scope stack: ["baseScope", "scope_a", "scope_b"]
  1. The dispose method is invoked on FirstScreen
  2. The dispose method is invoked on FirstModule which pops the current scope
  • Scope stack: ["baseScope", "scope_a"]
  1. Crash occurs on SecondScreen because scope_b doesn't exist

The issue here is that the dispose method of the FirstScreen is being invoked after the registration of SecondModule. This causes scope_b to be destroyed before it can be used.

Any advice on a recommended approach to scoping and disposing of dependencies by screen? Any help would be greatly appreciated!


FirstScreen

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

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


class _FirstScreenState extends State<FirstScreen> {
  @override
  void initState() {
    super.initState();

    FirstModule.register();
  }

  @override
  void dispose() {
    super.dispose();

    FirstModule.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: () => Navigator.popAndPushNamed(context, "secondScreen"),
      child: Text("Next"),
    );
  }
}

FirstModule:

class FirstModule {
  FirstModule._();

  static final _getIt = GetIt.I;

  static void register() {
    _getIt.pushNewScope(scopeName: "scope_a");
    // ...registrations...
  }

  static void dispose() {
    _getIt.popScope();
  }
}

SecondScreen:

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

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

class _SecondScreenState extends State<SecondScreen> {
  @override
  void initState() {
    super.initState();

    SecondModule.register();
  }

  @override
  void dispose() {
    super.dispose();

    SecondModule.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

SecondModule:

class SecondModule {
  SecondModule._();

  static final _getIt = GetIt.I;

  static void register() {
    _getIt.pushNewScope(scopeName: "scope_b");
    // ...registrations...
  }

  static void dispose() {
    _getIt.popScope();
  }
}
@masterwok masterwok changed the title Widget Lifecycle and Scope Disposale Navigation Issue Widget Lifecycle and Scope Disposal Navigation Issue Jan 20, 2022
@escamoteur
Copy link
Collaborator

Hi,

indeed, GetIt scopes are not ideal for what you are trying to do. for me, Scopes are not so much to be used in the connection of UI states but with states of your business logic. Like "no user logged in" -> "user logged in" or "new shopping cart created"

Maybe my talk on get_it might be helpful https://www.youtube.com/watch?v=YJ52kSfSMyM&list=PL-BFoWYMGZ2TvwY0uf1fBJB1358utBlAz&index=10

Can you elaborate why you register objects just for the the livetime of one Screen? Maybe I have a better approach?

@masterwok
Copy link
Author

So my goal is to register scopes by feature and not necessarily for each screen. For example, one of my features is a buy transaction workflow composed of multiple screens. Each screen in the workflow is injected with a store that makes sharing data across screens easier. When the user completes the workflow, I'd like to pop the scope to dispose of any dependencies registered within it.

I'm currently using the initState/dispose methods of the first screen in the feature to manage the scope. I realize this probably isn't a great way to go about scope management. Another idea I had was to potentially use a hook on Flutter navigation routes but wasn't sure if this approach would work either. Sorry, I realize now the example I gave when I opened the ticket didn't make it clear I was trying to scope by feature.

Do you have an approach you can recommend for scoping by feature? Also, thanks for linking your talk (and your library)! Lots of valuable information in there.

@escamoteur
Copy link
Collaborator

I would probably push a new scope the moment the user selects the button that creates a new transaction and pop it when the transaction is finished.

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

No branches or pull requests

2 participants