Search code examples
flutterasync-awaitfutureflutter-futurebuilder

Can I Use a Future<String> to 'Fill In' a Text() Widget Instead of Using FutureBuilder in Flutter?


I'm trying to better understand Futures in Flutter. In this example, my app makes an API call to get some information of type Future<String>. I'd like to display this information in a Text() widget. However, because my String is wrapped in a Future I'm unable to put this information in my Text() widget, and I'm not sure how to handle this without resorting to a FutureBuilder to create the small widget tree.

The following example uses a FutureBuilder and it works fine. Note that I've commented out the following line near the bottom:

Future<String> category = getData();

Is it possible to turn category into a String and simply drop this in my Text() widget?

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

class CocktailScreen extends StatefulWidget {
  const CocktailScreen({super.key});

  @override
  State<CocktailScreen> createState() => _CocktailScreenState();
}

class _CocktailScreenState extends State<CocktailScreen> {
  @override
  Widget build(BuildContext context) {
    Cocktails cocktails = Cocktails();

    Future<String> getData() async {
      var data = await cocktails.getCocktailByName('margarita');
      String category = data['drinks'][0]['strCategory'];
      print('Category: ${data["drinks"][0]["strCategory"]}');
      return category;
    }

    FutureBuilder categoryText = FutureBuilder(
      initialData: '',
      future: getData(),
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          if (snapshot.hasData) {
            return Text(snapshot.data);
          } else if (snapshot.hasError) {
            return Text(snapshot.error.toString());
          }
        }
        return const CircularProgressIndicator();
      },
    );

    //Future<String> category = getData();

    return Center(
      child: categoryText,
    );
  }
}

Here's my Cocktails class:

import 'networking.dart';

const apiKey = '1';
const apiUrl = 'https://www.thecocktaildb.com/api/json/v1/1/search.php';

class Cocktails {
  Future<dynamic> getCocktailByName(String cocktailName) async {
    NetworkHelper networkHelper =
        NetworkHelper('$apiUrl?s=$cocktailName&apikey=$apiKey');
    dynamic cocktailData = await networkHelper.getData();
    return cocktailData;
  }
}

And here's my NetworkHelper class:

import 'package:http/http.dart' as http;
import 'dart:convert';

class NetworkHelper {
  NetworkHelper(this.url);

  final String url;

  Future<dynamic> getData() async {
    http.Response response = await http.get(Uri.parse(url));
    if (response.statusCode == 200) {
      String data = response.body;
      var decodedData = jsonDecode(data);
      return decodedData;
    } else {
      //print('Error: ${response.statusCode}');
      throw 'Sorry, there\'s a problem with the request';
    }
  }
}


Solution

  • The best way is using FutureBuilder:

    FutureBuilder categoryText = FutureBuilder<String>(
            future: getData(),
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              switch (snapshot.connectionState) {
                case ConnectionState.waiting:
                  return Text('Loading....');
                default:
                  if (snapshot.hasError) {
                    return Text('Error: ${snapshot.error}');
                  } else {
                    var data = snapshot.data ?? '';
    
                    return Text(data);
                  }
              }
            },
          ),
    

    but if you don't want to use FutureBuilder, first define a string variable like below and change your adasd to this :

    String category = '';
    
    Future<void> getData() async {
      var data = await cocktails.getCocktailByName('margarita');
      setState(() {
         category = data['drinks'][0]['strCategory'];
      });
    }
    

    then call it in initState :

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

    and use it like this:

    @override
      Widget build(BuildContext context) {
        return Center(
          child: Text(category),
        );
      }
    

    remember define category and getData and cocktails out of build method not inside it.