5

Basically, I want to set the initial constructor value to StateProvider so I can use it in its child widgets.

final stateProvider = StateProvider((ref) => "default");

class ExampleWidget extends ConsumerWidget {

  ExampleWidget({required String text}){
    // how to override default hear as
    // stateProvider = text
  }

  @override
  Widget build(BuildContext context, ScopedReader watch) {
    return Column(
      children: [
        Text(watch(stateProvider).state),
        Container(
          child: ChildWidget(),
        ),
      ],
    );
  }
}

class ChildWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    return Container(
      child: Text(watch(stateProvider).state),
    );
  }
}

So how do I implement this?

I have tried this but the state value is not updating.

Edit ----------------

With riverpod_hook & hooks_flutter

final stateProvider = StateProvider((ref) => "default");

class ExampleWidget extends HookWidget {

  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance?.addPostFrameCallback((_) {
      useEffect((){
        context.read(stateProvider).state = "new value";
      },[]);
    });

    final state = useProvider(stateProvider).state;
    return Column(
      children: [
        Text(state),
      ],
    );
  }
}

above code throwing this error

Hooks can only be called from the build method of a widget that mix-in Hooks``

So I tried to initialize under the build method like this

useEffect((){
   context.read(stateProvider).state = "new value";
},[]);

but above code throws the following error:

The following Error was thrown building ExampleWidget(dirty, dependencies: [UncontrolledProviderScope]):
Instance of 'Error'

I just want to initialize the value once that's why I want to use useEffect hook.

Kartik Garasia
  • 1,324
  • 1
  • 18
  • 27
  • Did you try by using the `StateController` to change the value you want from the `StateNotifier`? – SalahAdDin Apr 26 '21 at 16:19
  • Yes, I did and value does get change but in the UI new changed value will not reflect. – Kartik Garasia Apr 26 '21 at 18:15
  • But it is also reflected on mine prior to rebuild the widget, that's why i did it in that way. – SalahAdDin Apr 27 '21 at 00:01
  • When you rebuild (Hot reload) that time some part of the widget tree will get updated and that will pick up the latest data from the provider. It will act like a Set State function. ( I am no expert in a flutter so take this answer as a grain of salt) – Kartik Garasia Apr 27 '21 at 07:16

1 Answers1

8

You can't access providers in the constructor normally. You also shouldn't update the state directly in the build method, as this can cause build errors (the widget hasn't finished building before you set a state which would cause another rebuild).

You should be able to use addPostFrameCallback to change the state after the widget has built.

For example:

class ExampleWidget extends ConsumerWidget {

  const ExampleWidget({required String text});

  final String text;

  @override
  Widget build(BuildContext context, ScopedReader watch) {
    WidgetsBinding.instance?.addPostFrameCallback((_) {
      context.read(stateProvider).state = text;
    });
    return Column(
      children: [
        Text(watch(stateProvider).state),
        Container(
          child: ChildWidget(),
        ),
      ],
    );
  }
}

Regarding your edits:

Hooks can only be called from the build method of a widget that mix-in Hooks

This is because the callback function is not really in the build method, it's executed externally.

The following Error was thrown building ExampleWidget(dirty, dependencies: [UncontrolledProviderScope]):
Instance of 'Error'

This is an example of the build errors I mentioned earlier in this post.

I would say you're better off refactoring to just set the StateProvider to the text in a prior widget rather than accepting text as a parameter and trying to set it, but I think the following would work with your current approach, though again I'd recommend approaching your problem differently.

class ExampleWidget extends HookWidget {

  const ExampleWidget({required String text});

  final String text;

  @override
  Widget build(BuildContext context) {
    final future = useMemoized(() => Future<void>.delayed(const Duration(milliseconds: 1)));
    final snapshot = useFuture<void>(future, initialData: null);
    if (snapshot.connectionState == ConnectionState.done) {
      context.read(stateProvider).state = text;
    }

    return Column(
      children: [
        Text(useProvider(stateProvider).state),
        Container(
          child: ChildWidget(),
        ),
      ],
    );
  }
}

By delaying the state change until after the initial build we avoid build errors.

Alex Hartford
  • 5,110
  • 2
  • 19
  • 36
  • Hey @Alex Thanks for the answer. Now I am using the `riverpod_hooks` and inside that I am using `useEffect` and under that I am trying to set the initial value. Can you give me an example with hooks? – Kartik Garasia Apr 09 '21 at 12:14
  • @KartikGarasia Added a version that uses hooks. Let me know if it helps or you need anything else. – Alex Hartford Apr 09 '21 at 13:39
  • I have updated my question please take a look. And also thank you for your quick response :) – Kartik Garasia Apr 09 '21 at 16:42
  • 1
    @KartikGarasia Updated once more, let me know how it goes. – Alex Hartford Apr 09 '21 at 18:29
  • Hey @AlexHartford I have tried your example and for the moment ( 1 sec ) it works and then it falls back to the same error so I think you are right I should not initialize this in the build method. But what bump me off so much is there is not many examples of such common use case. I have gone through almost all the official examples but no one initializing anything from the constructor. But thank you for taking the time and give me the right direction. I will just drill the prop in the child widget. – Kartik Garasia Apr 10 '21 at 09:01
  • I'm agree about it, there is no so much example about this, i'm stuck with this issue too. – SalahAdDin Apr 26 '21 at 14:56
  • The reason there aren't examples of this is that it's a roundabout way of doing things. There are likely better approaches to your use case you haven't evaluated yet. – Alex Hartford Apr 26 '21 at 15:02
  • 2
    It would be great look at tutorials which lead you with the correct approach. I fixed this by another approach but maybe it is not the correct one. – SalahAdDin Apr 26 '21 at 16:18