mycotoad
mycotoad

Reputation: 1

Flutter PaginatedDataTable from GET Request, Problems Parsing Response List

Using Flutter 3.10 Web, building a List of Users for FutureBuilder with PaginatedDataTable. I have the following in a single dart file:

import 'dart:async';
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: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> {

  @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) {
            if (ConnectionState.active != null && !snapshot.hasData) {
              print("ConnectionState is active, snapshot.hasData");
              // print(snapshot);
              // print(snapshot.data);
              // print(snapshot.stackTrace);
              return Center(child: Text('Loading'));
            }
            if (ConnectionState.done != null && snapshot.hasError) {
              print("ConnectionState is done, snapshot.hasError");
              return Center(child: Text(snapshot.error.toString()));
            } else {
              print("ConnectionState done, snapshot has data without errors.");
              print(snapshot);
              print(snapshot.data);
              print("snapshot.data is about to reach PaginatedDataTable.");
              return PaginatedDataTable(
                  columns: getColumns(),
                  source: UserDataTableSource(source: snapshot.data as List<User>)
              );
            }
          }
        )
    );
  }
}

// List<User> parseUsers(List<dynamic> snapshotData) {
//   final parsed = jsonDecode(snapshotData).cast<Map<String,dynamic>>();
//   return parsed.map<User>((json) => User.fromJson(json)).toList();
// }

// List<User> parseUsers(String requestBody) {
//   final parsed = jsonDecode(requestBody).cast<Map<String,dynamic>>();
//   return parsed.map<User>((json) => User.fromJson(json)).toList();
// }

Future getUsers() async {

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

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

    print("Status Code: "+response.statusCode.toString());
    print(response.body);

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

      print("user_fetch: after response received, before json.decode");
      print(json.decode(response.body));

      final list = json.decode(response.body);
      
      // List<User> list = json.decode(response.body);

      // List<User> list(String resBody) => List<User>.from(json.decode(response.body).map((x) => User.fromJson(x)));

      // List<User> list = userFromJson(response.body);

      // List<User> list = (response.body as List)
      //     .map((body) => User.fromJson(body))
      //     .toList();

      print("user_fetch: after response received, after json.decode");
      print(list);

      if (list == null) {

        print("user_fetch: after response received, after json.decode, request.body is null");
        print(list);
        return DefSource;

      } else {

        print("user_fetch: after response received, after json.decode, request.body is not null");
        print(list);

        return list;
      }
    } else {

      print("WTF");
      return DefSource;
    }
  } 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) {
    print("Inside UserDataTableSource");
    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;
}

class DefSource extends DataTableSource {
  @override
  DataRow getRow(int index) => DataRow.byIndex(
    index: index,
    cells: [
      DataCell(Text('id #$index')),
      DataCell(Text('alias $index')),
      DataCell(Text('first name $index')),
      DataCell(Text('last name $index')),
      DataCell(Text('email $index')),
      DataCell(Text('phone $index')),
      DataCell(Text('birth date $index')),
    ],
  );
  @override
  int get rowCount => 10;
  @override
  bool get isRowCountApproximate => false;
  @override
  int get selectedRowCount => 0;
}

User Model for reference:

import 'dart:convert';

List<User> userFromJson(String str) => List<User>.from(json.decode(str).map((x) => User.fromJson(x)));

String userToJson(List<User> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));

class User {
  User({
    required this.id,
    required this.alias,
    required this.nameFirst,
    required this.nameLast,
    required this.email,
    required this.phone,
    required this.dateBirth,
    this.password,
  });

  int id;
  String alias;
  String nameFirst;
  String nameLast;
  String email;
  String phone;
  DateTime dateBirth;
  String? password;

  factory User.fromJson(Map<String, dynamic> json) => User(
    id: json["id"],
    alias: json["alias"],
    nameFirst: json["nameFirst"],
    nameLast: json["nameLast"],
    email: json["email"],
    phone: json["phone"],
    dateBirth: DateTime.parse(json["date_birth"]),
    password: json["password"],
  );

  // factory User.fromJson(Map<String, dynamic> json) {
  //   Iterable list = json['users'];
  //   print(list.runtimeType);
  //   List<User> users = list.map((i) =>
  //       User.fromJson(i)).toList();
  //   return User(
  //       id: json["id"],
  //       alias: json["alias"],
  //       nameFirst: json["nameFirst"],
  //       nameLast: json["nameLast"],
  //       email: json["email"],
  //       phone: json["phone"],
  //       dateBirth: DateTime.parse(json["date_birth"]),
  //       password: json["password"],
  //
  //   );
  // }

  Map<String, dynamic> toJson() => {
    "id": id,
    "alias": alias,
    "nameFirst": nameFirst,
    "nameLast": nameLast,
    "email": email,
    "phone": phone,
    "date_birth": "${dateBirth.year.toString().padLeft(4, '0')}-${dateBirth.month.toString().padLeft(2, '0')}-${dateBirth.day.toString().padLeft(2, '0')}",
    "password": password,
  };
}

I believe the source of the problem is within "Future getUsers" where I have attempted the following (over several failed attempts) to parse the Response:

final list = json.decode(response.body);

final List<User> list = json.decode(response.body);

List<User> list(String resBody) => List<User>.from(json.decode(response.body).map((x) => User.fromJson(x)));

List<User> list = userFromJson(response.body);

List<User> list = (response.body as List)
    .map((body) => User.fromJson(body))
    .toList();

Using final list = json.decode(response.body); from the options above, I get closest to pushing snapshot through the PaginatedDataTable as part of the source (or so I believe based upon prints to console).

Expected a value of type 'List<User>', but got one of type 'List<dynamic>'

======== Exception caught by widgets library =======================================================
The following TypeErrorImpl was thrown building FutureBuilder<dynamic>(dirty, state: _FutureBuilderState<dynamic>#ff8f7):
Expected a value of type 'List<User>', but got one of type 'List<dynamic>'

The relevant error-causing widget was: 
  FutureBuilder<dynamic> FutureBuilder:file:///home/user/projects/flutter_axum/lib/views/user_data_table.dart:33:15
When the exception was thrown, this was the stack: 
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 288:49      throw_
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 121:3       castError
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 529:12  cast
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart 638:14     as_C
packages/flutter_axum/views/user_data_table.dart 152:34                           new
packages/flutter_axum/views/user_data_table.dart 53:29                            <fn>
packages/flutter/src/widgets/async.dart 612:48                                    build
packages/flutter/src/widgets/framework.dart 5198:27                               build
packages/flutter/src/widgets/framework.dart 5086:15                               performRebuild
packages/flutter/src/widgets/framework.dart 5251:11                               performRebuild
packages/flutter/src/widgets/framework.dart 4805:7                                rebuild
packages/flutter/src/widgets/framework.dart 2780:18                               buildScope
packages/flutter/src/widgets/binding.dart 903:9                                   drawFrame
packages/flutter/src/rendering/binding.dart 358:5                                 [_handlePersistentFrameCallback]
packages/flutter/src/scheduler/binding.dart 1284:15                               [_invokeFrameCallback]
packages/flutter/src/scheduler/binding.dart 1214:9                                handleDrawFrame
packages/flutter/src/scheduler/binding.dart 1072:5                                [_handleDrawFrame]
lib/_engine/engine/platform_dispatcher.dart 1236:13                               invoke
lib/_engine/engine/platform_dispatcher.dart 244:5                                 invokeOnDrawFrame
lib/_engine/engine/initialization.dart 190:45                                     <fn>
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 367:37  _checkAndCall
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 372:39  dcall
====================================================================================================

The other attempts to parse are resulting in a fail on the HomePage, such that the "Loading" screen appears while waiting for a proper response/snapshot which never arrives. The stacktrace is empty and the app basically enters a hanging state.

I have included print statements and some commented code in the application to provide the following for debugging:

print("Status Code: "+response.statusCode.toString());

Status Code: 200

print(request.body)

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

print(json.decode(request.body))

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

After returning the list from the request to the FutureBuilder for snapshot, validation is performed for snapshot.hasData and snapshot.hasError, and both are passing.

print(snapshot);

AsyncSnapshot<dynamic>(ConnectionState.done, [{id: 1, alias: ALIAS, name_first: FiRsTnAmE, name_last: LaStNaMe, email: email, phone: 9999999999, date_birth: 2000-01-31}, {id: 6, alias: username, name_first: fIrStNaMe, name_last: lAsTnAmE, email: email, phone: 2222222222, date_birth: 2000-12-01}], null, null)

print(snapshot.data);

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

I have referred to several stackflow links, without success. I am almost certain my failure is tied to the parsing of the API Response, but I'm running out of idea on how to move forward. A second pair of eyes, and some feedback would be greatly appreciated.

Upvotes: 0

Views: 73

Answers (0)

Related Questions