Change card color based on alertdialog option

Issue

I have a list of cards and each card has a long press function which when clicked, pops up an alert dialog. I would like the card to change color based on the option chosen in the alert dialog. My alert dialog has 3 options:
Completed (Card should change to color green),
In Progress ( Color orange),
Cancel (Color grey).

At first, when the screen loads, it should show list of cards each painted the color based on the value saved in the database. Then, when the user long presses a card and chooses an option from the alert dialog, the card’s color should change based on the chosen option. Only that particular card’s color should change.

I have read somewhere that this might be achievable using valuechangenotifier. So here’s what I did so far:

First I created my changenotifier class like below:

import 'package:flutter/material.dart';

class ColorChanger with ChangeNotifier{

  Color _color = Colors.white;

  ColorChanger(this._color);

  getColor() => _color;

  setTheme (Color color) {
    _color = color;
    notifyListeners();
  }

}

Then I used it in my dart class. However, the color does not seem to change. What am I missing here?

class OrderItem extends StatefulWidget {
  final ord.OrderItem order;
  OrderItem(this.order);

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

class _OrderItemState extends State<OrderItem> {
  var _expanded = false;
  var mycolor = Colors.white;

  @override
  Widget build(BuildContext context) {
    ColorChanger _color = Provider.of<ColorChanger>(context);
    var listProducts = widget.order.products;
    return Card(
      color: widget.order.orderStatus=='completed'
             ?Colors.lightGreen:widget.order.orderStatus=='inprogress'?
            Colors.orangeAccent:
             widget.order.orderStatus=='cancelled'?Colors.grey:mycolor,
      margin: EdgeInsets.all(10),
      child: Column(
        children: <Widget>[
          ListTile(
            title: RichText(
              text: new TextSpan(
                style: new TextStyle(
                  fontSize: 14.0,
                  color: Colors.black,
                ),
                children: <TextSpan>[
                  new TextSpan(
                      text: 'Order Number : ',
                      style: new TextStyle(fontWeight: FontWeight.bold)),
                  new TextSpan(text: widget.order.uniqueOrderNumber),
                ],
              ),
            ),
            trailing: IconButton(
              icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
              onPressed: () {
                setState(() {
                  _expanded = !_expanded;
                });
              },
            ),
            onLongPress: toggleSelection,
          ),
        ],
      ),
    );
  }

  void toggleSelection() {
     ColorChanger _color = Provider.of<ColorChanger>(context,listen:false);
     Widget completeOrder = FlatButton(
                    child: Text('Completed'),
                    onPressed: () async {
                      try {
                        Navigator.of(context).pop(true);
                       // setState(() {
                             _color.setTheme(Colors.lightGreen); 
                       // });
                        await Provider.of<Orders>(context, listen: false)
                            .updateOrder(widget.order,'completed');
                      } catch (error) {
                         
                      }
    });

    Widget startOrder = FlatButton(
                    child: Text('In progress'),
                    onPressed: () async {
                      try {
                        Navigator.of(context).pop(true);
                       // setState(() {
                          _color.setTheme(Colors.orangeAccent); 
                        //});
                        //Update Db to mark order in progress
                        await Provider.of<Orders>(context, listen: false)
                            .updateOrder(widget.order,'inprogress');
                      } catch (error) {
                         
                      }
    });

    Widget cancelOrder = FlatButton(
                    child: Text('Cancel'),
                    onPressed: () async {
                      try {
                        Navigator.of(context).pop(false);
                      //  setState(() {
                            _color.setTheme(Colors.grey); 
                      //  });
                        //Update Db to mark order as cancelled
                        await Provider.of<Orders>(context, listen: false)
                            .updateOrder(widget.order,'cancelled');
                      } catch (error) {
                         
                      }
    });
          showDialog(
            context: context,
            builder: (ctx) => AlertDialog(
              title: Text('Take Action'),
              content: Text('What do you want to do with the order?'),
              actions: <Widget>[
                startOrder,
                completeOrder,
                cancelOrder
              ],
            ),
          );
      });
  }
}

SECOND TRY based on Loren’s answer.

import 'package:flutter/material.dart';

class ColorChanger with ChangeNotifier{

  Color color = Colors.white;

  setTheme (Color newColor) {
    color = newColor;
    notifyListeners();
  }

}


class OrderItem extends StatefulWidget {
      final ord.OrderItem order;
      OrderItem(this.order);
    
      @override
      _OrderItemState createState() => _OrderItemState();
    }
    
    class _OrderItemState extends State<OrderItem> {
      var _expanded = false;
      
      
      //Set the color based on what was last saved in the DB 
      void didChangeDependencies() async {
     var colorChanger = Provider.of<ColorChanger>(context, listen: false);
     if(widget.order.orderStatus=='completed')
        colorChanger.setTheme(Colors.lightGreen);
     else if(widget.order.orderStatus=='inprogress')
        colorChanger.setTheme(Colors.orangeAccent);
      else if(widget.order.orderStatus=='cancelled')
        colorChanger.setTheme(Colors.grey);
    super.didChangeDependencies();
  }
    
      @override
      Widget build(BuildContext context) {
        var listProducts = widget.order.products;
          return  Consumer<ColorChanger>(
       builder: (context, colorChanger, child) {
        return Card(
          color: widget.order.orderStatus=='completed'
                 ?Colors.lightGreen:widget.order.orderStatus=='inprogress'?
                Colors.orangeAccent:
                 widget.order.orderStatus=='cancelled'?Colors.grey:mycolor,
          margin: EdgeInsets.all(10),
          child: Column(
            children: <Widget>[
              ListTile(
                title: RichText(
                  text: new TextSpan(
                    style: new TextStyle(
                      fontSize: 14.0,
                      color: Colors.black,
                    ),
                    children: <TextSpan>[
                      new TextSpan(
                          text: 'Order Number : ',
                          style: new TextStyle(fontWeight: FontWeight.bold)),
                      new TextSpan(text: widget.order.uniqueOrderNumber),
                    ],
                  ),
                ),
                trailing: IconButton(
                  icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
                  onPressed: () {
                    setState(() {
                      _expanded = !_expanded;
                    });
                  },
                ),
                onLongPress: toggleSelection,
              ),
            ],
          ),
        )};
      }
    
      void toggleSelection() {
         ColorChanger _color = Provider.of<ColorChanger>(context,listen:false);
         Widget completeOrder = FlatButton(
                        child: Text('Completed'),
                        onPressed: () async {
                          try {
                            Navigator.of(context).pop(true);
                           // setState(() {
                                 _color.setTheme(Colors.lightGreen); 
                           // });
                            await Provider.of<Orders>(context, listen: false)
                                .updateOrder(widget.order,'completed');
                          } catch (error) {
                             
                          }
        });
    
        Widget startOrder = FlatButton(
                        child: Text('In progress'),
                        onPressed: () async {
                          try {
                            Navigator.of(context).pop(true);
                           // setState(() {
                              _color.setTheme(Colors.orangeAccent); 
                            //});
                            //Update Db to mark order in progress
                            await Provider.of<Orders>(context, listen: false)
                                .updateOrder(widget.order,'inprogress');
                          } catch (error) {
                             
                          }
        });
    
        Widget cancelOrder = FlatButton(
                        child: Text('Cancel'),
                        onPressed: () async {
                          try {
                            Navigator.of(context).pop(false);
                          //  setState(() {
                                _color.setTheme(Colors.grey); 
                          //  });
                            //Update Db to mark order as cancelled
                            await Provider.of<Orders>(context, listen: false)
                                .updateOrder(widget.order,'cancelled');
                          } catch (error) {
                             
                          }
        });
              showDialog(
                context: context,
                builder: (ctx) => AlertDialog(
                  title: Text('Take Action'),
                  content: Text('What do you want to do with the order?'),
                  actions: <Widget>[
                    startOrder,
                    completeOrder,
                    cancelOrder
                  ],
                ),
              );
          });
      }
    }

When I do it this way, it changes the color of all the cards instead of just that one card. What am I doing wrong here?

Sharing order.dart

class OrderItem {
  final String id;
  final double amount;
  final int deliveryFee;
  final List<CartItem> products;
  final DateTime dateTime;
  final String deliveryMethod;
  final String uniqueOrderNumber;
  final String orderStatus; 
  final String userId;
  final String customMessage;
  final String customerName; 
  final String phoneNumber; 

  OrderItem( 
      {@required this.id,
      @required this.amount,
      @required this.products,
      @required this.dateTime,
      @required this.deliveryMethod,
      @required this.uniqueOrderNumber,
      @required this.isOrderComplete,
      this.orderStatus,
      @required this.customMessage,
      @required this.deliveryFee,
      this.customerName,
      this.phoneNumber,
      @required this.userId});
}

class Orders with ChangeNotifier {
  final String authToken;
  final String userId;

  Orders(this.authToken, this.userId);

  List<OrderItem> _orders = [];
  List<OrderItem> get orders {
    return [..._orders];
  }
  Future<void> updateOrder(OrderItem order,String orderStatus) async {
final id = order.id;
final customerId = order.userId;
final url =
    'https://cv.firebaseio.com/orders/$customerId/$id.json?auth=$authToken';
     try {
         await http.patch(url,
           body: json.encode({
             'orderStatus':orderStatus
          }));
     } catch (error) {
     print(error);
   }
notifyListeners();

}

Solution

UPDATED ANSWER:

So when trying to do this with Provider I kept getting errors that would have required me to keep bugging you for more and more code to try and replicate everything you have going on, and I didn’t want to get into that.

So this solution may or may not be acceptable to you because it uses GetX State Management, but it works. In addition it doesn’t require wrapping your whole app in provider widgets so dealing with scope etc…is a non issue.

Let’s add a statusColor property to your OrderItem model. This is what will get changed.

 Color statusColor = Colors.white; // or whatever you you want the default color to be

Your updated Orders class that uses GetX instead of ChangeNotifier (again, not because Provider can’t do this, but because I was dealing with too many errors and frankly GetX is easier in my opinion anyway)

class Orders extends GetxController {
  final String authToken;
  final String userId;

  Orders(this.authToken, this.userId);

  List<OrderItem> orders = []; // back to what I said earlier about no point in getters and setters here

// temp function just to test this on my end
  void addOrder(OrderItem order) {
    orders.add(order);
    update();
  }

// this loops through the list to find the matching order number,
// then updates the color for just that order
  void updateOrderStatusColor({OrderItem updatedOrder, String status}) {
    for (final order in orders) {
      if (order.uniqueOrderNumber == updatedOrder.uniqueOrderNumber) {
        switch (status) {
          case 'completed':
            {
              order.statusColor = Colors.greenAccent;
            }
            break;
          case 'inprogress':
            {
              order.statusColor = Colors.orangeAccent;
            }
            break;
          case 'cancelled':
            {
              order.statusColor = Colors.grey;
            }
            break;
        }
      }
    }
    update(); // equivelent of notifyListeners();
  }
  // ...the rest of your class
}

A few small changes to your card. didChangeDependencies can go away entirely.

// it seems like you had 2 classes with the same name, which is not recommended
class OrderItemCard extends StatefulWidget {
  final OrderItem order;

  OrderItemCard(this.order);

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

class _OrderItemCardState extends State<OrderItemCard> {
  var _expanded = false;
  final controller = Get.find<Orders>(); // equivilent of Provider.of... finds the same instance without needing context

  void toggleSelection() {
    Widget completeOrder = TextButton(
        child: Text('Completed'),
        onPressed: () async {
          try {
            Navigator.of(context).pop(true);

            controller.updateOrderStatusColor(
                updatedOrder: widget.order, status: 'completed'); // calling new function here
          } catch (error) {}
        });

    Widget startOrder = FlatButton(
        child: Text('In progress'),
        onPressed: () async {
          try {
            Navigator.of(context).pop(true);
            controller.updateOrderStatusColor(
                updatedOrder: widget.order, status: 'inprogress');
          } catch (error) {}
        });

    Widget cancelOrder = FlatButton(
        child: Text('Cancel'),
        onPressed: () async {
          controller.updateOrderStatusColor(
              updatedOrder: widget.order, status: 'cancelled');
          try {
            Navigator.of(context).pop(false);
          } catch (error) {}
        });

    showDialog(
      context: context,
      builder: (ctx) => AlertDialog(
        title: Text('Take Action'),
        content: Text('What do you want to do with the order?'),
        actions: <Widget>[startOrder, completeOrder, cancelOrder],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.all(10),
      color: widget.order.statusColor, // new color property added to your model
      child: Column(
        children: <Widget>[
          ListTile(
            title: RichText(
              text: new TextSpan(
                style: new TextStyle(
                  fontSize: 14.0,
                  color: Colors.black,
                ),
                children: <TextSpan>[
                  new TextSpan(
                      text: 'Order Number : ${widget.order.uniqueOrderNumber} ',
                      style: new TextStyle(fontWeight: FontWeight.bold)),
                ],
              ),
            ),
            trailing: IconButton(
              icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
              onPressed: () {
                setState(() {
                  _expanded = !_expanded;
                });
              },
            ),
            onLongPress: toggleSelection,
          ),
        ],
      ),
    );
  }
}

Not sure what you have going on in your UI but here’s a quick demo of how it would work in GetX. It’s a simple ListView.builder populated from the orders list from the GetX Class. The GetBuilder<Orders> widget rebuilds when update() is called. Also a simple button that adds a dummy item for demo purposes. I don’t know how you’re generating your unique order # but I’m just using the list index for this. Both inside a column within a scaffold on a demo page.

// Equivilent of Consumer but doesn't need context nor any provider widget above it
 GetBuilder<Orders>(
              builder: (controller) => Expanded(
                child: ListView.builder(
                    itemCount: controller.orders.length,
                    itemBuilder: (context, index) =>
                        OrderItemCard(controller.orders[index])),
              ),
            ),
            TextButton(
              onPressed: () {
                final controller = Get.find<Orders>();
                final orderItem = OrderItem(
                  orderStatus: ' ',
                  uniqueOrderNumber: controller.orders.length
                      .toString(), // just a hack to generate a unique order # for demo
                );
                controller.addOrder(orderItem);
              },
              child: Text('Add Item'),
            )

Last thing is just initializing the GetX Controller. It can be done anywhere as long as its before you try and use it.

void main() {
  // initialing the GetX GetxController
  // not sure how you're generating the required auth and user id
  // but I'm just passing in empty strings for now
  Get.put(Orders('', ''));
  runApp(MyApp());
}

enter image description here

So if you’re open to GetX here, you can leave Provider for any other ChangeNotifier classes you may have in place if you want. For this you would just need to replace any Consumer<Orders> with GetBuilder<Order> and then get rid of the Provider<Orders>(create:... widget entirely.

OLD ANSWER:

You’re missing a couple things in order to be using Provider properly and get the color changing the way you want.

For starters, your Card needs to be wrapped in a Consumer widget that gets notified of changes and rebuilds its children. Inside the Consumer, you need to be using the color property of the ChangeNotifier class. It doesn’t need to know or care about the orderStatus because you’re already explicitly telling it to change color when you call the setTheme method.

Consumer<ColorChanger>(  // this is what rebuilds and changes the color
        builder: (context, colorChanger, child) {
      return Card(
        color: colorChanger.color, // colorChanger here is equivalent of declaring final colorChanger = Provider.of<ColorChanger>(context...
        child: Column(
          children: <Widget>[
            ListTile(
              title: RichText(
                text: new TextSpan(
                  style: new TextStyle(
                    fontSize: 14.0,
                    color: Colors.black,
                  ),
                  children: <TextSpan>[
                    new TextSpan(
                        text: 'Order Number : ',
                        style: new TextStyle(fontWeight: FontWeight.bold)),
                    new TextSpan(text: widget.order.uniqueOrderNumber),
                  ],
                ),
              ),
              trailing: IconButton(
                icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
                onPressed: () {
                  setState(() {
                    _expanded = !_expanded;
                  });
                },
              ),
              onLongPress: toggleSelection,
            ),
          ],
        ),
      );
    });

Next, see this link as to why you’re not gaining anything with using the private _color and public getColor in your ChangeNotifier class.

So lets simplify that a bit.

class ColorChanger with ChangeNotifier {
  Color color = Colors.white;

  ColorChanger(this.color);

  setTheme(Color newColor) {
    color = newColor;
    notifyListeners();
  }
}

Now, whenever you call the setTheme function from your dialog, that card will change to whatever color you pass into it because the Consumer widget is notified, and will rebuild with the updated color value of the ChangeNotifier class.

Answered By – Loren.A

Answer Checked By – Terry (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *