rrousselGit / riverpod

A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
https://riverpod.dev
MIT License
6.17k stars 943 forks source link

Setting StateProvider state value throws error #459

Closed ffpetrovic closed 3 years ago

ffpetrovic commented 3 years ago

Describe the bug Setting StateProvider state value throws error.

To Reproduce

final navigatorStateProvider = StateProvider<GlobalKey<NavigatorState>?>(
      (ref) => null
);

class MyApp extends HookWidget {
  final navigatorKey = GlobalKey<NavigatorState>();

  @override
  Widget build(BuildContext context) {
    final navigator = useProvider(navigatorStateProvider);

    useEffect(() {
      navigator.state = navigatorKey;
    }, []);
    ...

Expected behavior I expect the state of this provider to be set without an error. The stack trace is

#0      StateNotifier.state= (package:state_notifier/state_notifier.dart:173:7)
#1      StateController.state= (package:riverpod/src/state_provider.dart:23:31)
#2      MyApp.build.<anonymous closure> (package:app/main.dart:32:47)
#3      _EffectHookState.scheduleEffect (package:flutter_hooks/src/primitives.dart:181:27)
#4      _EffectHookState.initHook (package:flutter_hooks/src/primitives.dart:161:5)
#5      _extension#0._createHookState (package:flutter_hooks/src/framework.dart:319:9)
#6      _extension#0._appendHook (package:flutter_hooks/src/framework.dart:330:20)
#7      HookElement._use (package:flutter_hooks/src/framework.dart:439:7)
#8      Hook.use (package:flutter_hooks/src/framework.dart:140:45)
#9      use (package:flutter_hooks/src/framework.dart:19:32)
#10     useEffect (package:flutter_hooks/src/primitives.dart:143:3)
#11     MyApp.build (package:app/main.dart:30:5)
#12     StatelessElement.build (package:flutter/src/widgets/framework.dart:4569:28)
#13     HookElement.build (package:flutter_hooks/src/framework.dart:417:27)
#14     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4495:15)
#15     Element.rebuild (package:flutter/src/widgets/framework.dart:4189:5)
#16     ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4474:5)
#17     ComponentElement.mount (package:flutter/src/widgets/framework.dart:4469:5)
#18     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3541:14)
#19     Element.updateChild (package:flutter/src/widgets/framework.dart:3306:18)
#20     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4520:16)
#21     Element.rebuild (package:flutter/src/widgets/framework.dart:4189:5)
#22     ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4474:5)
#23     ComponentElement.mount (package:flutter/src/widgets/framework.dart:4469:5)
#24     _UncontrolledProviderScopeElement.mount (package:flutter_riverpod/src/framework.dart:284:11)
#25     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3541:14)
#26     Element.updateChild (package:flutter/src/widgets/framework.dart:3306:18)
#27     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4520:16)
#28     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4667:11)
#29     Element.rebuild (package:flutter/src/widgets/framework.dart:4189:5)
#30     ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4474:5)
#31     StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4658:11)
#32     ComponentElement.mount (package:flutter/src/widgets/framework.dart:4469:5)
#33     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3541:14)
#34     Element.updateChild (package:flutter/src/widgets/framework.dart:3306:18)
#35     RenderObjectToWidgetElement._rebuild (package:flutter/src/widgets/binding.dart:1182:16)
#36     RenderObjectToWidgetElement.mount (package:flutter/src/widgets/binding.dart:1153:5)
#37     RenderObjectToWidgetAdapter.attachToRenderTree.<anonymous closure> (package:flutter/src/widgets/binding.dart:1095:18)
#38     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2647:19)
#39     RenderObjectToWidgetAdapter.attachToRenderTree (package:flutter/src/widgets/binding.dart:1094:13)
#40     WidgetsBinding.attachRootWidget (package:flutter/src/widgets/binding.dart:934:7)
#41     WidgetsBinding.scheduleAttachRootWidget.<anonymous closure> (package:flutter/src/widgets/binding.dart:915:7)
#42     _rootRun (dart:async/zone.dart:1346:47)
#43     _CustomZone.run (dart:async/zone.dart:1258:19)
#44     _CustomZone.runGuarded (dart:async/zone.dart:1162:7)
#45     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1202:23)
#46     _rootRun (dart:async/zone.dart:1354:13)
#47     _CustomZone.run (dart:async/zone.dart:1258:19)
#48     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1186:23)
#49     Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
#50     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:395:19)
#51     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:426:5)
#52     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)

Also, as you can see, what I'm trying to do, is make a provider with the state of GlobalKey<NavigatorState> so I can navigate inside other providers. Is there something else that I'm doing wrong, or is this the correct approach?

ffpetrovic commented 3 years ago

Also, maybe a useful piece of information -- when this assignment is executed:

  1. The error above is thrown
  2. The assignment is successful
  3. The useEffect(() {...}, []) hook is called again?
  4. Repeat 3. unless I add a null check if(navigator.state == null)
ffpetrovic commented 3 years ago

Actually what I just ended up doing to achieve my goal was

final navigatorStateProvider = StateProvider<GlobalKey<NavigatorState>>(
      (ref) => GlobalKey<NavigatorState>(),
);

But I'd still love an answer as to why the above is throwing an error, and maybe the rerunning of the useEffect hook peculiarity too?

rrousselGit commented 3 years ago

You can't create GlobalKeys like that. You need to keep a reference on them, or otherwise if your widget is somehow recreated, the GlobalKey will be recreated too.

Considering you fixed your problem by caching the GlobalKey, that is likely what your problem was.

ffpetrovic commented 3 years ago
  1. Adding fields inside the HookWidget class means that when the widget is rebuilt (or literally recreated?) the fields will be reinitialized? Because this is the root application class so I don't expect it to be recreated? I understand if I use a hook and therefore it might get rebuilt, but that's the point of this question.
  2. I'll take your word for it, but in the stack trace it says #0 StateNotifier.state= package:state_notifier/state_notifier.dart:173:7) so it doesn't really look like the problem is regarding the GlobalKey, but the state provider?

Thanks for your answer anyway! 👍

rrousselGit commented 3 years ago

Ah I misread your code

Your issue is instead that you modified a provider inside build, which is invalid.

ffpetrovic commented 3 years ago

You mean the navigator.state = navigatorKey; line?

But I modified it inside a side-effect, why is that invalid? The useEffect hook is supposed to be inside the build method, right?