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

1,520

Solution 1

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

Solution 2

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)}';
}
Share:
1,520
ANUP SAJJAN
Author by

ANUP SAJJAN

Updated on December 15, 2022

Comments

  • ANUP SAJJAN
    ANUP SAJJAN over 1 year

    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!

    • user1012500
      user1012500 over 3 years
      Hi did you resolve this - I'm facing the same issue?
    • ANUP SAJJAN
      ANUP SAJJAN over 3 years
      Hi @user1012500, kheral answer does solve it. But if you don't wanna change your code, then you can have a look at this solution, which is by the way is just a work-around. stackoverflow.com/a/61918380/12257758
  • ANUP SAJJAN
    ANUP SAJJAN over 3 years
    Thanks for the response. But I think you took the question wrong. My question was, if you have 2 buttons on top of the screen called delivered and cancelled and if you press delivered then the below list only shows the delivered orders, if you press cancelled, then it shows only cancelled orders. But this was not happening.
  • ANUP SAJJAN
    ANUP SAJJAN over 3 years
    thankyou! This seems to work correctly. But can you answer, why my code was not working? Because even after doing setState and updating the list with new values, the UI was not refreshing.
  • Kherel
    Kherel over 3 years
    it's hard to answer without seeng full example, if you show a git repository with the project, I probably could answer your question.
  • ANUP SAJJAN
    ANUP SAJJAN over 3 years
    Well, this was an old project of mine, I had already fixed this. I think I have provided a decent amount of code in question. But it's okay if you don't get it. Thanks for the help:)