Home > Net >  Flutter FutureBuilder refreshes all the UI with data reload
Flutter FutureBuilder refreshes all the UI with data reload

Time:09-17

I have a FutureBuilder that gets data from an API, I'll be reloading this data a lot, so the FutureBuilder will refresh a lot.

The problem I'm having is the entire widget structure gets rebuilt every time, and the ListViews and scrolls get reset back to their original positions. I want only the OrderStack to be rebuilt in place. Is this possible? If so how?

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

  @override
  ServingStationScreenState createState() => ServingStationScreenState();
}

class ServingStationScreenState extends State<ServingStationScreen> {

  late Future<Map<String, List<Order>>> ordersFuture;
  @override
  void initState() {
    super.initState();
    ordersFuture = getOrders();
  }

  Future<Map<String, List<Order>>> getOrders() async {
    try {
      var orders = (await APIService().getOrders())!;
      return {
        'ordered': orders.where((order) {
          return order.itemsOrdered!.isNotEmpty;
        }).toList(),
        'preparingAndAwaitingCancellation': orders.where((order) {
          return order.itemsPreparingAndAwaitingCancellation!.isNotEmpty;
        }).toList(),
      };
    } catch (e) {
      return Future.error(e);
    }
  }

  final List<String> emptyImages = [
    'assets/svg/barbecue.svg',
    'assets/svg/beer.svg',
    'assets/svg/breakfast.svg',
    'assets/svg/cooking.svg',
    'assets/svg/fresh-drink.svg',
    'assets/svg/ice-cream.svg',
    'assets/svg/mint-tea.svg',
    'assets/svg/pizza.svg',
    'assets/svg/special-event.svg',
    'assets/svg/tea.svg',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: Navigation(
        allowBack: false,
        activePage: 'Serving Station',
        restaurantName: 'Test Restaurant',
        actions: [
          Padding(
            padding: const EdgeInsets.only(right: 10),
            child: IconButton(
              onPressed: () {
                setState(() {
                  ordersFuture = getOrders();
                });
                // Get.to(() => const ServingStationHistoryScreen());
              },
              icon: const Icon(
                Icons.history_rounded,
                color: Colors.white,
                size: 26.0,
              ),
            ),
          ),
        ],
      ),
      extendBodyBehindAppBar: true,
      backgroundColor: const Color.fromRGBO(21, 21, 32, 1),
      body: FutureBuilder(
        future: ordersFuture,
        builder: (context, snapshot) {
          switch(snapshot.connectionState) {
            case ConnectionState.none:
            case ConnectionState.waiting:
            case ConnectionState.active:
              return const CircularProgressIndicator(
                color: Color.fromRGBO(93, 194, 188, 1),
              );
            case ConnectionState.done:
              if(snapshot.hasError) {
                return Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      Container(
                        padding: const EdgeInsets.only(bottom: 100),
                        child: Opacity(
                          opacity: 0.5,
                          child: SvgPicture.asset(
                            'assets/svg/server.svg',
                            height: SizerUtil.deviceType == DeviceType.tablet ? 500 : 200,
                            theme: const SvgTheme(
                              currentColor: Color.fromRGBO(93, 194, 188, 1),
                            ),
                          ),
                        ),
                      ),
                      Text(
                        'Service Unreachable',
                        textAlign: TextAlign.center,
                        style: TextStyle(fontSize: SizerUtil.deviceType == DeviceType.tablet ? 30 : 18, color: Colors.white),
                      ),
                      Text(
                        'Error: ${snapshot.error}',
                        textAlign: TextAlign.center,
                        style: TextStyle(fontSize: SizerUtil.deviceType == DeviceType.tablet ? 15 : 10, color: Colors.red),
                      ),
                    ],
                  ),
                );
              } else {
                final data = snapshot.data as Map<String, List<Order>>;
                return data['preparingAndAwaitingCancellation']!.isNotEmpty || data['ordered']!.isNotEmpty ? SafeArea(
                  bottom: false,
                  child: ListView(
                    shrinkWrap: false,
                    children: [
                      data['preparingAndAwaitingCancellation']!.isNotEmpty ? Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Container(
                            padding: const EdgeInsets.only(top: 20, left: 20, bottom: 20),
                            child: Row(
                              children: [
                                const Text('Current Items',
                                  style: TextStyle(
                                    color: Colors.white,
                                    fontWeight: FontWeight.bold,
                                    fontSize: 30,
                                  ),
                                ),
                                Container(
                                  margin: const EdgeInsets.only(left: 10, right: 10),
                                  padding: const EdgeInsets.only(left: 5, right: 5, top: 2, bottom: 2),
                                  decoration: BoxDecoration(
                                      color: Colors.yellow[200],
                                      border: Border.all(
                                        color: Colors.transparent,
                                      ),
                                      borderRadius: const BorderRadius.all(Radius.circular(20))
                                  ),
                                  child: const Text(
                                    'Preparing',
                                    style: TextStyle(
                                      color: Colors.black,
                                      fontWeight: FontWeight.bold,
                                      fontSize: 15,
                                    ),
                                  ),
                                ),
                                Container(
                                  padding: const EdgeInsets.only(left: 5, right: 5, top: 2, bottom: 2),
                                  decoration: BoxDecoration(
                                      color: Colors.red[200],
                                      border: Border.all(
                                        color: Colors.transparent,
                                      ),
                                      borderRadius: const BorderRadius.all(Radius.circular(20))
                                  ),
                                  child: const Text(
                                    'Awaiting Cancellation',
                                    style: TextStyle(
                                      color: Colors.black,
                                      fontWeight: FontWeight.bold,
                                      fontSize: 15,
                                    ),
                                  ),
                                ),
                              ],
                            ),
                          ),
                          SingleChildScrollView(
                            scrollDirection: Axis.horizontal,
                            child: SizedBox(
                              width: data['preparingAndAwaitingCancellation']!.length * 500 < 2500 ? data['preparingAndAwaitingCancellation']!.length * 500 : 2500,
                              child: MasonryGridView.count(
                                physics: const NeverScrollableScrollPhysics(),
                                crossAxisCount: data['preparingAndAwaitingCancellation']!.length < 5 ? data['preparingAndAwaitingCancellation']!.length : 5,
                                mainAxisSpacing: 10,
                                crossAxisSpacing: 10,
                                shrinkWrap: true,
                                itemCount: data['preparingAndAwaitingCancellation']!.length,
                                padding: const EdgeInsets.only(left: 20, right: 20),
                                itemBuilder: (context, index) {
                                  return OrderStack(
                                    order: data['preparingAndAwaitingCancellation']![index],
                                    filter: OrderStatusFilter.preparingAndAwaitingCancellation,
                                  );
                                },
                              ),
                            ),
                          ),
                        ],
                      ) : Container(),
                      data['ordered']!.isNotEmpty ? Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Container(
                            padding: const EdgeInsets.only(top: 20, left: 20, bottom: 20),
                            child: Row(
                              children: [
                                const Text('New Items',
                                  style: TextStyle(
                                    color: Colors.white,
                                    fontWeight: FontWeight.bold,
                                    fontSize: 30,
                                  ),
                                ),
                                Container(
                                  margin: const EdgeInsets.only(left: 10, right: 10),
                                  padding: const EdgeInsets.only(left: 5, right: 5, top: 2, bottom: 2),
                                  decoration: BoxDecoration(
                                      color: Colors.white,
                                      border: Border.all(
                                        color: Colors.transparent,
                                      ),
                                      borderRadius: const BorderRadius.all(Radius.circular(20))
                                  ),
                                  child: const Text(
                                    'Ordered',
                                    style: TextStyle(
                                      color: Colors.black,
                                      fontWeight: FontWeight.bold,
                                      fontSize: 15,
                                    ),
                                  ),
                                ),
                              ],
                            ),
                          ),
                          SingleChildScrollView(
                            scrollDirection: Axis.horizontal,
                            child: SizedBox(
                              width: data['ordered']!.length * 500 < 2500 ? data['ordered']!.length * 500 : 2500,
                              child: MasonryGridView.count(
                                physics: const NeverScrollableScrollPhysics(),
                                crossAxisCount: data['ordered']!.length < 5 ? data['ordered']!.length : 5,
                                mainAxisSpacing: 10,
                                crossAxisSpacing: 10,
                                shrinkWrap: true,
                                itemCount: data['ordered']!.length,
                                padding: const EdgeInsets.only(left: 20, right: 20),
                                itemBuilder: (context, index) {
                                  return OrderStack(
                                    order: data['ordered']![index],
                                    filter: OrderStatusFilter.ordered,
                                  );
                                },
                              ),
                            ),
                          ),
                        ],
                      ) : Container(),
                    ],
                  ),
                ) : Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      Container(
                        padding: const EdgeInsets.only(bottom: 100),
                        child: Opacity(
                          opacity: 0.5,
                          child: SvgPicture.asset(
                            emptyImages.elementAt(Random().nextInt(emptyImages.length)),
                            height: SizerUtil.deviceType == DeviceType.tablet ? 500 : 200,
                            theme: const SvgTheme(
                              currentColor: Color.fromRGBO(93, 194, 188, 1),
                            ),
                          ),
                        ),
                      ),
                      Text(
                        'There are currently no pending orders.',
                        textAlign: TextAlign.center,
                        style: TextStyle(fontSize: SizerUtil.deviceType == DeviceType.tablet ? 30 : 18, color: Colors.white),
                      ),
                    ],
                  ),
                );
              }
          }
        },
      ),
    );
  }
}

CodePudding user response:

You should drop your FutureBuilder as far down your widget tree as possible, wrapping only the widgets that are affected by that future. It looks like you have a SingleChildScrollView that holds the OrderStack, so you might wrap only that widget in the FutureBuilder. Then, only that portion of the UI is updated when the ordersFuture completes.

Also, if this is essentially a stream of data, then consider using StreamBuilder instead - FutureBuilder is typically used to wait for one-time completion of a task, once (e.g. loading the data to display in a list), not necessarily to refresh it.

  • Related