harwinder97
harwinder97

Reputation: 39

how to use riverpod, by combing futureprovider with stateprovider?

I am using futureprovider for getting response from api , but i want get the list from api and assign it to the stateprovider by using listprovider.state="data from api" , how to will it work , how to combine future provdier with the state provider .

Upvotes: 0

Views: 2118

Answers (1)

Adnan
Adnan

Reputation: 1295

UPDATED ANSWER

After a discussion with @31Carlton7, he's opened an issue on github, and after a discussion with Remi Rousselet (the creator of riverpod) we've reached a better solution for this problem.

(from the final solution on the issue)

Running the app:

main.dart

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return const ProviderScope(
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

Creating the foo class and provider:

foo.dart

part 'foo.g.dart';

class Foo {
  final int bar;
  int? baz;

  Foo(
    this.bar, {
    this.baz,
  });
}

@riverpod
class FooController extends _$FooController {
  FooController(this.foo);

  Foo foo;

  @override
  FutureOr<Foo> build() async {
    foo = await getFoo();
    return foo;
  }

  Future<Foo> getFoo() async {
    await Future.delayed(const Duration(seconds: 1));
    return Foo(1);
  }
}

Implementation using Async capabilities: home.dart

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Consumer(
        builder: (context, ref, _) {
          // Get the provider and watch it
          final fooAsync = ref.watch(fooControllerProvider);

          // Use .when to render UI from future
          return fooAsync.when(
            data: (foo) => Text('bar: ${foo.bar}, baz: ${foo.baz}'),
            loading: () => const CircularProgressIndicator(),
            error: (err, stack) => Text(err.toString()),
          );
        },
      ),
    );
  }
}

Implementation using Notifier capabilities: home.dart

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Consumer(
        builder: (context, ref, _) {
          // Get Foo provider and set the state of it.
          // Use it as if it were a State Provider.
          ref.watch(fooControllerProvider.notifier).foo = Foo(3);

          // Use Foo in UI (.requireValue is used to be able to listen to changes)
          final foo = ref.watch(fooControllerProvider).requireValue;
          // Use .when to render UI from future
          return Text('bar: ${foo.bar}, baz: ${foo.baz}');
        },
      ),
    );
  }
}

OLD ANSWER

This is a topic that I've been struggling with and thinking about a lot lately.

What I think is missing in Remi's answer, is the ability to convert the Future data to a maniputable data.

When you're recieving Future data using either a FutureProvider and implementing the ui using the when method OR using the FutureBuilder widget, they both will trigger a rebuild when the remote data is received, so if you try to assign the value to your StateProvider it will trigger a rebuild during another rebuild which will throw.

I currently have 2 workarounds for this, and I will be updating my answer as I get more info about this.

For this example, we'll have a future provider that will wait and then return a fake data:

final _futureCounterProv = FutureProvider(
  (ref) async {
    Future.delayed(
      Duration(seconds: 3),
    );
    return Random().nextInt(100);
  },
);

1. Future.microtask:

Future.microtask enables you to run an operation after the current rebuild ends.

You have to make sure that your StateProvider dependencies are in a Consumer below the Future.microtask call or the Future.microtask will be called on each state update, which will keep reseting the StateProvider's value to the future value

// this provider will provide the current value of the counter

final _counterProv = StateProvider((ref) => 0);

class Body extends ConsumerWidget {
  const Body({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ref.watch(_futureCounterProv).when(
      loading: () {
        return const Center(
          child: CircularProgressIndicator(),
        );
      },
      error: (error, stackTrace) {
        return Text(error.toString());
      },
      data: (data) {
        Future.microtask(
          () {
            // Assigning the future value to the `StateProvider`
            return ref.read(_counterProv.notifier).state = data;
          },
        );
        return Consumer(
          builder: (context, ref, _) {
            final count = ref.watch(_counterProv);

            return Column(
              children: [
                IconButton(
                  onPressed: () {
                    ref
                        .read(_counterProv.notifier)
                        .update((value) => value + 1);
                  },
                  icon: const Icon(Icons.add),
                ),
                Text(
                  count.toString(),
                ),
              ],
            );
          },
        );
      },
    );
  }
}

2. ChangeNotifierProvider:

StateProvider has 2 options to update its value: the value setter and the update method, and they both trigger a rebuild. In this workaround we want to implement a state update that does not trigger rebuild. A way to do this is by using a ChangeNotifierProvider instead of StateProvider. By using a ChangeNotifierProvider we can control our own update actions and call notifyListeners (which will trigger a rebuild) whenever we want.

You have to make sure that your ChangeNotifierProvider dependencies are in a Consumer below the updateNoNotify call, or the ChangeNotifierProvider's will keep reseting to the future's value. Also you have to make sure that all the widgets that are consuming this ChangeNotifierProvider are in the widget tree below the updateNoNotify, or they will not be rebuilt as we're not triggering a rebuild

// the new `_counterProv`
final _counterProv = ChangeNotifierProvider(
  (ref) => _CounterNotifier(),
);

class _CounterNotifier extends ChangeNotifier {
  int _value = 0;

  int get value => _value;

  void update(int Function(int value) update) {
    _value = update(_value);
    // trigger a rebuild
    notifyListeners();
  }

  void updateNoNotify(int Function(int value) update) {
    _value = update(_value);
  }
}

// the ui
class Body extends ConsumerWidget {
  const Body({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ref.watch(_futureCounterProv).when(
      loading: () {
        return const Center(
          child: CircularProgressIndicator(),
        );
      },
      error: (error, stackTrace) {
        return Text(error.toString());
      },
      data: (data) {
        // calling `updateNoNotify` which does not trigger
        // trigger rebuild as it does not call `notifyListeners`
        ref.read(_counterProv.notifier).updateNoNotify(
              (e) => data,
            );
        return Consumer(
          builder: (context, ref, _) {
            final count = ref.watch(_counterProv).value;

            return Column(
              children: [
                IconButton(
                  onPressed: () {
                    ref.read(_counterProv.notifier).update(
                          (value) => value + 1,
                        );
                  },
                  icon: const Icon(Icons.add),
                ),
                Text(
                  count.toString(),
                ),
              ],
            );
          },
        );
      },
    );
  }
}

These are not the safest workarounds, but they are workarounds, and I will be updating this answer once I find a safe way to do this.

Upvotes: 1

Related Questions