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

Decouple hooks from Element/BuildContext #144

Open
derolf opened this issue Jun 20, 2020 · 5 comments
Open

Decouple hooks from Element/BuildContext #144

derolf opened this issue Jun 20, 2020 · 5 comments
Assignees
Labels
enhancement New feature or request

Comments

@derolf
Copy link

derolf commented Jun 20, 2020

If flutter_hooks could abstract over its dependencies to the flutter framework for scheduling, we could drop in a different scheduling engine and use the hooks also outside of Widget trees.

@rrousselGit rrousselGit added the enhancement New feature or request label Jul 4, 2020
@rrousselGit
Copy link
Owner

What is the use-case?

@SynSzakala
Copy link

SynSzakala commented Oct 14, 2021

I will drop into this conversation, since I think I have a interesting use-case, expanding beyond hooks' initial scope (sharing stateful logic between widgets by composition).

We are experimenting (and successfully applied to some simple projects) with an experimental architecture that uses hooks as a main state management solution. Snippet below demonstrates it's basic principle (classic "counter" example):

class ScreenState {
  final int value;
  final Function() onButtonPressed;
}

ScreenState useScreenState() {
  final counterState = useState(0);
  return ScreenState(value: counterState.value, onButtonPressed: () => counterState.value++);
}

class Screen extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final state = useScreenState();
    return ScreenView(state);
  }
}

You can further enhance this approach with provider (Provider<GlobalState>.value(value: useGlobalState())) to create hierarchical, inter-dependent global (or local) states.

This hook-based state composition expanded to the level of an entire app seems to produce quite elegant and maintainable (easy to refactor) code. Particularly, effects allow for intuitive, selective "listening" to changes in providers above, without the need to use streams. Cool thing is this approach does not prevent you from more stream-based style (Stream<int> instead of int value in State, and useStreamController() or similar instead of useState() - your hook effectively becomes a BLoC). Of course, this is still very much experimental and we're trying to find out how it'll work out in bigger, more complicated cases.

How does this connect to the original issue?

There's one case, where this approach is a pain in the ass. Consider a global state which maintains connection to some kind of network stream (e.g. WebSockets, SSE), and exposes bool isConnected. When application goes background, such stream should be disconnected, since system may close our connection at any time. In our global state we can listen to AppLifecycleState changes and cancel the subscription, which schedules a rebuild with isConnected=false. But other providers (hooks) won't be notified about this change until the app goes back to the foreground (since flutter won't rebuild the widget tree when app is in the background). We can work around this by exposing Stream<bool> isConnected in State and listening to it, but this kind of defeats the purpose.

This could be resolved from decoupling hooks from flutter widget rebuilds, in a way similar to how riverpod's ProviderContainer can work without external vsync by scheduling updates on current thread's event loop. By abstracting away something like HookContext with HookContext.current, you could have two kinds of hooks:

  • one requiring just the HookContext.current to be set, with something like T build() (actually most of the current hooks),
  • existing Hook (possibly a subclass) requiring also the HookElement.current, with T build(BuildContext) (e.g useContext, useIsMounted)
    From there, integrating it with e.g riverpod should be straightforward, by creating HookProvider which sets HookContext.current to an implementation which schedules rebuilds in the current ProviderContainer.
    This is just a quick speculation, making it work while maintaining reasonable backwards compatibility may be difficult. However, I'd want to hear what do you think of it. I'm willing to help with the implementation if you think this is a good idea. If not, maybe I will just fork and try on my own ;)

@derolf
Copy link
Author

derolf commented Oct 14, 2021

For exactly the background issue you have, we have created a pump that still pumps frames if the app is in background.

@SynSzakala
Copy link

Can you share details of your solution? This seems like a nice workaround.

@rrousselGit rrousselGit self-assigned this May 10, 2023
@SynSzakala
Copy link

For anybody still interested in a solution - some time ago we created our own implementation of hooks: utopia_hooks. It can use different scheduling strategies and doesn't require a Widget tree to work. Syntax-wise it's mostly identical to flutter_hooks so should be easy to migrate.

@rrousselGit if you think implementing similar capabilities to flutter_hooks might be valuable, I'll be happy to discuss & help.

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

No branches or pull requests

3 participants