1

I am new to flutter and I got a situation where I need to reuse data from an asynchronous call, after researching a bit I found out about the nice FutureBuilder Widget and I am using, it works great but I have this situation where I need the data from the future call in two different widgets like the code fragment below which causes the async call to be executed twice and I would like to avoid it.

How to avoid the two calls? What's the recommended approach in this case? I couldn't find a reference/recommendation for this situation.

body: Column(
        children: [

          Container(
            height: 200,
            child: FutureBuilder(
              future: weight.findAll(),
              builder: (context, snapshot) {
                  if(!snapshot.hasData) {
                    return Center(child: CircularProgressIndicator());
                  } else {
                    this._seriesList = _plotWeightSeries(snapshot.data);
                    return _lineChart();
                  }
              },
            ),

          ),

          Expanded(
            child: FutureBuilder(
              future: weight.findAll(),
              builder: (context, snapshot) {
                if (!snapshot.hasData) {
                  return Center(child: CircularProgressIndicator());
                } else {
                  return _getSlidableListView(snapshot.data);
                }
              },
            ),
          ),
        ],
      ),
groo
  • 4,213
  • 6
  • 45
  • 69
  • Global [state management](https://flutter.dev/docs/development/data-and-backend/state-mgmt/options) and decoupling api calls from the view. – Chance Feb 21 '21 at 00:20
  • 1
    Hi, sorry I don't get it. This specific page represents 10% of the app, it seems to me that adding such information to a global state management is a big waste of memory. And what about the "decoupling API calls from the view" can you please elaborate a bit on that? – groo Feb 21 '21 at 00:50
  • 1
    Sorry for my bad English. The answer below summarized a little of what I said about state management and the removal of api calls from the view, a good practice of some architectures is that the view requests the data to the state manager and it makes the api calls that is to decouple the view. Separating the different services from the view will make it easier to run tests on the view and the state manager. – Chance Feb 21 '21 at 02:19

1 Answers1

4

Here is an example using Flutter Hooks and Riverpod.

I define a FutureProvider to fetch the weights from the server:

final weightsProvider = FutureProvider((ref) => findAllWeights());

Future<List<double>> findAllWeights() async {
  print('FETCHING DATA'); // This gets run only once
  final random = Random();
  await Future.delayed(Duration(seconds: 2));
  return List.generate(20, (index) => 50 + 20 * random.nextDouble());
}

And then, I use the result in both my WidgetOne to calculate the SUM and my WidgetTwo to calculate the AVERAGE. As you will see, the FETCHING DATA only happens once.

Full source code

import 'dart:math' show Random;

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/all.dart';

void main() {
  runApp(
    ProviderScope(
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Flutter Demo',
        home: HomePage(),
      ),
    ),
  );
}

final weightsProvider = FutureProvider((ref) => findAllWeights());

Future<List<double>> findAllWeights() async {
  print('FETCHING DATA'); // This gets run only once
  final random = Random();
  await Future.delayed(Duration(seconds: 2));
  return List.generate(20, (index) => 50 + 20 * random.nextDouble());
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          WidgetOne(),
          WidgetTwo(),
        ],
      ),
    );
  }
}

class WidgetOne extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final weights = useProvider(weightsProvider);
    return Card(
      child: Column(
        children: [
          Text('SUM of the weights'),
          weights.when(
            data: (data) => Text(data.reduce((a, b) => a + b).toString()),
            loading: () => CircularProgressIndicator(),
            error: (_, __) => Text('Something bad happended'),
          ),
        ],
      ),
    );
  }
}

class WidgetTwo extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final weights = useProvider(weightsProvider);
    return Card(
      child: Column(
        children: [
          Text('AVERAGE of the weights'),
          weights.when(
            data: (data) =>
                Text((data.reduce((a, b) => a + b) / data.length).toString()),
            loading: () => CircularProgressIndicator(),
            error: (_, __) => Text('Something bad happended'),
          ),
        ],
      ),
    );
  }
}

I used Riverpod in this examples but there are other State Management, check here for a curated List of state management approaches.

Update for Riverpod 1.0

import 'dart:math' show Random;

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

void main() {
  runApp(
    const ProviderScope(
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Flutter Demo',
        home: HomePage(),
      ),
    ),
  );
}

final weightsProvider = FutureProvider((ref) => findAllWeights());

Future<List<double>> findAllWeights() async {
  print('FETCHING DATA'); // This gets run only once
  final random = Random();
  await Future.delayed(const Duration(seconds: 2));
  return List.generate(20, (index) => 50 + 20 * random.nextDouble());
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: const [
          WidgetOne(),
          WidgetTwo(),
        ],
      ),
    );
  }
}

class WidgetOne extends HookConsumerWidget {
  const WidgetOne({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final weights = ref.watch(weightsProvider);
    return Card(
      child: Column(
        children: [
          const Text('SUM of the weights'),
          weights.when(
            data: (data) => Text(data.reduce((a, b) => a + b).toString()),
            loading: () => const CircularProgressIndicator(),
            error: (_, __) => const Text('Something bad happended'),
          ),
        ],
      ),
    );
  }
}

class WidgetTwo extends HookConsumerWidget {
  const WidgetTwo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final weights = ref.watch(weightsProvider);
    return Card(
      child: Column(
        children: [
          const Text('AVERAGE of the weights'),
          weights.when(
            data: (data) =>
                Text((data.reduce((a, b) => a + b) / data.length).toString()),
            loading: () => const CircularProgressIndicator(),
            error: (_, __) => const Text('Something bad happended'),
          ),
        ],
      ),
    );
  }
}

Thierry
  • 7,775
  • 2
  • 15
  • 33
  • 1
    Thanks, this is exactly what I was looking for. I will take a better look at Riverpod. – groo Feb 21 '21 at 01:20
  • _"As you will see, the FETCHING DATA only happens once."_ but I see both widgets make a call to `useProvider(weightsProvider);` and your `Future> findAllWeights()` does not seem to have an `if` statement to check if the data is already there so I do not get this part about not fetching twice – MwBakker Nov 18 '21 at 10:18
  • That's the whole point of using [Providers](https://riverpod.dev/docs/concepts/providers/). """Wrapping a piece of state in a provider allows easily accessing that state in multiple locations. Providers are a complete replacement for patterns like Singletons.""" By the way, with the version 1.0.0 of riverpod, only one syntax remains: `ref.watch(provider)`. `useProvider` is removed in favor of `HookConsumerWidget`. Check my updated answer. – Thierry Jan 03 '22 at 09:24