SliverChildBuilderDelegate Not rebuilding its children when the data is changed using setState

Viewed 1797

I am having a UI which shows user the delivered Items and cancelled items in a grocery app .

'Delivered' ->pressing this button shows the delivered item card widgets.

and on the same page

'Cancelled' ->pressing this button shows the cancelled item card widgets.

I am using SliverChildBuilderDelegate to build card widgets based on the current input of the user .(Initially when the page is loaded I am showing all delivered Items .)

SliverList(
              delegate: SliverChildBuilderDelegate(
                (context, index) => MyOrdersCard(
                  orderNumber: orderList[index][0],
                  trackingNumber: orderList[index][1],
                  quantity: orderList[index][2],
                  totalAmount: orderList[index][3],
                  status: orderList[index][4],
                  date: orderList[index][5],
                  statusColor: orderList[index][6],
                ),
                childCount: orderList.length,
              ),
            ),

I am filling the list ('orderList' ) in the below code with the status variable having the status of the order .

var orderList = [];

handleClick(status) {

     setState(() {

          orderList = [];

          var color;
          switch (status) {
              case 'Processing':
                color = Colors.yellow;
                activeBtn = 2;
                break;
              case 'Cancelled':
                color = Colors.red;
                activeBtn = 3;
                break;
              case 'Delivered':
                color = Colors.green;
                activeBtn = 1;
                break;
          }
          for (int i = 0; i < 10; i++) {
                  orderList.add([
                    '123457' + '$i',
                    '17abc999',
                    3,
                    500,
                    status,
                    '0$i-12-2019',
                    color
                  ]);
          }

});

When the button is pressed the above function is called with the status as the argument .

        RaisedButton(
                      child: Text('Delivered'),                      
                      onPressed: () {
                        handleClick('Delivered');
                      },
                    ),
        RaisedButton(
                      child: Text('Processing'),
                      onPressed: () {
                        handleClick('Processing');
                      },
                    ),

Now , the UI displays all delivered card items correctly , but if user presses 'Cancelled' Button then the 'handleClick' function is called and the list is filled with all the new cancelled orders and status is also filled as cancelled. But the sliverChildBuilder is not getting refreshed . The UI remains the same . If I scroll below then I can see the cancelled orders , because they are builded freshly as the scroll down happens .Now if I scroll up again then I can see all cancelled orders because the old builded cards were destroyed and sliverChildBuilder builded the cards as the scroll up happened on the fly . I don't want this. I want the sliverChildBuilder to destroy all old children created for 'delivered' items when the 'cancelled' Button is pressed and render all new cancelled orders from the new list ('orderList'). Is there any way I could do this ? Hope my problem is clear!

2 Answers

I hope I'm not too late. And my answer would be useful.

import 'dart:math' as math;

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        body: SafeArea(
          child: MyHomePage(),
        ),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> {
  List<Order> orderList = [
    Order(status: Status.delivered),
    Order(status: Status.canceled),
    Order(status: Status.delivered),
  ];

  void _showStatus(int index, Status status) {
    setState(() {
      currentStatusToShow = status;
    });
  }

  Status currentStatusToShow = Status.canceled;
  @override
  Widget build(BuildContext context) {
    var showList = orderList
        .where((order) => order.status == currentStatusToShow)
        .toList();
    return CustomScrollView(
      slivers: [
        SliverPadding(
          padding: EdgeInsets.only(bottom: 20, top: 50),
          sliver: SliverToBoxAdapter(
            child: Column(
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: [
                    RaisedButton(
                      color: Colors.red,
                      onPressed: () => _showStatus(0, Status.canceled),
                      child: Text('Cancel'),
                    ),
                    RaisedButton(
                      color: Colors.green,
                      onPressed: () => _showStatus(0, Status.delivered),
                      child: Text('delivered'),
                    )
                  ],
                )
              ],
            ),
          ),
        ),
        SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) => MyOrdersCard(
              order: showList[index],
            ),
            childCount: showList.length,
          ),
        ),
      ],
    );
  }
}

class MyOrdersCard extends StatelessWidget {
  const MyOrdersCard({
    Key key,
    this.order,
    this.changeStatus,
  }) : super(key: key);

  final Order order;
  final void Function(Status) changeStatus;

  Color get color {
    switch (order.status) {
      case Status.processing:
        return Colors.yellow;
      case Status.canceled:
        return Colors.red;
      case Status.delivered:
      default:
        return Colors.green;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      height: 100,
      color: color,
      child: Column(
        children: [
          Text(order.status.toString()),
          SizedBox(height: 20),
          Text(order.orderId)
        ],
      ),
    );
  }
}

enum Status { processing, canceled, delivered }

class Order {
  Order({this.status});
  Status status;

  String orderId = 'SA-${math.Random().nextInt(1040)}';
}

If you use SliverChildBuilderDelegate, you should check child widget for key.

example:

List<String> data = ['first', 'second'];

SliverList(
    delegate:
        SliverChildBuilderDelegate((context, index) {
            return ChildWidget(data[index], UniqueKey());
        }, childCount: data.length),
);

Keys helps rebuilding children when you update data.

class ChildWidget extends StatelessWidget {
const ChildWidget(this.sourceData, key) : super(key: key);
    final String sourceData;

@override
Widget build(BuildContext context) {
    return Text(sourceData ?? '');
    }
}

If you need more information how keys work, please visit https://api.flutter.dev/flutter/widgets/GlobalKey-class.html or https://www.youtube.com/watch?v=kn0EOS-ZiIc

Related