Jolzal
Jolzal

Reputation: 597

How to properly sort a List of object and clone it

so i was creating this app which is an item catalogue for an online shop using StaggeredGridView, here i add a function to sort the item from a list, so that i can sort it by price or make it back to default sorting, but i encounter a problem where the list cant go back to default sorting when i select it, here is the code :

class ItemCatalogState extends State<ItemCatalog>{

  sortedBy sorted = sortedBy.init;
  bool isInit = true;
  var selectedList = [];

  void sortItem(List<Item> initialList){
    switch (sorted) {
      case sortedBy.lowestPrice:
        setState(() {
          selectedList.sort((a,b){return a.price.compareTo(b.price);}); 
        });
        break;
      case sortedBy.highestPrice:
        setState(() {
          selectedList.sort((a,b){return b.price.compareTo(a.price);});
        });
        break;
      case sortedBy.init:
      //NOT WORKING
        setState(() {
          selectedList = initialList; 
        });
        break;
       default:
    }
  }

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    List<Item> itemList = Provider.of<List<Item>>(context);
    
    if(isInit&&itemList!=null){
      selectedList = itemList.map((e) => Item(image: e.image,name: e.name,isReady: e.isReady,price: e.price,seller: e.seller)).toList();
      isInit = false;
    }

    sortItem(itemList);

    return Container(
          child: DropdownButtonHideUnderline(
            child: DropdownButton(
              value: sorted,
              items: [
                DropdownMenuItem(
                  child: Text('Default',),
                  value: sortedBy.init,),
                DropdownMenuItem(
                  child: Text('Lowest',),
                  value: sortedBy.lowestPrice,),
                DropdownMenuItem(
                  child: Text('Highest'),
                  value: sortedBy.highestPrice,),
              ], 
              onChanged: (value){
                setState(() {
                  sorted = value;
                });
              }),
          ),
        ),
        Container(
                  decoration: BoxDecoration(
                    color: kGrayMainColor,
                  ),
                  child: StaggeredGridView.countBuilder(
                    itemCount: selectedList!=null?selectedList.length:itemList.length,
                    crossAxisCount: 2,
                    itemBuilder: (context, index) {
                      Item currentItem = selectedList!=null?selectedList[index]:itemList[index];
                      return ItemTile(item: currentItem, size: size,);
                    },
                  ),
                ),
              ),
      ]),]
    );
  }

It was supposed to call the original List when i click the default drop down, but nothing changed, could i possibly wrong at copying the list to selectedList? Thankyou and any advice regarding my other bad code practice is appreciated since im still learning.

Upvotes: 0

Views: 618

Answers (2)

Abion47
Abion47

Reputation: 24736

Inside sortItems you set selectedList to be equal to initialList. From that point on, both variables are now pointing at the same collection of objects, which means anything you do to one, you will also do to the other. And since you are getting the collection through provider, these changes will also affect the original list that was provided.

Instead of a direct assignment, copy initialList again so that the two lists are never pointing to the same collection.

Incidentally, there's a much easier way to create copies of lists:

selectedList = List.from(initialList);

On another note, I'm assuming that the default value of sorted is sortedBy.init. That makes the initial copy within your build method redundant as the sortItems method immediately overwrites the value of selectedList. Instead, just depend on sortItems to generate your list without having to worry about initialization:

void sortItem(List<Item> initialList){
  switch (sorted) {
    case sortedBy.lowestPrice:
      selectedList = List.from(initialList)
        ..sort((a,b) => a.price.compareTo(b.price));
      break;
    case sortedBy.highestPrice:
      selectedList = List.from(initialList)
        ..sort((a,b) => b.price.compareTo(a.price));
      break;
    case sortedBy.init:
      selectedList = List.from(initialList); 
      break;
  }
  setState(() {}); // Just call this once to reduce code duplication and verbosity
}

@override
Widget build(BuildContext context) {
  ...

  List<Item> itemList = Provider.of<List<Item>>(context);
  sortItems(itemList);

  ...
}

Upvotes: 1

kazume
kazume

Reputation: 1373

This is how I would solve it (I changed your List<Item> to a List<String> for simplicity, but you get the gist):

Load the original list into the _sortedList variable (initially in didChangeDependencies) via the Provider and repeat this whenever you are in need of the original list again.

(My Provider returns ['aa', 'dd', 'cc'], so I could get in that map() you do on the list as well :) )

enum SortOrder { ascending, descending, original }

class _TestState extends State<Test> {
  var _sortedList = List<String>();

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _loadOriginalList();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              RaisedButton(
                child: Text('Ascending'),
                onPressed: () => setState(() => _sortList(SortOrder.ascending)),
              ),
              RaisedButton(
                child: Text('Descending'),
                onPressed: () => setState(() => _sortList(SortOrder.descending)),
              ),
              RaisedButton(
                child: Text('Original'),
                onPressed: () => setState(() => _sortList(SortOrder.original)),
              ),
            ],
          ),
          ListView.builder(
            shrinkWrap: true,
            itemCount: _sortedList.length,
            itemBuilder: (context, index) => Center(
              child: Text(_sortedList[index]),
            ),
          ),
        ],
      ),
    );
  }

  void _sortList(SortOrder sortOrder) {
    switch (sortOrder) {
      case SortOrder.ascending:
        _sortedList.sort();
        break;
      case SortOrder.descending:
        _sortedList.sort();
        _sortedList = _sortedList.reversed.toList();
        break;
      case SortOrder.original:
        _loadOriginalList();
        break;
    }
  }

  void _loadOriginalList() {
    final originalList = Provider.of<List<String>>(context, listen: false);
    _sortedList.clear();
    _sortedList.addAll(originalList.map((e) => e.substring(0, 1)));
  }
}

Upvotes: 0

Related Questions