Search code examples
flutterflutter-listviewflutter-dropdownbutton

How to make short ListView of DropDownButtons build faster?


I have a short ListView of a maximum of 10 items. Each list item will contain a DropDownButton which will hold around 1K DropDownMenuItems for selections.

In native Android, I was able to implement one that performed very smoothly, but with Flutter it takes a while to build the ListView which causes the UI to freeze.

In my case, I will need to rebuild the ListView upon every change in one of its items, so It will be a major issue.

Is there a way to make the ListView build faster, or at least be able to display a ProgressBar till it builds?

N.B: Using --profile configuration to simulate a release version improves the performance a lot, but still there is a sensed freeze.

Here's my sample code which you can directly copy/paste if you want to test it yourself.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool showList = false;
  final List<DropdownMenuItem<int>> selections = List.generate(
    1000,
    (index) => DropdownMenuItem<int>(
      value: index,
      child: Text("$index"),
    ),
  );

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: Container(
          width: double.infinity,
          child: Column(
            children: [
              ElevatedButton(
                child: Text("toggle list visibility"),
                onPressed: () {
                  setState(() {
                    showList = !showList;
                  });
                },
              ),
              Expanded(
                child: showList
                    ? ListView.builder(
                        cacheExtent: 2000,
                        itemCount: 10,
                        itemBuilder: (context, index) {
                          return Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Center(
                              child: Container(
                                height: 200,
                                color: Colors.green,
                                child: Column(
                                  children: [
                                    Text("List Item: $index"),
                                    DropdownButton<int>(
                                      onChanged: (i) {},
                                      value: 1,
                                      items: selections,
                                    ),
                                  ],
                                ),
                              ),
                            ),
                          );
                        })
                    : Text("List Not Built"),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Solution

  • Load dropdown when clicking the button.

    Add this widget on your main List View

       InkWell(
              onTap: () {
                showDialog(
                    context: context,
                    builder: (_) {
                      return VendorListAlert(selectVendor: selectVendorTap);
                    });
              },
              child: // create a widget, looks like your drop down
            ),
    

    Handle tap event

    void selectVendorTap(pass your model){
    // logic
    }
    

    Sample for custom Alert

    No need to create a mutable widget, the immutable widget is better.

    class VendorListAlert extends StatefulWidget {
      final Function selectVendor;
    
      const VendorListAlert({Key key, this.selectVendor}) : super(key: key);
    
      @override
      _VendorListAlertState createState() => _VendorListAlertState();
    }
    
    class _VendorListAlertState extends State<VendorListAlert> {
      List<UserModel> _searchVendor = [];
    
      @override
      void initState() {
        super.initState();
        _searchVendor = List.from(ypModel);
      }
    
      @override
      Widget build(BuildContext context) {
    
        return AlertDialog(
          content: Container(
            width: width,
            child: ListView.builder(
              shrinkWrap: true,
              itemCount: _searchVendor.length,
              itemBuilder: (BuildContext context, int index) {
                return Card(
                  child: InkWell(
                    onTap: () {
                      widget.selectVendor(_searchVendor[index]);
                      Navigator.pop(context);
                    },
                    child: 
                  ),
                );
              },
            ),
          ),
        );
      }
    }