[SOLVED] JSON API is not updating UI in Flutter

Issue

This Content is from Stack Overflow. Question asked by Mohammed Bekele

I’m using FutureBuilder for fetching API request. but it only builds once and it doesn’t update ui unless hot reloded. i aslo tried StreamBuilder and convert the future method to stream. so how can i update my ui. please i already asked this question and got no answer. whay is this happening?

model

import 'dart:convert';

List<User> usersFromJson(String str) =>
    List<User>.from(json.decode(str).map((json) => User.fromJson(json)));
String usersToJson(List<User> data) =>
    json.encode(List<dynamic>.from(data.map((e) => e.toJson())));

class User {
  int? id;
  String? name;
  String? username;
  String? email;
  String? role;

  User({this.id, this.name, this.username, this.email, this.role});

  @override
  toString() => 'User: $name';

  factory User.fromJson(Map<String, dynamic> json) => User(
      email: json['email'],
      name: json['name'],
      id: json['id'],
      username: json['username'],
      role: json['role']);

  Map<String, dynamic> toJson() =>
      {"name": name, "username": username, "email": email, "role": role};
}

api call

Future fetchUsers() async {
  Uri url = Uri.parse("${BASE_URL}user");
  final response = await http.get(url, headers: <String, String>{
    'Content-Type': 'application/json; charset=UTF-8',
    'Authorization': 'Bearer $TOKEN',
  });

  var userview = <User>[];

  if (response.statusCode == 200) {
    var jsonres = json.decode(response.body);
    for (var res in jsonres) {
      userview.add(User.fromJson(res));
    }
  }
  return userview;
}

Future createUser(String name, String username, String email, String password,
    String roles) async {
  Uri url = Uri.parse("${BASE_URL}user");
  final response = await http
      .post(url,
          headers: <String, String>{
            'Content-Type': 'application/json; charset=UTF-8',
            'Authorization': 'Bearer $TOKEN',
          },
          body: jsonEncode(<String, dynamic>{
            'name': name,
            'email': email,
            'username': username,
            'password': password,
            'roles': roles
          }))
      .then((value) => fetchUsers());
}

and my page

StreamBuilder(
                        stream: fetchUsers().asStream(),
                        builder: (context, snapshot) {
                          if (snapshot.hasData) {
                            return Column(
                              children: [
                                PaginatedDataTable(
                                    sortColumnIndex: sortColumnIndex,
                                    sortAscending: isAscending,
                                    columns: [
                                      DataColumn(
                                          label: const Text("Id"),
                                          onSort: onSort),
                                      DataColumn(
                                          label: const Text("Name"),
                                          onSort: onSort),
                                      DataColumn(
                                          label: const Text("Username"),
                                          onSort: onSort),
                                      DataColumn(
                                          label: const Text("Email"),
                                          onSort: onSort),
                                      DataColumn(
                                          label: const Text("Roles"),
                                          onSort: onSort),
                                      DataColumn(
                                          label: const Text("Actions"),
                                          onSort: onSort),
                                    ],
                                    header: Row(
                                      mainAxisAlignment:
                                          MainAxisAlignment.spaceBetween,
                                      children: [
                                        Text(
                                          "Manage Users",
                                          style: TextStyle(
                                              fontSize: width * 0.04,
                                              fontWeight: FontWeight.normal),
                                        ),
                                        MaterialButton(
                                          onPressed: () {
                                            showMaterialModalBottomSheet(
                                                context: context,
                                                builder: (context) => SizedBox(
                                                      height: height * 0.9,
                                                      child:
                                                          BottomSheetWidget(),
                                                    ),
                                                shape:
                                                    const RoundedRectangleBorder(
                                                        borderRadius:
                                                            BorderRadius.only(
                                                                topLeft: Radius
                                                                    .circular(
                                                                        15),
                                                                topRight: Radius
                                                                    .circular(
                                                                        15))));
                                          },
                                          color: const Color.fromRGBO(
                                              30, 119, 66, 1),
                                          shape: RoundedRectangleBorder(
                                              borderRadius:
                                                  BorderRadius.circular(10)),
                                          child: Text(
                                            "Add User",
                                            style: TextStyle(
                                                color: Colors.white,
                                                fontSize: width * 0.03),
                                          ),
                                        )
                                      ],
                                    ),
                                    source: dataSource(
                                        snapshot.data! as List<User>))
                              ],
                            );
                          } else if (snapshot.hasError) {
                            return Text("${snapshot.error}");
                          }
                          return const Center(
                              child: CircularProgressIndicator());
                        })

additionally i’m using paginated datatable and this is the code for that.(it’s related)

in the page

DataTableSource dataSource(List<User> userList) =>
      MyTable(datasList: userList, context: context);

and the datasource page i’m calling the createUser here.

class MyTable extends DataTableSource {
  MyTable({required this.datasList, required this.context});
  final List<User> datasList;
  BuildContext context;

  Widget Button(String title, Color color, String id) {
    return MaterialButton(
      onPressed: () {
        //deleteUser(id);
        //updateUser(id, title);
        createUser("name2", "user2", "email2@email.com", "password", "Admin");
      },
      child: Text(
        title,
        style: const TextStyle(color: Colors.white),
      ),
      color: color,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
    );
  }

  @override
  DataRow? getRow(int index) {
    return DataRow.byIndex(index: index, cells: [
      DataCell(Text(datasList[index].id.toString())),
      DataCell(ConstrainedBox(
        constraints: const BoxConstraints(maxWidth: 100),
        child: Text(
          datasList[index].name.toString(),
          overflow: TextOverflow.ellipsis,
        ),
      )),
      DataCell(ConstrainedBox(
          constraints: const BoxConstraints(maxWidth: 100),
          child: Text(
            datasList[index].username.toString(),
            overflow: TextOverflow.ellipsis,
          ))),
      DataCell(ConstrainedBox(
          constraints: const BoxConstraints(maxWidth: 100),
          child: Text(
            datasList[index].email.toString(),
            overflow: TextOverflow.ellipsis,
          ))),
      DataCell(ConstrainedBox(
          constraints: const BoxConstraints(maxWidth: 100),
          child: Text(
            datasList[index].role.toString(),
            overflow: TextOverflow.ellipsis,
          ))),
      DataCell(Row(
        children: [
          Button("Edit", Colors.lightBlue, datasList[index].id.toString()),
          const SizedBox(
            width: 5,
          ),
          Button("Delete", Colors.red, datasList[index].id.toString()),
        ],
      )),
    ]);
  }

  @override
  bool get isRowCountApproximate => false;

  @override
  int get rowCount => datasList.length;

  @override
  int get selectedRowCount => 0;
}



Solution

StreamBuilder class

Widget rebuilding is scheduled by each interaction, using
State.setState, but is otherwise decoupled from the timing of the
stream.


i see the code and can’t found any issue. but im bit doubt with asStream().

as documentation said: asStream method:

Creates a Stream containing the result of this future.

maybe you can try with this : stream.fromFuture

found the issue here: https://stackoverflow.com/a/55169382/12838877
since your fetch method is not common , it is a Future method that return value of Future value.

Stream.fromFuture(fetchUsers)

it’s just my doubt. hope solve the problem. because it’s too long to explain in the comments, so I’ll put it in the answer section


This Question was asked in StackOverflow by Mohammed Bekele and Answered by pmatatias It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.

people found this article helpful. What about you?