6

I'm using Flutter-Dart-SQLite to develop a quiz application. Here I have used RadioListTile for radio button functionality. I am passing the text value from an array to this StatefulWidget. This is the code I'm using,

    import 'package:flutter/material.dart';
    import 'package:get/get_state_manager/get_state_manager.dart';
    import 'package:loginform_validation_demo/QuizApp/controllers/question_controller.dart';
    import 'package:loginform_validation_demo/resource-file/constants.dart';
    import 'package:shared_preferences/shared_preferences.dart';
    import 'package:flutter/src/material/radio_list_tile.dart';

    class OptionRadio extends StatefulWidget {
      final String text;
      final int index;
      final VoidCallback press;

      const OptionRadio({
        Key key,
        this.text,
        this.index,
        this.press,
      }) : super();

      @override
      OptionRadioPage createState() =>
          OptionRadioPage(this.text, this.index, this.press);
    }

    class OptionRadioPage extends State<OptionRadio> {
      final String text;
      int index;
      final VoidCallback press;
      QuestionController controllerCopy =QuestionController();

      int id = 1;
      int selectedButton = null;
      bool _isButtonDisabled;

      OptionRadioPage(this.text, this.index, this.press);

      @override
      void initState() {
        _isButtonDisabled = false;
      }

      int _selected = null;

      @override
      Widget build(BuildContext context) {
        return GetBuilder<QuestionController>(
            init: QuestionController(),
            builder: (qnController) {
              Color getTheRightColor() {
                if (qnController.isAnswered) {
                  if (index == qnController.selectedAns &&
                      qnController.selectedAns == qnController.correctAns) {

                    return kBlackColor;
                  } else if (index == qnController.selectedAns &&
                      qnController.selectedAns != qnController.correctAns) {
                    return kBlackColor;
                  }

                }
                return kBlackColor;
              }

              return InkWell(
                onTap: press,
                child: Container(
                  child: Row(
                    children: <Widget>[
                      Expanded(
                        child: Container(
                            // height: 60.0,
                            child: Theme(
                              data: Theme.of(context).copyWith(
                                  unselectedWidgetColor: Colors.grey,
                                  disabledColor: Colors.blue),
                              child: Column(children:
                              [
                                RadioListTile(
                                  title: Text(
                                    "${index + 1}. $text",
                                    style: TextStyle(
                                        color: getTheRightColor(), fontSize: 16),
                                    softWrap: true,
                                  ),
                                  /*Here the selectedButton which is null initially takes place of value after onChanged. Now, I need to clear the selected button when other button is clicked */
                                  groupValue: selectedButton,
                                  value: index,
                                  activeColor: Colors.green,
                                  onChanged:
                                      (val) async {

                                    debugPrint(
                                        'Radio button is clicked onChanged $val');
                                    setState(() {
                                      debugPrint('Radio button setState $val');
                                      selectedButton = val;
                                      debugPrint('Radio button is clicked onChanged $index');
                                    
                                    });
                                    SharedPreferences prefs = await SharedPreferences.getInstance();
                                    prefs.setInt('intValue', val);
                                  },
                                  toggleable: true,
                                ),
                              ]
                              ),
                            )),
                      ),
                    ],
                  ),
                ),
              );
            });
      }

      @override
      State<StatefulWidget> createState() {
        throw UnimplementedError();
      }
    }

Here the selectedButton which is null initially takes place of value of group value after onChanged. Now, I need to clear the selected button when the other button is clicked.

enter image description here

I'm guessing I have something done wrong in group value part.

The part where I use OptionRadio,

class QuestionCardWidgetRadio extends StatelessWidget {
  OptionRadio optionRadio = OptionRadio();
  QuestionCardWidgetRadio({
    Key key,
    this.note,
    this.index,
    this.questionLength,
  }) : super(key: key);

  final BlazorQuiz note;
  final int index;
  final int questionLength;
  bool _isAnswered = false;
  bool get isAnswered => this._isAnswered;
  int selectedButton;

  @override
  Widget build(BuildContext context) {
    QuestionController _questionController =
        Get.put(QuestionController());
    debugPrint("Answer Print : ${note.Answer.split(",")}");
    return Container(
        margin: EdgeInsets.symmetric(horizontal: kDefaultPadding, vertical: 35),
        decoration: BoxDecoration(
          color: Colors.white,
        borderRadius: BorderRadius.circular(25),
        ),
        padding: EdgeInsets.all(kDefaultPadding),
    child: SingleChildScrollView(
    scrollDirection: Axis.vertical,
        child: Column(
          children: [
            ...List.generate(
              note.Answer.split(",").length,
              (index) => OptionRadio(
                index: index,
                text: note.Answer.split(",")[index],
                press: (val) => setState(() {
                  selectedButton = int.parse(val.toString());
                  print("$selectedButton");
                }),
                selectedButton: selectedButton,
                // press:
                //       () =>{
                //   _isAnswered = true,
                //   _questionController.checkAns(note, index),
                //     selectedButton,
                // }
              ),

            SingleChildScrollView(
              scrollDirection: Axis.vertical,
              child: TextButton(
                onPressed: () async {
                  debugPrint("Clicked options : ${note.Answer.isNotEmpty} isAnswered: $isAnswered");
                  debugPrint('Check Index : ${index} check quiz model Lenght : ${QuizModel.question.length} check note Id : ${note.Id}');
                  if(index+1 == questionLength){
                    // score screen
                    debugPrint('Navigate to score screen');
                    Text(
                      "Submit",
                      style: Theme.of(context)
                          .textTheme
                          .button
                          .copyWith(color: Colors.white),
                    );
                    _questionController.scoreNavigation(index, context);
                  }
                  else{
                    if (note.Answer.isNotEmpty == !isAnswered) {
                      debugPrint('Clicked RadioBtn ${optionRadio.index}');
                      SharedPreferences prefs = await SharedPreferences.getInstance();
                      //Return int
                      int intValue = prefs.getInt('intValue');
                      debugPrint('Int value from sharedPrefs: $intValue');
                      _questionController.nextQuestion(note,intValue,"","",[]);
                      prefs.remove("intValue");
                    }else{
                      debugPrint('Click proper option for Radio');
                    }
                  }
                },

                child: Container(
                  width: double.infinity,
                  alignment: Alignment.center,
                  padding: EdgeInsets.all(kDefaultPadding * 0.75),
                  margin: EdgeInsets.all(37),
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.all(Radius.circular(10)),
                      color: Color(ColorData.button)
                  ),
                  child: Text(
                    "Next Question",
                    style: Theme.of(context)
                        .textTheme
                        .button
                        .copyWith(color: Colors.white),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  setState(Null Function() param0) {}
}

I get values from an array where the options are separated by comma. "Answer":"Male,Female"

Terin Tittu
  • 143
  • 1
  • 1
  • 15
  • You need to save 'selectedButton' value at outside of 'OptionRadio' Widget and share that value with 'OptionRadio' widget for 'Male' and 'OptionRadio' widget for 'Female'. – KuKu Dec 29 '21 at 11:38
  • @KuKu I tested the above method and was able to receive the correct value that was clicked in the log, but the icon did not change colors. For both, Icon stays unselected. I tried to make a widget out of the specified value in setState(). selectedButton = val; because it is a final field, it is not possible to assign a value. What should I do next? – Terin Tittu Dec 30 '21 at 05:51
  • I made a package on pub.dev for radio groups. It makes them very easy to use. https://pub.dev/packages/radio_group_v2 – JebediahTheAmishProgrammer Feb 22 '23 at 20:43

3 Answers3

2

Here is sample code by using your 'OptionRadioPage' Widget.
(but some code deleted because of leak of code)
As I commented, 'groupValue' should shared with two 'OptionRadioPage' widgets.
So I store that value 'OptionRadioPage' parent widget and
also pass the that value to 'OptionRadioPage' widgets.
enter image description here

import 'package:flutter/material.dart';
import 'package:flutter/src/material/radio_list_tile.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Main(),
    );
  }
}

class Main extends StatefulWidget {
  Main({Key key}) : super(key: key);

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

class _MainState extends State<Main> {
  int selectedButton;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            OptionRadio(
                text: 'Male',
                index: 0,
                selectedButton: selectedButton,
                press: (val) {
                  selectedButton = val;
                  setState(() {});
                }),
            OptionRadio(
                text: 'Female',
                index: 1,
                selectedButton: selectedButton,
                press: (val) {
                  selectedButton = val;
                  setState(() {});
                }),
          ],
        ),
      ),
    );
  }
}

class OptionRadio extends StatefulWidget {
  final String text;
  final int index;
  final int selectedButton;
  final Function press;

  const OptionRadio({
    Key key,
    this.text,
    this.index,
    this.selectedButton,
    this.press,
  }) : super();

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

class OptionRadioPage extends State<OptionRadio> {
  // QuestionController controllerCopy =QuestionController();

  int id = 1;
  bool _isButtonDisabled;

  OptionRadioPage();

  @override
  void initState() {
    _isButtonDisabled = false;
  }

  int _selected = null;

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () {
        widget.press(widget.index);
      },
      child: Container(
        child: Row(
          children: <Widget>[
            Expanded(
              child: Container(
                  // height: 60.0,
                  child: Theme(
                data: Theme.of(context).copyWith(
                    unselectedWidgetColor: Colors.grey,
                    disabledColor: Colors.blue),
                child: Column(children: [
                  RadioListTile(
                    title: Text(
                      "${widget.index + 1}. ${widget.text}",
                      style: TextStyle(color: Colors.black, fontSize: 16),
                      softWrap: true,
                    ),
                    /*Here the selectedButton which is null initially takes place of value after onChanged. Now, I need to clear the selected button when other button is clicked */
                    groupValue: widget.selectedButton,
                    value: widget.index,
                    activeColor: Colors.green,
                    onChanged: (val) async {
                      debugPrint('Radio button is clicked onChanged $val');
                      // setState(() {
                      //   debugPrint('Radio button setState $val');
                      //   selectedButton = val;
                      //   debugPrint('Radio button is clicked onChanged $widget.index');
                      // });
                      // SharedPreferences prefs = await SharedPreferences.getInstance();
                      // prefs.setInt('intValue', val);
                      widget.press(widget.index);
                    },
                    toggleable: true,
                  ),
                ]),
              )),
            ),
          ],
        ),
      ),
    );
  }
}
KuKu
  • 6,654
  • 1
  • 13
  • 25
1

This is happening because you are defining individual groupValue for each Radio button. Rather than defining selectedButton field for each button define it once in that file where you are calling this widget and pass the value through it's constructor like below code :

class OptionRadio extends StatefulWidget {
  final String text;
  final int index;
  final ValueChanged<Object> press;
  final int selectButton;

  const OptionRadio({
    Key key,
    this.text,
    this.index,
    this.press, 
    this.selectButton,
  }) : super();

  @override
  OptionRadioPage createState() =>
      OptionRadioPage(this.text, this.index, this.press);
}

and use this value and press function to your Radio button like this :

                RadioListTile(
                              title: Text(
                                "${index + 1}. $text",
                                style: TextStyle(
                                    color: Colors.green, fontSize: 16),
                                softWrap: true,
                              ),
                              groupValue: widget.selectButton,
                              value: index,
                              activeColor: Colors.green,
                              onChanged: widget.press,
                            )

also pass ValueChanged(Object) in place of VoidCallBack :

final ValueChanged<Object> press;

and pass a proper function there like :

OptionRadio(
                        text: "abc",
                        index: index,
                        press: (val) => setState(() {
                          selectButton = int.parse(val.toString());
                          print("$selectButton");
                        }),
                        selectButton: selectButton,
                      )
Diwyansh
  • 2,961
  • 1
  • 7
  • 11
  • Thanks for the answer. I tried the above code and in log I'm able to get proper value that is clicked but the icon doesn't change to any color. Icon remains unselected for both. I tried to assign selected value in setState() as widget.selectButton = val; but since its a final field couldn't assign value. Next what should I do? – Terin Tittu Dec 30 '21 at 05:24
  • Please check the updated answer and also change selectButton type to int. Let me know if there's any issue. – Diwyansh Dec 30 '21 at 10:53
  • When I give static text value no issue rises, but here I'm getting value from an array and when I implement this code in my OptionRadio class error occurs in Scaffold part. I have added more details about the place where I use this OptionRadio. Please help me solve this issue, I'm stuck with this for days – Terin Tittu Dec 30 '21 at 13:16
  • my above answer working fine when you pass static values right? – Diwyansh Dec 30 '21 at 13:18
  • make sure that you are passing same value of same data type. – Diwyansh Dec 30 '21 at 13:31
0

1) Firstly define Boolean in the class.

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  bool isAdmin = true; //<-- Here 
}

2) Then use the Ternary operator.

Column(
  children: [
    IconButton(
      onPressed: () {
        setState(() {
          isAdmin = true;
        });
      },
      icon: Icon(
        isAdmin ? Icons.radio_button_on : Icons.radio_button_off, //<-- Here
      ),
    ),
    const Text("Admin") 
  ],
),
Column(
  children: [
    IconButton(
      onPressed: () {
        setState(() {
          isAdmin = false;
        });
      },
      icon: Icon(
        isAdmin ? Icons.radio_button_off : Icons.radio_button_on, //<-- Here
      ),
    ),
    const Text("Customer")
  ],
),
Sunderam Dubey
  • 1
  • 11
  • 20
  • 40