signals.dart icon indicating copy to clipboard operation
signals.dart copied to clipboard

Hot reload with `SignalProvider` causes create to rerun again

Open SPiercer opened this issue 11 months ago • 7 comments

to reproduce use any example use of SignalProvider i.e https://dartsignals.dev/flutter/signal-provider/

as the title says it's so self explainatory

however in this example

class Counter extends FlutterSignal<int> {
  Counter([super.value = 0]);

  void increment() => value++;
}

class Example extends StatelessWidget {
  const Example({super.key});

  @override
  Widget build(BuildContext context) {
    return SignalProvider<Counter>(
      create: () => Counter(),
      child: Scaffold(...)
    );
}

the create function always creates a new instance of the Counter object causing a new signal to be created every time ! after long trials the only thing i managed to do is to store it in a variable

class Counter extends FlutterSignal<int> {
  Counter([super.value = 0]);

  void increment() => value++;
}

final counter = Counter();

class Example extends StatelessWidget {
  const Example({super.key});

  @override
  Widget build(BuildContext context) {
    return SignalProvider<Counter>(
      create: () => counter, 
      child: Scaffold(...)
    );
}

this prevents the behavior but trials to be done to find other unexpected behaviours for the lifecycle

SPiercer avatar Jan 04 '25 21:01 SPiercer

after trial

having autoDispose = true will cause it to throw signal has been read after disposed and will not allow it to recreate it again

SPiercer avatar Jan 04 '25 21:01 SPiercer

after second trial

i used get_it as a service locator to create instances

i'm currently trying to create a good architecture plan using signals with SignalProvider and now got into two usecases

1. singletons (manual lifecycle autoDispose = false)

good for listing pages and long running screens with active services or etc.. using registerLazySingleton or registerSingleton and passing the disposeFunc

sl.registerLazySingleton(
  () => ProductListingSignal(),
  dispose: (signal) => signal.dispose(),
);

usage in screen

SignalProvider<ProductListingSignal>(
  create: () => sl(),
  child: Scaffold(...)
);

2. factory transient instance (automatic lifecycle autoDispose = true)

good for new pages and pages with parameters using registerCachedFactoryParam instead of registerFactoryParam because it would hold a weak ref. and prevent the hot reload bug

sl.registerCachedFactoryParam<ProductDetailsSignal, int, void>(
  (i, _) => ProductDetailsSignal(i),
);

usage in screen

SignalProvider<ProductDetailsSignal>(
  create: () => sl(param1: index),
  child: Scaffold(...)
);

SPiercer avatar Jan 05 '25 20:01 SPiercer

Still looking into the best way to solve for keeping the instances stable on hot reload.

Currently the create callback is treaded like a computed signal so it would get recreated if signals referenced at creation changed.

Hot reload rebuilds computed because of some edge cases in the Watch widget and disposed signals.

rodydavis avatar Jan 28 '25 17:01 rodydavis

i used InhertedProvider from package:provider

check my fork : https://github.com/SPiercer/signals.dart/

SPiercer avatar Jan 28 '25 17:01 SPiercer

I'd also be interested in a solution to this, as using Signals throughout our app has completely broken hot reload for all screens for us!

haf avatar Mar 10 '25 10:03 haf

Try using signals_hooks!

rodydavis avatar Sep 25 '25 10:09 rodydavis

SignalProvider doesnt actually work the way it looks like it should because it doesnt store the created signal in any kind of state.

Instead, i'd recommend having an InheritedSignal that accepts a pre-existing signal for access, and a SignalProvider which is a stateful widget that properly handles the lifecycle of the signal factory

TekExplorer avatar Oct 23 '25 00:10 TekExplorer