DevBoi

[Flutter] Form 빌더 동적 생성하기 본문

[Mobile]/[Flutter]

[Flutter] Form 빌더 동적 생성하기

HiSmith 2023. 8. 26. 18:29
반응형

FormBuilder는 폼을 관리하기 위해 편하게 제공해주는 모듈이다.

특정 액션시 Form의요소를 동적으로 변경해보자

 

우선 동적 요소 추가를 해주기 위한, 위젯이다.

Expanded(
                child: MaterialButton(
                  color: Theme.of(context).colorScheme.secondary,
                  child: const Text(
                    "Add Score",
                    style: TextStyle(color: Colors.white),
                  ),
                  onPressed: () {
                    setState(() {
                      fields.add(
                      NewScore(
                        name: 'score_${fields.length}',
                      ));
                    });
                  },
                ),
              ),

해당 버튼을 클릭하면, fields에 NewScore라는 위젯이 추가가 된다.

그리고 삭제를 하면? Widget List에서 마지막 원소를 빼준다.

 

Expanded(
                child: MaterialButton(
                  color: Theme.of(context).colorScheme.secondary,
                  child: const Text(
                    "Dec Score",
                    style: TextStyle(color: Colors.white),
                  ),
                  onPressed: () {
                    setState(() {
                      fields.removeAt(fields.length - 1);
                    });
                  },
                ),
              ),

 

위는 remove시키는 모듈이다.

무튼 이렇게 하고, FormBuilder안에서 감싸주면, 해당 위젯에 대한 정보가 Json stringify 되서 저장된다.

class NewScore extends StatelessWidget {
  const NewScore({
    super.key,
    required this.name,
  });
  final String name;


  @override
  Widget build(BuildContext context) {

    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Row(
        children: [
          Expanded(
            child: FormBuilderTextField(
              name: name,
              validator: FormBuilderValidators.numeric(errorText: 'score is number'),
              decoration: const InputDecoration(
                label: Text('Score'),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

 

생성되는 newScore는 이렇다.

해당 객체가 그냥 단순히 반복되는 것이다.

 

뭐 사실 데모용으로 시현을 위해 필요한 부분이라, 디자인이나 별도 신경은 쓰지않았다.

단순히 위젯 추가 삭제 관리, Form으로 동적 대응이 전부이다.

아래는 전체 소스이다.

import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';


class CreatePopup extends StatefulWidget {

  @override
  State<CreatePopup> createState() => _CreatePopupState();
}

class _CreatePopupState extends State<CreatePopup> {
  final _formKey = GlobalKey<FormBuilderState>();
  final List<Widget> fields = [];
  String savedValue = '';
  var subjectList = ['수학', '영어', '체육'];
  @override
  void initState() {
    savedValue = _formKey.currentState?.value.toString() ?? '';
    super.initState();
  }


  @override
  Widget build(BuildContext context) {
    return FormBuilder(
      key: _formKey,
      // IMPORTANT to remove all references from dynamic field when delete
      clearValueOnUnregister: true,
      child: Column(
        children: <Widget>[
          const SizedBox(height: 20,),
          FormBuilderDropdown<String>(
            name: 'subject',
            decoration: InputDecoration(
                labelText: "subject"
            ),
            items: subjectList.map((subject) => DropdownMenuItem(
            alignment: AlignmentDirectional.center,
            value: subject,
            child: Text(subject),
            )).toList(),
            valueTransformer: (val) => val?.toString(),
          ),
          FormBuilderTextField(
            name: 'small-subject',
            validator: FormBuilderValidators.required(errorText: 'small-subject is required!!'),
            decoration: const InputDecoration(
              label: Text('small-subject'),
            ),
          ),
          ...fields,
          const SizedBox(height: 10),
          Row(
            children: <Widget>[

              const SizedBox(width: 20),
              Expanded(
                child: MaterialButton(
                  color: Theme.of(context).colorScheme.secondary,
                  child: const Text(
                    "Add Score",
                    style: TextStyle(color: Colors.white),
                  ),
                  onPressed: () {
                    setState(() {
                      fields.add(
                      NewScore(
                        name: 'score_${fields.length}',
                      ));
                    });
                  },
                ),
              ),
              Expanded(
                child: MaterialButton(
                  color: Theme.of(context).colorScheme.secondary,
                  child: const Text(
                    "Dec Score",
                    style: TextStyle(color: Colors.white),
                  ),
                  onPressed: () {
                    setState(() {
                      fields.removeAt(fields.length - 1);
                    });
                  },
                ),
              ),
            ],
          ),
          MaterialButton(
              color: Theme.of(context).colorScheme.secondary,
              height: 30,
              child: const Text(
                "Save",
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () {
                _formKey.currentState!.saveAndValidate();
                setState(() {
                  savedValue =
                      _formKey.currentState?.value.toString() ?? '';
                  print(savedValue.toString());
                });
              },
            ),
          // const Divider(height: 20),
          // Text('Saved value: $savedValue'),
        ],
      ),
    );
  }
}

class NewScore extends StatelessWidget {
  const NewScore({
    super.key,
    required this.name,
  });
  final String name;


  @override
  Widget build(BuildContext context) {

    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Row(
        children: [
          Expanded(
            child: FormBuilderTextField(
              name: name,
              validator: FormBuilderValidators.numeric(errorText: 'score is number'),
              decoration: const InputDecoration(
                label: Text('Score'),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
반응형