[Flutter] 일반 Alert 대화 상자 클래스 만들기
상태 관리에는 Hooks Riverpod이 사용됩니다.
가능하면 사용하세요.
목표 구성

AlertDialog에 앞서 일반 지원 클래스(Custom AlertDialog, Custom Text FieldDialog)가 준비되어 있습니다.
일반 Alert 대화 상자 클래스 만들기
CustomAlertDialog
대화 상자의 컨텐트를 자유롭게 구현할 수 있는 Custom AlertDialog를 만듭니다.
custom_alert_dialog.dart
import 'package:flutter/material.dart';
class CustomAlertDialog extends StatelessWidget {
const CustomAlertDialog({
Key? key,
required this.title,
required this.contentWidget,
this.cancelActionText,
this.cancelAction,
required this.defaultActionText,
this.action,
}) : super(key: key);
final String title;
final Widget contentWidget;
final String? cancelActionText;
final Function? cancelAction;
final String defaultActionText;
final Function? action;
Widget build(BuildContext context) {
return AlertDialog(
title: Text(title),
content: contentWidget,
actions: [
if (cancelActionText != null)
TextButton(
child: Text(cancelActionText!),
onPressed: () {
if (cancelAction != null) cancelAction!();
Navigator.of(context).pop(false);
},
),
TextButton(
child: Text(defaultActionText),
onPressed: () {
if (action != null) action!();
Navigator.of(context).pop(true);
},
),
],
);
}
}
요점:content
속성actions
, action
에서 동작 단추를 눌렀을 때의 처리CustomTextFieldDialog
텍스트 필드가 있는 대화 상자만 있는 CustomTextFieldDialog를 만들었습니다.
솔직히 커스텀 얼러트다이얼로그를 사용해도 가능하지만, OK 버튼으로 검증할 수 있는 폼 위젯을 미리 준비했다.
custom_text_field_dialog.dart
import 'package:flutter/material.dart';
class CustomTextFieldDialog extends StatelessWidget {
const CustomTextFieldDialog({
Key? key,
required this.title,
required this.contentWidget,
this.cancelActionText,
this.cancelAction,
required this.defaultActionText,
this.action,
}) : super(key: key);
final String title;
final Widget contentWidget;
final String? cancelActionText;
final Function? cancelAction;
final String defaultActionText;
final Function? action;
Widget build(BuildContext context) {
const key = GlobalObjectKey<FormState>('FORM_KEY');
return AlertDialog(
title: Text(title),
content: Form(
key: key,
child: contentWidget,
),
actions: [
if (cancelActionText != null)
TextButton(
child: Text(cancelActionText!),
onPressed: () {
if (cancelAction != null) cancelAction!();
Navigator.of(context).pop(false);
},
),
TextButton(
child: Text(defaultActionText),
onPressed: () {
if (key.currentState!.validate()) {
if (action != null) action!();
Navigator.of(context).pop(true);
}
},
),
],
);
}
}
견본집
위의 공통 Alert 대화 상자 클래스를 사용하여 대화 상자를 구현합니다.
우선 임용할 반을 준비해라.
dialog_sample_page.dart

dialog_sample_page.dart
import 'package:flutter/material.dart';
import 'widgets/alert_dialog_button_widget.dart';
import 'widgets/dropdown_dialog_button_widget.dart';
import 'widgets/text_field_dialog_button_widget.dart';
import 'widgets/text_field_dialog_button_widget2.dart';
class DialogSamplePage extends StatelessWidget {
const DialogSamplePage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dialog sample'),
actions: const [],
),
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context) {
return Center(
child: Column(
children: const [
AlertDialogButtonWidget(),
DropdownDialogButtonWidget(),
TextFieldDialogButtonWidget(),
TextFieldDialogButtonWidget2(),
],
),
);
}
}
기본 대화상자

버튼을 누르면 대화상자의 코드가 여기에 표시됩니다.
기본 대화상자 견본
alert_dialog_button_widget.dart
import 'package:flutter/material.dart';
import 'package:flutter_dialog_sample/presentation/common_widgets/custom_alert_dialog.dart';
class AlertDialogButtonWidget extends StatelessWidget {
const AlertDialogButtonWidget({
Key? key,
}) : super(key: key);
Widget build(BuildContext context) {
return ElevatedButton(
child: const Text('基本のAlertダイアログボタン'),
onPressed: () => showDialog(
context: context,
builder: (context) => CustomAlertDialog(
title: '基本のAlertダイアログ',
contentWidget: const Text('This is an alert dialog.'),
cancelActionText: 'Cancel',
cancelAction: () {},
defaultActionText: 'OK',
action: () {
// TODO: implement method
},
),
),
);
}
}
OK, 취소 버튼 클릭 시 처리를 적절히 수행하십시오.드롭다운 목록이 있는 대화 상자

드롭다운 목록이 있는 대화 상자가 생성되었습니다.
드롭다운 대화 상자 견본
dropdown_dialog_button_widget.dart
import 'package:flutter/material.dart';
import 'package:flutter_dialog_sample/presentation/common_widgets/custom_alert_dialog.dart';
import 'package:flutter_dialog_sample/presentation/common_widgets/custom_dropdown.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
enum CityType {
tokyo,
nagoya,
osaka,
}
class City {
static const Map<CityType, String> allItems = {
CityType.tokyo: '東京',
CityType.nagoya: '名古屋',
CityType.osaka: '大阪',
};
}
class DropdownDialogButtonWidget extends HookConsumerWidget {
const DropdownDialogButtonWidget({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final cityType = useState<CityType>(CityType.tokyo);
return ElevatedButton(
child: const Text('ドロップダウンダイアログボタン'),
onPressed: () => showDialog(
context: context,
builder: (context) => CustomAlertDialog(
title: 'ドロップダウンダイアログ',
contentWidget: Column(
mainAxisSize: MainAxisSize.min,
children: [
CustomDropdown<CityType>(
labelText: '',
list: City.allItems.keys.toList(),
allTitles: City.allItems.entries
.map(
(e) => e.value,
)
.toList(),
selectedValue: cityType.value,
onChanged: (cityType) => cityType.value = cityType!,
),
],
),
cancelActionText: 'Cancel',
cancelAction: () {},
defaultActionText: 'OK',
action: () {
// TODO: implement method
},
),
),
);
}
}
Dropdown MenuItem 목록 제작과 관련해서는 다른 학급의 커스텀 Dropdown 레벨을 준비해 구현했다.enum에서 정의한 클래스와 데이터 목록을 이 항목에 전달합니다.Dropdown MenuItem 목록을 구현하여 제작한 Custom Dropdown 레벨입니다.
custom_dropdown.dart
custom_dropdown.dart
import 'package:flutter/material.dart';
class CustomDropdown<T> extends StatefulWidget {
const CustomDropdown({
Key? key,
required this.labelText,
required this.list,
required this.allTitles,
required this.selectedValue,
required this.onChanged,
}) : super(key: key);
final String labelText;
final List<T> list;
final List<String> allTitles;
final T selectedValue;
final Function(dynamic) onChanged;
_CustomDropdownState<T> createState() => _CustomDropdownState<T>();
}
class _CustomDropdownState<T> extends State<CustomDropdown> {
late T _selectedValue;
void initState() {
super.initState();
_selectedValue = widget.selectedValue;
}
Widget build(BuildContext context) {
final List<DropdownMenuItem<T>> _dropDownMenuModelNameItems = [];
for (int i = 0; i < widget.list.length; i++) {
_dropDownMenuModelNameItems.add(
DropdownMenuItem(
child: Text(
widget.allTitles[i],
),
value: widget.list[i],
),
);
}
return InputDecorator(
decoration: InputDecoration(
labelText: widget.labelText,
),
child: DropdownButtonHideUnderline(
child: DropdownButton<T>(
isExpanded: true,
isDense: true,
value: _selectedValue,
items: _dropDownMenuModelNameItems,
onChanged: (value) {
setState(() => _selectedValue = value!);
widget.onChanged(value);
},
),
),
);
}
}
원래 OK 버튼을 클릭하면 선택한 항목이 저장됩니다.다음은 StateNotifier 클래스에서 save 방법을 준비한 샘플입니다.(영구화층 미실현)
StateNotifier 클래스를 준비하여 샘플 저장
dialog_sample_state.dart
import 'package:flutter_dialog_sample/presentation/pages/widgets/dropdown_dialog_button_widget.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'dialog_sample_state.freezed.dart';
// part 'dialog_sample_state.g.dart';
class DialogSampleState with _$DialogSampleState {
factory DialogSampleState({
(CityType.tokyo) CityType cityType,
('') String name,
('') String number,
}) = _DialogSampleState;
// factory DialogSampleState.fromJson(Map<String, dynamic> json) =>
// _$DialogSampleStateFromJson(json);
}
dialog_sample_notifier.dartimport 'package:flutter_dialog_sample/presentation/pages/widgets/dropdown_dialog_button_widget.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'dialog_sample_state.dart';
final dialogSampleStateProvider =
StateNotifierProvider.autoDispose<DialogSampleNotifier, DialogSampleState>(
(ref) => DialogSampleNotifier(),
);
class DialogSampleNotifier extends StateNotifier<DialogSampleState> {
DialogSampleNotifier() : super(DialogSampleState());
void read() {}
void save({
CityType? cityType,
String? name,
String? number,
}) {
if (cityType != null) {
state = state.copyWith(cityType: cityType);
}
if (name != null) {
state = state.copyWith(name: name);
}
if (number != null) {
state = state.copyWith(number: number);
}
}
}
dropdown_dialog_button_widget.dartimport 'package:flutter/material.dart';
import 'package:flutter_dialog_sample/presentation/common_widgets/custom_alert_dialog.dart';
import 'package:flutter_dialog_sample/presentation/common_widgets/custom_dropdown.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
+ import '../dialog_sample_notifier.dart';
enum CityType {
tokyo,
nagoya,
osaka,
}
class City {
static const Map<CityType, String> allItems = {
CityType.tokyo: '東京',
CityType.nagoya: '名古屋',
CityType.osaka: '大阪',
};
}
class DropdownDialogButtonWidget extends HookConsumerWidget {
const DropdownDialogButtonWidget({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
+ final state = ref.watch(dialogSampleStateProvider);
+ final notifier = ref.watch(dialogSampleStateProvider.notifier);
- final cityType = useState<CityType>(CityType.tokyo);
+ final cityType = useState<CityType>(state.cityType);
return ElevatedButton(
child: const Text('ドロップダウンダイアログボタン'),
onPressed: () => showDialog(
context: context,
builder: (context) => CustomAlertDialog(
title: 'ドロップダウンダイアログ',
contentWidget: Column(
mainAxisSize: MainAxisSize.min,
children: [
CustomDropdown<CityType>(
labelText: '',
list: City.allItems.keys.toList(),
allTitles: City.allItems.entries
.map(
(e) => e.value,
)
.toList(),
selectedValue: cityType.value,
onChanged: (value) => cityType.value = value!,
),
],
),
cancelActionText: 'Cancel',
cancelAction: () {
+ cityType.value = state.cityType;
},
defaultActionText: 'OK',
action: () {
+ notifier.save(
+ cityType: cityType.value,
+ );
},
),
),
);
}
}
텍스트 필드가 있는 대화 상자

텍스트 필드가 있는 위 그림 대화상자를 실현해 보십시오.
텍스트 필드 대화상자 견본
text_field_dialog_button_widget.dart
import 'package:flutter/material.dart';
import 'package:flutter_dialog_sample/presentation/common_widgets/custom_text_field_dialog.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class TextFieldDialogButtonWidget extends HookConsumerWidget {
const TextFieldDialogButtonWidget({
Key? key,
}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final nameController = useTextEditingController();
final numberController = useTextEditingController();
return ElevatedButton(
child: const Text('テキストフィールドダイアログボタン'),
onPressed: () => showDialog(
context: context,
builder: (context) => CustomTextFieldDialog(
title: 'テキストフィールドダイアログ',
contentWidget: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: nameController,
maxLength: 10,
autovalidateMode: AutovalidateMode.onUserInteraction,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
labelText: '名前',
errorMaxLines: 2,
),
validator: (value) {
if (value == null || value.isEmpty) {
// return 'Name must not be null or empty.';
return '名前を入力してください。';
}
if (value.length > 10) {
return '';
}
return null;
},
),
TextFormField(
controller: numberController,
maxLength: 10,
autovalidateMode: AutovalidateMode.onUserInteraction,
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
labelText: '番号',
errorMaxLines: 2,
),
validator: (value) {
if (value == null || value.isEmpty) {
// return 'Number must not be null or empty.';
return '番号を入力してください。';
}
if (value.length > 10) {
return '';
}
return null;
},
),
],
),
cancelActionText: 'Cancel',
cancelAction: () {},
defaultActionText: 'OK',
action: () {
// TODO: implement method
},
),
),
);
}
}
입력한 경우 및 OK 버튼 헤더가 검증됩니다.
여기서도 보존 처리를 적절하게 실시해야 하지만, 예와 같다
샘플 저장 처리
text_field_dialog_button_widget.dart
+ import '../dialog_sample_notifier.dart';
class TextFieldDialogButtonWidget extends HookConsumerWidget {
const TextFieldDialogButtonWidget({
Key? key,
}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
+ final state = ref.watch(dialogSampleStateProvider);
+ final notifier = ref.watch(dialogSampleStateProvider.notifier);
- final nameController = useTextEditingController();
+ final nameController = useTextEditingController(text: state.name);
- final numberController = useTextEditingController();
+ final numberController = useTextEditingController(text: state.number);
return ElevatedButton(
child: const Text('テキストフィールドダイアログボタン'),
onPressed: () => showDialog(
context: context,
builder: (context) => CustomTextFieldDialog(
title: 'テキストフィールドダイアログ',
contentWidget: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: nameController,
maxLength: 10,
autovalidateMode: AutovalidateMode.onUserInteraction,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
labelText: '名前',
errorMaxLines: 2,
),
validator: (value) {
if (value == null || value.isEmpty) {
// return 'Name must not be null or empty.';
return '名前を入力してください。';
}
if (value.length > 10) {
return '';
}
return null;
},
),
TextFormField(
controller: numberController,
maxLength: 10,
autovalidateMode: AutovalidateMode.onUserInteraction,
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
labelText: '番号',
errorMaxLines: 2,
),
validator: (value) {
if (value == null || value.isEmpty) {
// return 'Number must not be null or empty.';
return '番号を入力してください。';
}
if (int.tryParse(value) == null) {
return '番号を入力してください。';
}
if (value.length > 10) {
return '';
}
return null;
},
),
],
),
cancelActionText: 'Cancel',
cancelAction: () {
nameController.text = state.name;
numberController.text = state.number;
},
defaultActionText: 'OK',
action: () {
notifier.save(
name: nameController.text,
number: numberController.text,
);
},
),
),
);
}
}
AlertDialog와CupertinoAlertDialog를 분리합니다.
iOS 스타일의 대화상자인 Cupertino AlertDialog의 경우 TextFormField를 실행하려면 구축할 때widget 오류가 발생합니다.
※ 자세한 내용은 아래 내용을 참조하세요.
결론은 다음과 같습니다
cancelAction
의 카드 애플릿 패키지를 사용하면 됩니다.AlertDialog와CupertinoAlertDialog의 견본을 나누다
text_field_dialog_button_widget.dart
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dialog_sample/presentation/common_widgets/custom_text_field_dialog.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class TextFieldDialogButtonWidget2 extends HookConsumerWidget {
const TextFieldDialogButtonWidget2({
Key? key,
}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final nameController = useTextEditingController();
final numberController = useTextEditingController();
final builder = CustomTextFieldDialog(
title: 'テキストフィールドダイアログ2',
contentWidget: Card(
color: Colors.transparent,
elevation: 0.0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: nameController,
maxLength: 10,
autovalidateMode: AutovalidateMode.onUserInteraction,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
labelText: '名前',
errorMaxLines: 2,
),
validator: (value) {
if (value == null || value.isEmpty) {
// return 'Name must not be null or empty.';
return '名前を入力してください。';
}
if (value.length > 10) {
return '';
}
return null;
},
),
TextFormField(
controller: numberController,
maxLength: 10,
autovalidateMode: AutovalidateMode.onUserInteraction,
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
labelText: '番号',
errorMaxLines: 2,
),
validator: (value) {
if (value == null || value.isEmpty) {
// return 'Number must not be null or empty.';
return '番号を入力してください。';
}
if (value.length > 10) {
return '';
}
return null;
},
),
],
),
),
cancelActionText: 'Cancel',
cancelAction: () {},
defaultActionText: 'OK',
action: () {
// TODO: implement method
},
);
return ElevatedButton(
child: const Text('テキストフィールドダイアログボタン2'),
onPressed: () {
if (kIsWeb || Platform.isAndroid) {
showDialog(
context: context,
builder: (context) => builder,
);
} else {
showCupertinoDialog(
context: context,
builder: (context) => builder,
);
}
},
);
}
}
evelation:0.0
에서는 Platform.isAndroid
와 showDialog
로 나뉜다.builder 부분은 이전에 정의된 카드 애플릿으로 싸인 부분을 공동으로 사용했다(showDialog와 카드 애플릿으로 싸도 잘 나타낼 수 있다).
(2022.4.1 보충)
▶ CustomText Field Dialog도 Cupertino Alert Dialog에 대한 대응이 필요해서 덧붙였다🙇
CustomTextFieldDialog
custom_text_field_dialog.dart
// ignore_for_file: avoid_print
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class CustomTextFieldDialog extends StatelessWidget {
const CustomTextFieldDialog({
Key? key,
required this.title,
required this.contentWidget,
this.cancelActionText,
this.cancelAction,
required this.defaultActionText,
this.action,
}) : super(key: key);
final String title;
final Widget contentWidget;
final String? cancelActionText;
final Function? cancelAction;
final String defaultActionText;
final Function? action;
Widget build(BuildContext context) {
const key = GlobalObjectKey<FormState>('FORM_KEY');
if (kIsWeb || Platform.isAndroid) {
return AlertDialog(
title: Text(title),
content: Form(
key: key,
child: contentWidget,
),
actions: [
if (cancelActionText != null)
TextButton(
child: Text(cancelActionText!),
onPressed: () {
if (cancelAction != null) cancelAction!();
Navigator.of(context).pop(false);
},
),
TextButton(
child: Text(defaultActionText),
onPressed: () {
if (key.currentState!.validate()) {
print('Validate OK');
if (action != null) action!();
Navigator.of(context).pop(true);
} else {
print('Validate NG');
}
},
),
],
);
} else {
return CupertinoAlertDialog(
title: Text(title),
content: Form(
key: key,
child: contentWidget,
),
actions: [
if (cancelActionText != null)
CupertinoDialogAction(
child: Text(cancelActionText!),
onPressed: () {
if (cancelAction != null) cancelAction!();
Navigator.of(context).pop(false);
},
),
CupertinoDialogAction(
child: Text(defaultActionText),
onPressed: () {
if (key.currentState!.validate()) {
print('Validate OK');
if (action != null) action!();
Navigator.of(context).pop(true);
} else {
print('Validate NG');
}
},
),
],
);
}
}
}
최후
코드량이 상당히 많은 양으로 바뀌었네요.
요청이 있으면github 링크를 공개하고 싶습니다.
참고 자료
Reference
이 문제에 관하여([Flutter] 일반 Alert 대화 상자 클래스 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/mamoru_takami/articles/b76b734f2d7783텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)