FutureBuilder runs twice even it is not supposed to

249 Views Asked by At

This is my code:

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

  @override
  State<HomePage> createState() => _HomePageState();
  }

  class _HomePageState extends State<HomePage> {
  late Future<ImageProvider> userImageFuture;
  LoggedUser loggedUser = Cache.getLoggedUser();
  
  @override
  void initState() {
    userImageFuture = Buttons.getUserImage(loggedUser.Id, context);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).backgroundColor,
      body: SingleChildScrollView(
        child: SafeArea(
          child: Column(
            children: <Widget>[
              Row(
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.only(top: 15, left: 15),
                    child: FutureBuilder<ImageProvider>(
                      future: userImageFuture,
                      builder: (BuildContext context,
                          AsyncSnapshot<ImageProvider> snapshot) {
                        return CircleAvatar(
                          radius: 20,
                          foregroundImage: snapshot.data,
                          backgroundImage: Settings.DefaultUserImage,
                        );
                      },
                    ),
                  ),

                  ...

                ],
              ),
              
              ...

            ],
          ),
        ),
      ),
    );
  }
}

The problem is that FutureBuilder runs twice. It is supposed to fetch user profile picture from async method from another class:

class Buttons {
    static Future<ImageProvider> getUserImage(
      int userId, BuildContext context) async {
    
    ...

    return image;
  }
}

I followed this instructions. I edited my code and as far as I understand it from the docs:

The future must have been obtained earlier, e.g. during State.initState, State.didUpdateConfig, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.

I am relatively new to Flutter but I am convinced that this Future is not created at the same time as FutureBuilder.

Any possible solutions? I would be very grateful, because I've spent too much time on this. Thanks.

1

There are 1 best solutions below

0
Peter Koltai On

You should check for the snapshot's status in the builder method. Currently you are returning the CircleAvatar immediately, but in fact for the first run it is more than likely that the future is not completed yet.

You should follow steps like these in every case when you use FutureBuilder:

builder: (context, snapshot) {
  if (snapshot.connectionState != ConnectionState.done) {
    // means the future is not completed yet, display
    // a progress indicator for example
    return const Center(child: CircularProgressIndicator());
  }
  
  if (snapshot.hasError) {
    // there can be an error even if the future is completed,
    // display an error message or whatever you like to do
    // to handle the error
    return const Center(child: Text('Error'));
  }

  if (!snapshot.hasData) {
    // even a completed, errorless future can contain
    // no data, for example there is no matching result,
    // you can skip this check if you can manage
    // missing or null in `snapshot.data`, but generally
    // it is the recommended way
    // (if you check source code, you will see that
    // hasData is simply checking data against null value:
    // bool get hasData => data != null;)
    return const Center(child: Text('No data'));
  }
  
  // now you are good to go
  return CircleAvatar(
    radius: 20,
    foregroundImage: snapshot.data,
    backgroundImage: Settings.DefaultUserImage,
  );
}

And you don't really need:

builder: (BuildContext context, AsyncSnapshot<ImageProvider> snapshot)

You can simply use this, the types will be inferred by Dart:

builder: (context, snapshot)