Search code examples
flutterdartflutter-provider

How to deselected other buttons when a button is selected?


I'm building a line graph and the spots can be edited by selecting them, however, I want the spots to work like radio buttons, only one can be selected at a time. I can't get it to work right now:

enter image description here

the graph is rendered by fetching data from SQflite and then I use Provider to build it

main.dart :

FutureProvider<List<Map<String, dynamic>>?>(
        create: (context) {
          return RecordsDatabase.instance.getRecords();
        },
        initialData: null,
        catchError: (_, __) => <Map<String, dynamic>>[
          {'error': 'Something went wrong'}
        ],
        child: (),
      )

Home

Consumer<List<Map<String, dynamic>>?>(
        builder: (context, List<Map<String, dynamic>>? records, child) {
      if (records == null) {
        return const CircularProgressIndicator();
      } else if (records.isNotEmpty && records.first.containsKey('error')) {
        return Text(records.first['error'] as String);
      } else {
        GraphState graph = GraphState(records: records, context: context);

        return MultiProvider(
          providers: [
            ChangeNotifierProvider<GraphState>(create: (_) => graph),
            ChangeNotifierProvider<ButtonMode>(
                create: (context) => ButtonMode())
          ],
          child: Scaffold(
            backgroundColor: Colors.black,
            body: Stack(children: [
              Center(
                  child: (records.isEmpty == true)
                      ? CircularProgressIndicator()
                      : graph.records.isEmpty
                          ? Text(
                              'No Records',
                              style:
                                  TextStyle(color: Colors.white, fontSize: 24),
                            )
                          : MyGraph()),

the GraphState model renders the spots , lines and guidelines in a big list to be rendered in a Stack in MyGraph()

GraphState({required this.records, required this.context}) {
        titles = renderSideTitleWeights();
    spots = renderSpots();
    spotLines = renderLines();
    horizontalGuidelines = renderHorizontalLines();
    graphList = new List.from(horizontalGuidelines)
      ..addAll(spotLines)
      ..addAll(spots);
  }


//render the spots of the graph
  //render the spots of the graph
  List<GraphSpot> renderSpots() {
   
    List<GraphSpot> spots = spotsXPos.mapIndexed<GraphSpot>((record, index) {
      return GraphSpot(
        x: record['xPos'],
        y: record['yPos'],
        date: record['record_date'],
        weight: record['weight'],
        id: index,
        selected: false,
             );
    }).toList();

    return spots;
  }

and the GraphSpot widget render the spots on the graph :

class GraphSpot extends StatefulWidget {
  final double x;
  final double y;
  final String date;
  final double weight;
  final int id;
  final bool selected;

  GraphSpot({
    required this.x,
    required this.y,
    required this.date,
    required this.weight,
    required this.id,
    required this.selected,
  });

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

class _GraphSpotState extends State<GraphSpot> {
  @override
  Widget build(BuildContext context) {
    return Consumer<ButtonMode>( //ButtonMode change the add button to edit and delete
      builder: (context, buttonMode, child) {
        return Positioned(
          left: widget.x - 5, //minus half of the width of the spot to center
          bottom: widget.y -
              10, //minus half of the height of the spot and padding to center
          child: GestureDetector(
            onTap: () {
              !widget.selected
                  ? buttonMode.setEditing()
                  : buttonMode.setAdd();
        
            },
            child: Column(
              children: [
                Container(
                  width: 40,
                  decoration: new BoxDecoration(
                    color: Colors.lightGreen,
                    shape: BoxShape.rectangle,
                  ),
                  child: Text(widget.weight.toStringAsFixed(1),
                      textAlign: TextAlign.center,
                      style: TextStyle(
                          color: Colors.blue,
                          fontSize: 14,
                          fontWeight: FontWeight.bold)),
                ),
                DecoratedBox(
                  decoration: selected
                      ? BoxDecoration(
                          shape: BoxShape.circle,
                          border: Border.all(width: 3, color: Colors.red))
                      : BoxDecoration(shape: BoxShape.circle),
                  child: Padding(
                    padding: const EdgeInsets.all(5.0),
                    child: Container(
                      width: 10,
                      height: 10,
                      decoration: BoxDecoration(
                        color: Colors.blue,
                        shape: BoxShape.circle,
                      ),
                    ),
                  ),
                )
              ],
            ),
          ),
        );
      },
    );
  }
}

I have no idea where in my code and how I can implement this .


Solution

  • I'd need more code to answer accurately.

    However, one solution is to lift up the state.

    In Flutter, it makes sense to keep the state above the widgets that use it.

    So the StatefulWidget that render the GraphSpot can have a _selectedIndex property that holds the selected index. Then you can pass an onGraphSpotTap function to each GraphSpot Widget.

    int? _selectedIndex;
     
    List<GraphSpot> spots = spotsXPos.mapIndexed<GraphSpot>((record, index) {
      return GraphSpot(
        x: record['xPos'],
        y: record['yPos'],
        date: record['record_date'],
        weight: record['weight'],
        id: index,
        selected: index == _selectedIndex ? true : false,
        onGraphSpotTap: () {
            setState(() {
              _selectedIndex = index;
            });
          }
    
             );
    }).toList();
    

    Then in your GraphSpot add the new onGraphSpotTap property.

    class GraphSpot extends StatelessWidget {
      .....
      final VoidCallback? onGraphSpotTap;
    
      ....
      onTap: onGraphSpotTap
    

    Don't forget to modify the GraphSpot Widget rendering based on its selected property