Home > Mobile >  How to use FAB with FutureBuilder
How to use FAB with FutureBuilder

Time:05-20

What I would like to achieve: show a FAB only if a webpage responds with status 200.

Here are the necessary parts of my code, I use the async method to check the webpage:

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  late Future<Widget> futureWidget;
 
  void _incrementCounter() {
    setState(() {
      _counter  ;
    });
  }
 
  @override
  void initState() {
    super.initState();
    futureWidget = _getFAB();
  }
 
  Future<Widget> _getFAB() async {
    final response = await http
        .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
 
    if (response.statusCode == 200) {
      // If the server did return a 200 OK response,
      // return something to create FAB
      return const Text('something');
    } else {
      // If the server did not return a 200 OK response,
      // then throw an exception.
      throw Exception('Failed to load url');
    }
  }

And with the following FutureBuilder I am able to get the result if the snapshot has data:

        body: Center(
          child: FutureBuilder<Widget>(
            future: futureWidget,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return FloatingActionButton(
                    backgroundColor: Colors.deepOrange[800],
                    child: Icon(Icons.add_shopping_cart),
                    onPressed:
                        null); // navigate to webview, will be created later
              } else if (snapshot.hasError) {
                return Text('${snapshot.error}');
              }
 
              // By default, show a loading spinner.
              return const CircularProgressIndicator();
            },
          )

My problem is that I want to use it here, as a floatingActionButton widget:

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
[further coding...]
      ),
      body: // Indexed Stack to keep data
      IndexedStack(
          index: _selectedIndex,
          children: _pages,
        ),
        floatingActionButton: _getFAB(),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>
[further coding...]

But in this case Flutter is throwing the error The argument type 'Future' can't be assigned to the parameter type 'Widget?'.

Sure, because I am not using the FutureBuilder this way. But when I use FutureBuilder like in the coding above then Flutter expects further positional arguments like column for example. This ends in a completely different view as the FAB is not placed over the indexedstack in the typical FAB position anymore.

I have searched for several hours for a similar question but found nothing. Maybe my code is too complicated but Flutter is still new to me. It would be great if someone could help me :)

CodePudding user response:

You can use the just _getFAB() method to do it. You can't assign _getFab() method's return value to any widget since it has a return type Future. And also, when you are trying to return FAB from the FutureBuilder it will return FAB inside the Scaffold body.

So, I would suggest you fetch the data from the _getFAB() method and assign those data to a class level variable. It could be bool, map or model class etc. You have to place conditional statements in the widget tree to populate the state before the data fetching and after the data fetching. Then call setState((){}) and it will rebuild the widget tree with new data. Below is an simple example.

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

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

  @override
  State<FabFuture> createState() => _FabFutureState();
}

class _FabFutureState extends State<FabFuture> {
  bool isDataLoaded = false;

  @override
  void initState() {
    super.initState();
    _getFAB();
  }

  Future<void> _getFAB() async {
    final response = await http
        .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));

    if (response.statusCode == 200) {
      isDataLoaded = true;
      setState(() {});
    } else {
      isDataLoaded = false;
      //TODO: handle error
      setState(() {});
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: const Center(
        child: Text('Implemet body here'),
      ),
      floatingActionButton: isDataLoaded
          ? FloatingActionButton(
              backgroundColor: Colors.deepOrange[800],
              child: const Icon(Icons.add_shopping_cart),
              onPressed: null)
          : const SizedBox(),
    );
  }
}

Here I used a simple bool value to determine if I should show the FAB or not. The FAB will only show after the data is successfully fetched. After practicing these ways and you get confident about them, I would like to suggest learning state management solutions to handle these types of works.

  • Related