Avoid ListView's unwanted refresh

241

To me the easiest solution would be just make it stateless and use a Getx class.

class ScheduleController extends GetxController {
  var chosenData;

  void updateChosenData(var data) {
    chosenData = data;
    update();
  }
}

And your Schedule.dart would look like this:

class Schedule extends StatelessWidget {
  final FirebaseFirestore _db = FirebaseFirestore.instance;
  final DateTime _yesterday = DateTime.now().subtract(Duration(days: 1));

  @override
  Widget build(BuildContext context) {
    final controller = Get.put(ScheduleController());
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Expanded(
          child: StreamBuilder<QuerySnapshot>(
            stream: _db
                .collection('Schedule')
                .where('date', isGreaterThan: _yesterday)
                .limit(10)
                .orderBy('date')
                .snapshots(),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.active) {
                return ListView.builder(
                  itemCount: snapshot.data!.docs.length,
                  itemBuilder: (context, index) {
                    var data = snapshot.data!.docs[index];
                    return ListTile(
                      leading: Icon(Icons.person),
                      title: Text(data['project'],
                          style: TextStyle(fontWeight: FontWeight.bold)),
                      subtitle: Text(data['parkour']),
                      onTap: () => controller.updateChosenData(data), // calls method from GetX class
                    );
                  },
                );
              } else {
                return Center(child: CupertinoActivityIndicator());
              }
            },
          ),
        ),
        VerticalDivider(),
        Expanded(
          child: Container(
            alignment: Alignment.center,
            color: Colors.black26,
            child: GetBuilder<ScheduleController>(
              builder: (controller) => Text('${controller.chosenData}'), // only this rebuilds
            ),
          ),
        ),
      ],
    );
  }
}

This way the listview.builder never rebuilds, only the Text widget directly inside the GetBuilder gets rebuilt when you selected a different ListTile.

Share:
241
SLendeR
Author by

SLendeR

Updated on January 01, 2023

Comments

  • SLendeR
    SLendeR over 1 year

    As the following animation displays, when I tap one of the list items that StreamBuilder() is querying, it shows the items data on the right darker container (it's always Instance of '_JsonQueryDocumentSnapshot'). But at the same time in each tap, the whole list is refreshing itself, which is not very cost-effective I believe.

    How can I avoid this unwanted refresh? Answers with GetX state management dependency are also welcome.

    enter image description here

    class Schedule extends StatefulWidget {
      @override
      _ScheduleState createState() => _ScheduleState();
    }
    
    class _ScheduleState extends State<Schedule> {
    
      final FirebaseFirestore _db = FirebaseFirestore.instance;
      final DateTime _yesterday = DateTime.now().subtract(Duration(days: 1));
    
      var _chosenData;
    
      @override
      Widget build(BuildContext context) {
        return Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Expanded(
              child: StreamBuilder<QuerySnapshot>(
                stream: _db.collection('Schedule').where('date', isGreaterThan: _yesterday).limit(10).orderBy('date').snapshots(),
                builder: (context, snapshot) {
                  if (snapshot.connectionState == ConnectionState.active) {
                    return ListView.builder(
                      itemCount: snapshot.data!.docs.length,
                      itemBuilder: (context, index) {
                        var data = snapshot.data!.docs[index];
                        return ListTile(
                          leading: Icon(Icons.person),
                          title: Text(data['project'], style: TextStyle(fontWeight: FontWeight.bold)),
                          subtitle: Text(data['parkour']),
                          onTap: () {
                            setState(() {_chosenData = data;});
                          },
                        );
                      },
                    );
                  } else {
                    return Center(child: CupertinoActivityIndicator());
                  }
                },
              ),
            ),
            VerticalDivider(),
            Expanded(
              child: Container(
                alignment: Alignment.center,
                color: Colors.black26,
                child: Text('$_chosenData'),
              ),
            ),
          ],
        );
      }
    }