flutter
flutter

Reputation: 6766

How do you convert a list future to a list to use as a variable not a widget?

I'm trying to implement the PaginatedDataTable class in flutter. A required field in the constructor of this class, is the class DataTableSource. Looking at the data table example in the material section of the flutter gallery examples here. There is a member variable for the DataTableSource called List<Dessert> _desserts where it's values are hard coded. In my implementation, I'm making a http call and returning some json to be decoded.

List<Result> parseResults(String responseBody) {
  final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();

  return parsed.map<Result>((json) => Result.fromJson(json)).toList();
}

Future<List<Result>> fetchResults(http.Client client) async {
  final response = await client.get('https://api.myjson.com/bins/j5xau');

  // Use the compute function to run parseResults in a separate isolate
  return compute(parseResults, response.body);

In my DataTableSource class I'm not sure how to instantiate the list.

`final List<Result> results = fetchResults(http.Client);` 

doesn't compile because fetchResults() returns a future. If I change the return type to a future the results does compile but I need my returned json to be of type List so I can use methods like sort etc..How should I convert the future to a list.

Upvotes: 2

Views: 12241

Answers (1)

Ringil
Ringil

Reputation: 6537

In your DataTableSource class just remove the results variable. Then in your build function, you can use a FutureBuilder widget like this:

FutureBuilder<List<Result>>(
  future: fetchResults(http.Client), 
  builder: (BuildContext context, AsyncSnapshot<List<Result>> snapshot) {
    switch (snapshot.connectionState) {
      case ConnectionState.none:
        return Text('Press button to start.');
      case ConnectionState.active:
      case ConnectionState.waiting:
        return Text('Awaiting result...');
      case ConnectionState.done:
        if (snapshot.hasError)
          return Text('Error: ${snapshot.error}');
        return Text('Result: ${snapshot.data}');
    }
    return null; // unreachable
  },
)

Note that snapshot.data is a List<Result> now and you can use it in the same way you were using it back when it was a hard coded value.

Edit:

If you don't want to use a FutureBuilder I would suggest that you have a function that basically modifies the value of the results when the http call completes. Here's an example of what I mean:

Make DessertDataSource take in a List<Result> in its constructor to define the value of results like so:

class DessertDataSource extends DataTableSource {
  final List<Result> results;
  DessertDataSource(this.results);
  // rest of the class
}

In the _DataTableDemoState, make _dessertsDataSource no longer final and change its initial value to DessertDataSource([]). Also, add a boolean indicating when the data is loaded already or not.

class _DataTableDemoState extends State<DataTableDemo> {
  // other fields!
  DessertDataSource _dessertsDataSource = DessertDataSource([]);
  bool isLoaded = false;

Then add the following function to _DataTableDemoState. The boolean ensures we only make the http call once.

Future<void> getData() async {
  final results = await fetchResults(http.Client);
  if (!isLoaded) {
      setState(() {
        _dessertsDataSource = DessertDataSource(results);
        isLoaded = true;
      });
  }
}

Finally call that function upon pressing a button or some other trigger or maybe just at the beginning of your build function.

@override
Widget build(BuildContext context) {
    getData();
    return MYWidget();
}

Then whenever the data is returned from the http call, the widget will automatically update with the new data.

Upvotes: 4

Related Questions