0

Flutter 3.10: FutureBuilder with PaginatedDataTable from GET Response, but snapshot.data is Empty.

I am creating an app which include a User List page showing records in a PaginatedDataTable inside a FutureBuilder. The PaginatedDataTable includes a DataTableSource, which relies upon a GET request/response which returns a List/, where User refers to a model.

EDITED: Replaced original code samples below. Revised to consolidate into single file for debugging, and included more print statements to assist with debugging through console.

PROBLEM from Console:

{alias: ALIAS, password: password}

AsyncSnapshot<List>(ConnectionState.waiting, null, null, null) null snapshot.hasData failed

200 [{"id":6,"alias":"username","name_first":"fIrStNaMe","name_last":"lAsTnAmE","email":"gmail@gmail.com","phone":"2222222222","date_birth":"2000-12-01"},{"id":1,"alias":"ALIAS","name_first":"FiRsTnAmE","name_last":"LaStNaMe","email":"email@email.com","phone":"9999999999","date_birth":"2000-01-31"}]

user_fetch: after response received [{id: 6, alias: username, name_first: fIrStNaMe, name_last: lAsTnAmE, email: gmail@gmail.com, phone: 2222222222, date_birth: 2000-12-01}, {id: 1, alias: ALIAS, name_first: FiRsTnAmE, name_last: LaStNaMe, email: email@email.com, phone: 9999999999, date_birth: 2000-01-31}]

user_fetch, 200 response received, before return list [{id: 6, alias: username, name_first: fIrStNaMe, name_last: lAsTnAmE, email: gmail@gmail.com, phone: 2222222222, date_birth: 2000-12-01}, {id: 1, alias: ALIAS, name_first: FiRsTnAmE, name_last: LaStNaMe, email: email@email.com, phone: 9999999999, date_birth: 2000-01-31}]

AsyncSnapshot<List>(ConnectionState.done, null, Expected a value of type 'FutureOr<List>', but got one of type 'List', ) null snapshot.hasData failed

CODE BELOW:

import 'dart:convert';
import 'package:flutter_axum/providers/url.dart';
import 'package:flutter/material.dart';
import 'package:flutter_axum/providers/user_auth.dart';
//import 'package:flutter_axum/providers/user_data_table_source.dart';
//import 'package:flutter_axum/providers/user_fetch.dart';
//import 'package:flutter_axum/widgets/paginated_data_table.dart';
import 'package:http/http.dart' as http;
import '../models/user.dart';
import '../providers/user_secure_storage.dart';

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

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

class _HomePageState extends State<HomePage> {
  //final DataTableSource source = UserDataTableSource();
  //final columns = UserDataTableSource().getColumns();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('User Paginated Data Table'),
        actions: [
          IconButton(
              icon: const Icon(Icons.exit_to_app),
              onPressed: () {
                ///logout
                AuthenticationProvider().logoutUser(context: context);
              }),
        ],
      ),
        body: FutureBuilder(
          future: getUsers(),
          builder: (context, snapshot) {
            print(snapshot);
            print(snapshot.data);
            if (ConnectionState.active != null && !snapshot.hasData) {
              print("snapshot.hasData failed");
              return Center(child: Text('Loading'));
            }
            if (ConnectionState.done != null && snapshot.hasError) {
              print("snapshot.hasError failed");
              return Center(child: Text(snapshot.error.toString()));
            } else {
              print("snapshot reaches PaginatedDataTable");
              return PaginatedDataTable(
                  columns: getColumns(),
                  source: UserDataTableSource(source: snapshot.data! as List<User>)
              );
            }
          }
        )
    );
  }
}

Future<List<User>> getUsers() async {

  final StorageService storageService = StorageService();
  final bearer = await storageService.readSecureData('token');
  final url = BaseUrl.baseUrl;
  String _url = "$url/user/list";

  try {
    final request = await http.get(Uri.parse(_url), headers: {'Access-Control-Allow-Origin': '*', 'Authorization': 'Bearer $bearer'});

    print(request.statusCode);
    print(request.body);

    if (request.statusCode == 200 || request.statusCode == 201){

      final list = json.decode(request.body);
      print("user_fetch: after response received");
      print(list);

      if (json.decode(request.body) == null) {
        return list;
      } else {

        print("user_fetch, 200 response received, before return list");
        print(list);

        return list;
      }
    } else {
      List<User> list = [];
      return list;
    }
  } catch (e) {
    return Future.error(e.toString());
  }
}

class UserDataTableSource extends DataTableSource {
  UserDataTableSource({required source}) : userList = source;

  final List<User> userList;

  @override
  DataRow getRow(int index) {
    final User user = userList[index];

    return DataRow.byIndex(index: index, cells: [
      DataCell(Text('${user.id}')),
      DataCell(Text(user.alias)),
      DataCell(Text(user.nameFirst)),
      DataCell(Text(user.nameLast)),
      DataCell(Text(user.email)),
      DataCell(Text(user.phone)),
      DataCell(Text(user.dateBirth)),
      //const DataCell(Text("Edit|Delete")),
    ]);
  }
    @override
    bool get isRowCountApproximate => false;
    @override
    int get rowCount => userList.length;
    @override
    int get selectedRowCount => 0;
}

const String colID = 'ID';
const String colAlias = 'Alias';
const String colNameFirst = 'First Name';
const String colNameLast = 'Last Name';
const String colEmail = 'Email';
const String colPhone = 'Phone';
const String colDateBirth = 'Date of Birth';

List<DataColumn> getColumns(){
  final List<DataColumn> columns =
  <DataColumn>[
    const DataColumn(
      label: Text(colID),
      tooltip: colID,
    ),
    const DataColumn(
      label: Text(colAlias),
      tooltip: colAlias,
    ),
    const DataColumn(
      label: Text(colNameFirst),
      tooltip: colNameFirst,
    ),
    const DataColumn(
      label: Text(colNameLast),
      tooltip: colNameLast,
    ),
    const DataColumn(
      label: Text(colEmail),
      tooltip: colEmail,
    ),
    const DataColumn(
      label: Text(colPhone),
      tooltip: colPhone,
    ),
    const DataColumn(
      label: Text(colDateBirth),
      tooltip: colDateBirth,
    ),
  ];

  return columns;
}

In the PROBLEM output above, you can see the print statements return the Response Status Code, followed by the response.body, then a print of the list following json.decode.

The output shows the NULL snapshot while ConnectionState is 'waiting', but then it returns empty with an error message, "Expected a value of type 'FutureOr<List>', but got one of type 'List'" once ConnectionState returns 'done'.

Any feedback or guidance would be greatly appreciated!

mycotoad
  • 1
  • 2
  • what do you see if you add `print(snapshot)`? – pskink Jun 05 '23 at 05:18
  • @pskink print(snapshot) produces null. print(snapshot.data) outputs null as well. However, when I call UserProvider().getUsers() it returns the full list as expected, sample below: [{id: 6, alias: a, name_first: a, name_last: a, email: a, phone: a, date_birth: 2023-05-30}] – mycotoad Jun 05 '23 at 12:45
  • `print(snapshot)` cannot show `null` as `snapshot` is non-nullable value – pskink Jun 05 '23 at 13:37
  • @pskink Understood, and thanks for your reply. Do you have any suggestion on how to resolve whatever issue is preventing snapshot from remaining NULL? I thought the value or function in 'future:' should be sufficient. I have a similar ListView behind a FutureBuilder in the same app, which works correctly using the same GET request/response. Following sample code (i.e., https://m2.material.io/components/data-tables/flutter#using-data-tables) - the FutureBuilder handles snapshot, it should not be instantiated or set in advance. Any additional input would be greatly appreciated! – mycotoad Jun 05 '23 at 14:13
  • @pskink After some more exploring, I've isolated the following error message from snapshot in the FutureBuilder / PaginatedDataTable. I'm still convinced it's related to the DataTableSource, but I'm not certain how to proceed: **_AsyncSnapshot(ConnectionState.done, null, Expected a value of type 'FutureOr', but got one of type 'List', ) null snapshot.hasData failed_** – mycotoad Jun 05 '23 at 19:41
  • @mycotoad You are returning the wrong response from getUsers() method. You have define the return type of method as List but you are directly returning the json.decode() response which is invalid here. You need to manually convert your json.decode() response which is List here to List by using .map() method of list. Then return the response it will work. – Arbaz Shaikh Jun 09 '23 at 07:04

0 Answers0