Home > Enterprise >  Can I Use a Future<String> to 'Fill In' a Text() Widget Instead of Using FutureBuild
Can I Use a Future<String> to 'Fill In' a Text() Widget Instead of Using FutureBuild

Time:11-23

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';
    }
  }
}

CodePudding user response:

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() {
    // TODO: implement initState
    super.initState();
  }

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.

CodePudding user response:

Yes, you can achieve getting Future value and update the state based on in without using Using FutureBuilder, by calling the Future in the initState(), and using the then keyword, to update the state when the Future returns a snapshot.

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

  @override
  State<StatefuleWidget> createState() => _StatefuleWidgetState();
}

class _StatefuleWidgetState extends State<StatefuleWidget> {
  String? text;

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

  @override
  void initState() {
    getData().then((value) {
      setState(() {
        text = value;
      });
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Text(text ?? 'Loading');
  }
}

here I made the text variable nullable, then in the implementation of the Text() widget I set to it a loading text to be shown until the it Future is done0

  • Related