Never give up

Flutter - Stepper with a problem 본문

해왔던 삽질..

Flutter - Stepper with a problem

대기만성 개발자 2021. 3. 24. 22:59
반응형

material widget들 중 Stepper를 사용해보려고 예제를 만들어봤는데

 

생각보다 문제가 많은 위젯이란걸 느꼈습니다

 

소스코드와 함께 이 부분에 대해서 말해보고자 합니다

class StepperExample extends StatefulWidget {
  final String title;

  StepperExample(this.title);

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

class _StepperExampleState extends State<StepperExample> {
  List<Step> _list = List.generate(
      5,
      (index) => Step(
          title: Text('Step - ${index + 1}'),
          content: Text('Blah blah - $index'),
          state: StepState.editing));
  StreamController<int> _controller = StreamController<int>();

  @override
  void dispose() {
    _controller.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text(widget.title)),
        body: StreamBuilder<int>(
            stream: _controller.stream,
            initialData: 0,
            builder: (context, snapshot) {
              int step = snapshot.data!;
              return Stepper(
                  steps: _list,
                  currentStep: snapshot.data!,
                  onStepCancel: () {
                    _list[step] = Step(
                        title: Text('Step - ${step + 1}'),
                        content: Text('Blah blah - $step'),
                        state: StepState.error);

                    if (step <= 0) {
                      setState(() {});
                      return;
                    }

                    _controller.add(--step);
                  },
                  onStepContinue: () {
                    _list[step] = Step(
                        title: Text('Step - ${step + 1}'),
                        content: Text('Blah blah - $step'),
                        state: StepState.complete);

                    if (step >= _list.length - 1) {
                      setState(() {});
                      return;
                    }

                    _controller.add(++step);
                  },
                  onStepTapped: (index) => _controller.add(index));
            }));
  }
}

간단하게 Streambuilder를 이용해서 callback 이벤트에 따라 currentStep과 list를 갱신해주는 예제입니다

 

근데 여기서 왜 _list[step].state = State.complete 이런식으로 안했는지 궁금하신 분들이 있을텐데

 

Step class의 내부를 보면 답이 나옵니다

class Step {
  const Step({
    required this.title,
    this.subtitle,
    required this.content,
    this.state = StepState.indexed,
    this.isActive = false,
  }) : assert(title != null),
       assert(content != null),
       assert(state != null);

  final Widget title;

  final Widget? subtitle;

  final Widget content;

  final StepState state;

  final bool isActive;
}

네 초기화 할때 final로 되어있어서 이후에 변경이 불가능합니다

 

유저가 continue 혹은 cancel할 때 그 상태를 표시해주려면 새로 초기화를 해줘야된다는 얘기죠

 

즉 Step의 state를 변경하기 위해 전체를 초기화 해야된다는 말입니다..

 

< 이게 뭔 개소리야!? >

다른 개발자분들은 어떻게 사용하는지 보려고 stackoverflow를 찾아봤는데

(링크 : stackoverflow.com/questions/52832710/how-to-modify-a-step-stepstate-on-continue)

 

가장 표를 많이받으신 분의 소스를 보니

 

class _State extends State<MyApp> {

  int _current;

  List<StepState> _listState;

  @override
  void initState() {
    _current = 0;
    _listState = [
      StepState.indexed,
      StepState.editing,
      StepState.complete,
    ];
    super.initState();
  }

  List<Step> _createSteps(BuildContext context) {
    List<Step> _steps = <Step>[
      new Step(
        state: _current == 0
            ? _listState[1]
            : _current > 0 ? _listState[2] : _listState[0],
        title: new Text('Step 1'),
        content: new Text('Do Something'),
        isActive: true,
      ),
      new Step(
        state: _current == 1
            ? _listState[1]
            : _current > 1 ? _listState[2] : _listState[0],
        title: new Text('Step 2'),
        content: new Text('Do Something'),
        isActive: true,
      ),
      new Step(
        state: _current == 2
            ? _listState[1]
            : _current > 2 ? _listState[2] : _listState[0],
        title: new Text('Step 3'),
        content: new Text('Do Something'),
        isActive: true,
      ),
    ];
    return _steps;
  }

  @override
  Widget build(BuildContext context) {
    List<Step> _stepList = _createSteps(context);
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Stepper Example'),
      ),
      body: new Container(
        padding: new EdgeInsets.all(20.0),
        child: new Center(
          child: new Column(
            children: <Widget>[
              Expanded(
                child: Stepper(
                  type: StepperType.vertical,
                  steps: _stepList,
                  currentStep: _current,
                  onStepContinue: () {
                    setState(() {
                      if (_current < _stepList.length - 1) {
                        _current++;
                      } else {
                        _current = _stepList.length - 1;
                      }
                      //_setStep(context);
                    });
                  },
                  onStepCancel: () {
                    setState(() {
                      if (_current > 0) {
                        _current--;
                      } else {
                        _current = 0;
                      }
                      //_setStep(context);
                    });
                  },
                  onStepTapped: (int i) {
                    setState(() {
                      _current = i;
                    });
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

이런식으로 사용을 하시는데 이것도 엄청난 낭비 같은데 왜 구조를 이렇게 만들어 놨는지 잘 모르겠습니다

 

제작자의 의도를 잘 모르겠고, 지금 당장은 좋은 패키지가 있다면 찾아서 쓰는게 훨씬 나을거 같습니다..

반응형
Comments