Flutter Dialog에서 라디오 버튼 사용하기(Stateful Dialog)

viiviii·2021년 11월 25일
0
post-thumbnail

flutter version 2.5.2 기준으로 작성됨

  • 이 글에선 dialog를 호출한 위젯의 상태도 바꾸고 싶은 경우와 아닌 경우를 나눠서 설명

1. StatelessWidget에서 Dialog를 호출한 경우

  • dialog 내부의 상태만 변경하고 싶을 때

The widget returned by the builder does not share a context with the location that showDialog is originally called from. Use a StatefulBuilder or a custom StatefulWidget if the dialog needs to update dynamically.

showDialog 문서에서는 호출한 곳의 context를 공유하지 않으니, Dialog를 동적으로 변경하고 싶으면 StatefulBuilderStatefulWidget으로 사용하라고 적혀있음

방법 A) StatefulBuilder 사용

  • Dialog에서 상태 변경을 원하는 위젯을 StatefulBuilder로 감싸서 사용
Future<dynamic> showStatefulBuilderDialog(BuildContext context) async {
  await showDialog<void>(
    context: context,
    builder: (_) {
      int? selectedRadio = 0;
      return AlertDialog(
        content: StatefulBuilder(
          builder: (__, StateSetter setState) {
            return Column(
              mainAxisSize: MainAxisSize.min,
              children: List<Widget>.generate(3, (int index) {
                return Radio<int>(
                  value: index,
                  groupValue: selectedRadio,
                  onChanged: (int? value) {
                    setState(() => selectedRadio = value);
                  },
                );
              }),
            );
          },
        ),
      );
    },
  );
}

방법 B) StatefulWidget 사용

  • DialogStatefulWidget으로 커스텀해서 사용함
// dialog를 사용하는 곳
Future<dynamic> showStatefulWidgetDialog(BuildContext context) async {
  await showDialog<void>(
    context: context,
    builder: (_) {
      return MyStatefulDialog();
    },
  );
}

// StatefulWidget으로 dialog 작성
class MyStatefulDialog extends StatefulWidget {
  
  State<MyStatefulDialog> createState() => _MyStatefulDialogState();
}

class _MyStatefulDialogState extends State<MyStatefulDialog> {
  int? selectedRadio = 0;

  
  Widget build(BuildContext context) {
    return AlertDialog(
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: List<Widget>.generate(3, (int index) {
          return Radio<int>(
            value: index,
            groupValue: selectedRadio,
            onChanged: (int? value) {
              setState(() => selectedRadio = value);
            },
          );
        }),
      ),
    );
  }
}

2. StatefulWidget에서 Dialog를 호출한 경우

  • Dialog를 호출한 위젯에도 Dialog의 변경 값을 바로바로 표시하고 싶을 때
  • 이 경우 DialogStatefulWidget으로 분리하여 구현한 경우는 잘 작동하므로 제외하겠음

방법) StatefulBuilder 사용

  • 아래와 같이StatefulWidget에서 StatefulBuilder를 사용한 경우 이미 setState()가 존재하므로 StatefulBuilderStateSetter의 메서드명을setState로 선언하면 이름이 중복되는 문제가 발생한다
  • 이런 경우 대부분 가까운 놈을 부른거겠지?라고 생각하기 때문에 Builder 안의 StateSetter setState가 호출되므로 원하던 바깥 위젯의 상태는 변경되지 않는다 (context share 안함)

설명을 위해 코드를 짧게 한 스샷이며, setState의 주인은 각각 색깔로 매칭함

  • 그러므로 StateSettersetState를 다른 이름으로 선언해주면 잘 동작한다
Future<dynamic> showStatefulDialog() async {
  await showDialog<void>(
    context: context,
    builder: (_) {
      int? selectedRadio = 0;
      return AlertDialog(
        content: StatefulBuilder(
          builder: (__, StateSetter setDialogState) { // 변수명 변경
            return Column(
              mainAxisSize: MainAxisSize.min,
              children: List<Widget>.generate(3, (int index) {
                return Radio<int>(
                  value: index,
                  groupValue: selectedRadio,
                  onChanged: (int? value) {
                    setDialogState(() => selectedRadio = value);
                    setState(() => text = 'MyStatefulWidget $value');
                  },
                );
              }),
            );
          },
        ),
      );
    },
  );
}
  • 추가로 이 방법 말고도 바깥 위젯의 상태를 변경하는 이벤트 콜백을 파라미터로 받아서 사용해도 된다

전체 코드

전체 코드는 dart.pad에 추가했으니 여기서 테스트 해보자!

0개의 댓글