한국어
오픈소스포럼
 이곳은 다양한 오픈소스 프로젝트를 소개하고 리뷰, 활용 방법을 공유합니다.

Flutter 위젯의 상태관리에 대해서

makersweb 2023.04.06 11:13 조회 수 : 447

이 글에서는 Flutter에서 위젯의 상태관리에 대해서 몇 가지 기본적인 접근방법을 소개하고 있다.

TL;DR:

  • 상태를 관리하는 방법에는 여러 가지가 있다.
  • 위젯 디자이너는 사용할 접근 방식을 선택한다.
  • 확실하지 않은 경우 상위 위젯에서 상태를 관리하는 것이 좋다.

stateful 위젯의 상태는 누가 관리할까? 위젯 자체? 상위 위젯? 둘 다? 제3객체? 답은… 상황에 따라 다르다. 위젯을 대화형으로 만드는 몇 가지 유효한 방법이 있다. 위젯을 설계할 때 위젯이 어떻게 사용될 것으로 예상하는지에 따라 결정을 내린다. 상태를 관리하는 가장 일반적인 방법은 다음과 같다.

  • 위젯 자체에서 상태를 관리
  • 부모에서 위젯의 상태를 관리
  • 위 두가지를 적절히 혼합

어떤 접근 방식을 사용할지 어떻게 결정할까? 다음 원칙은 결정하는 데 도움이 된다.

  • 상태가 사용자 데이터인 경우(예: 확인란의 선택 또는 선택 해제 모드 또는 슬라이더의 위치) 상태는 상위 위젯에서 가장 잘 관리된다.

  • 상태가 유동적인 경우(예: 애니메이션) 위젯 자체에서 상태를 관리하는 것이 가장 좋다.

확실하지 않은 경우 상위 위젯에서 상태를 관리하는 것부터 시작하는 것이 좋다.

예제

TapboxA, TapboxB 및 TapboxC의 세 가지 간단한 예를 만들어 상태를 관리하는 다양한 방법의 예를 살펴본다.

예제는 모두 유사하게 작동한다. 각 예제는 탭할 때 녹색 또는 회색 상자 사이를 전환하는 컨테이너를 만든다. boolean의 _active은 색상을 결정한다. 녹색은 활성, 회색은 비활성이다.

tapbox-active-state.pngtapbox-inactive-state.png

이 예제에서는 GestureDetector를 사용하여 Container에서 활동을 캡처한다.

위젯 자체에서 상태를 관리

때로는 위젯이 내부적으로 상태를 관리하는 것이 가장 합리적이다. 예를 들어 ListView는 콘텐츠가 렌더링 상자를 초과하면 자동으로 스크롤된다. ListView를 사용하는 대부분의 개발자는 ListView의 스크롤 동작을 관리하기를 원하지 않으므로 ListView 자체에서 스크롤 오프셋을 관리한다.

_TapboxAState 클래스의 역할:

  • TapboxA의 상태를 관리한다.
  • 상자의 현재 색상을 결정하는 _active 부울을 정의한다.
  • 상자를 누를 때 _active를 업데이트하고 setState() 함수를 호출하여 UI를 업데이트하는 _handleTap() 함수를 정의한다.
  • 위젯에 대한 모든 대화형 동작을 구현한다.
import 'package:flutter/material.dart';

// TapboxA manages its own state.

//------------------------- TapboxA ----------------------------------

class TapboxA extends StatefulWidget {
  const TapboxA({super.key});

  @override
  State<TapboxA> createState() => _TapboxAState();
}

class _TapboxAState extends State<TapboxA> {
  bool _active = false;

  void _handleTap() {
    setState(() {
      _active = !_active;
    });
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: _active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
        child: Center(
          child: Text(
            _active ? 'Active' : 'Inactive',
            style: const TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
      ),
    );
  }
}

//------------------------- MyApp ----------------------------------

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter Demo'),
        ),
        body: const Center(
          child: TapboxA(),
        ),
      ),
    );
  }
}

위젯의 상태를 상위 위젯이 관리

종종 부모 위젯이 상태를 관리하고 자식 위젯에 업데이트 시기를 알려주는 것이 가장 합리적일 수 있다. 예를 들어 IconButton을 사용하면 아이콘을 탭 가능한 버튼으로 취급할 수 있다. IconButton은 적절한 조치를 취할 수 있도록 부모 위젯이 버튼이 탭되었는지 여부를 알아야 한다고 결정했기 때문에 Stateless 위젯이다.

다음 예에서 TapboxB는 콜백을 통해 상태를 부모에게 내보낸다. TapboxB는 상태를 관리하지 않기 때문에 StatelessWidget의 하위 클래스다.

ParentWidgetState 클래스의 역할:

  • TapboxB의 _active 상태를 관리한다.
  • 상자를 탭할 때 호출되는 메서드인 _handleTapboxChanged()를 구현한다.
  • 상태가 변경되면 setState()를 호출하여 UI를 업데이트한다.

TapboxB 클래스의 역할:

  • 모든 상태가 부모에 의해 처리되기 때문에 StatelessWidget을 확장하여 구현된다.
  • 탭이 감지되면 부모에게 알린다.
import 'package:flutter/material.dart';

// ParentWidget manages the state for TapboxB.

//------------------------ ParentWidget --------------------------------

class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      child: TapboxB(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//------------------------- TapboxB ----------------------------------

class TapboxB extends StatelessWidget {
  const TapboxB({
    super.key,
    this.active = false,
    required this.onChanged,
  });

  final bool active;
  final ValueChanged<bool> onChanged;

  void _handleTap() {
    onChanged(!active);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
        child: Center(
          child: Text(
            active ? 'Active' : 'Inactive',
            style: const TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
      ),
    );
  }
}

적절히 혼합(Mix and match)한 방식

일부 위젯의 경우 믹스 앤 매치 방식이 가장 적합하다. 이 시나리오에서 상태 저장 위젯은 일부 상태를 관리하고 상위 위젯은 상태의 다른 측면을 관리한다.

TapboxC 예제에서 아래로 탭하면 상자 주위에 진한 녹색 테두리가 나타난다. 위로 탭하면 테두리가 사라지고 상자의 색상이 변경된다. 위로 탭하면 테두리가 사라지고 상자의 색상이 변경된다. TapboxC는 _active 상태를 부모에게 내보내지만 _highlight 상태는 내부적으로 관리한다. 이 예제에는 _ParentWidgetState 및 _TapboxCState라는 두 개의 State 객체가 있다.

_ParentWidgetState 객체의 역할:

  • _active 상태를 관리한다.
  • 상자를 탭할 때 호출되는 메서드인 _handleTapboxChanged()를 구현한다.
  • 탭이 발생하고 _active 상태가 변경되면 setState()를 호출하여 UI를 업데이트한다.

_TapboxCState 객체의 역할:

  • _highlight 상태를 관리한다.
  • GestureDetector는 모든 탭 이벤트를 수신한다. 사용자가 탭하면 하이라이트 표시가 추가된다(진한 녹색 테두리로 구현됨). 사용자가 탭을 놓으면 하이라이트가 제거된다.
  • setState()를 호출하여 탭 다운, 탭 업 또는 탭 취소 시 UI를 업데이트하고 _highlight 상태가 변경된다.
  • 탭 이벤트에서 해당 상태 변경을 부모 위젯에 전달하여 widget 속성을 사용하여 적절한 조치를 취한다.
import 'package:flutter/material.dart';

//---------------------------- ParentWidget ----------------------------

class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      child: TapboxC(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//----------------------------- TapboxC ------------------------------

class TapboxC extends StatefulWidget {
  const TapboxC({
    super.key,
    this.active = false,
    required this.onChanged,
  });

  final bool active;
  final ValueChanged<bool> onChanged;

  @override
  State<TapboxC> createState() => _TapboxCState();
}

class _TapboxCState extends State<TapboxC> {
  bool _highlight = false;

  void _handleTapDown(TapDownDetails details) {
    setState(() {
      _highlight = true;
    });
  }

  void _handleTapUp(TapUpDetails details) {
    setState(() {
      _highlight = false;
    });
  }

  void _handleTapCancel() {
    setState(() {
      _highlight = false;
    });
  }

  void _handleTap() {
    widget.onChanged(!widget.active);
  }

  @override
  Widget build(BuildContext context) {
    // This example adds a green border on tap down.
    // On tap up, the square changes to the opposite state.
    return GestureDetector(
      onTapDown: _handleTapDown, // Handle the tap events in the order that
      onTapUp: _handleTapUp, // they occur: down, up, tap, cancel
      onTap: _handleTap,
      onTapCancel: _handleTapCancel,
      child: Container(
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
          border: _highlight
              ? Border.all(
                  color: Colors.teal[700]!,
                  width: 10.0,
                )
              : null,
        ),
        child: Center(
          child: Text(widget.active ? 'Active' : 'Inactive',
              style: const TextStyle(fontSize: 32.0, color: Colors.white)),
        ),
      ),
    );
  }
}

대체 구현으로 활성 상태를 내부에 유지하면서 highlight 표시 상태를 부모에게 내보냈을 수 있지만 누군가에게 해당 탭 상자를 사용하도록 요청하면 별로 의미가 없다고 불평할 것이다. 개발자는 상자가 활성화되어 있는지 여부를 확인해야 한다. 개발자는 highlight 표시 관리 방법에 관심이 없으며 탭 상자에서 이러한 세부 정보를 처리하는 것을 선호할 수 있다.

번호 제목 글쓴이 날짜 조회 수
» Flutter 위젯의 상태관리에 대해서 file makersweb 2023.04.06 447
34 [NodeGui] JavaScript로 데스크탑 응용프로그램 작성 file makersweb 2023.02.21 1056
33 openFrameworks 한글 폰트 설정 및 출력하기 file makersweb 2023.02.19 195
32 LVGL 을 통해 GUI 구현 시 한글 폰트 추가 file makersweb 2023.02.07 1065
31 Windows에서 Qt Creator + CMake + vcpkg 로 C++ 개발환경 구성 (POCO 라이브러리 DirectoryWatcher 예제) file makersweb 2023.01.14 651
30 NAppGUI, C언어용 크로스 플랫폼 GUI 라이브러리 file makersweb 2022.10.10 855
29 OTA 오픈소스 프로젝트 makersweb 2022.08.03 468
28 AGL (Automotive Grade Linux) 개요 file makersweb 2022.06.19 1849
27 Chromium과 Ozone 층 file makersweb 2022.03.03 725
26 Flutter Application 에서 한글(EUC-KR) 깨져서 나오는 문제 file makersweb 2022.01.06 2664
25 CopperSpice 에 대해서 (C++ Gui 라이브러리) file makersweb 2022.01.02 536
24 Flutter/Dart 와 Qt/QML 비교 file makersweb 2021.11.07 1396
23 VSCode 와 Qbs 플러그인으로 C/C++ 개발환경 구성 file makersweb 2021.09.12 839
22 ZeroMQ 를 이용한 Qt 응용프로그램 간 통신 file makersweb 2021.08.28 828
21 C++를 위한 Lottie 라이브러리 with SDL2 file makersweb 2021.08.15 962
20 CANdevStudio 를 사용하여 CAN 네트워크 시뮬레이션 file makersweb 2021.03.09 1685
19 Protocol Buffers 를 이용한 직렬화 with Conan Package Manager file makersweb 2021.02.24 859
18 라즈베리파이에서 Redis의 Pub/Sub 패턴을 사용하는 Electron 응용프로그램 file makersweb 2021.01.31 679
17 Nana, C++용 크로스플랫폼 GUI 라이브러리 file makersweb 2021.01.06 2062
16 라즈베리파이4에서 openFrameworks 예제 실행 file makersweb 2020.12.13 558