본문 바로가기

Personal Posting/Flutter

Flutter에서 Hooks 사용하기

 

Flutter에서 Hooks는 UI 로직을 관리하는 새로운 방식으로, 기존의 StatefulWidget을 대체하거나 보완하여 코드의 가독성을 높이고 로직을 간결하게 만들어준다. React에서 처음 도입된 Hooks는 Flutter에서도 비슷한 개념으로 사용되며, 특히 반복적인 초기화 및 해제 작업을 줄이고 재사용성을 높이는 데 유용하다.

(단, React Hooks 처럼 공식 지원이 아니고 flutter_hooks 라는 서드파티 패키지 설치를 통해 사용이 가능하다.)

 

Hooks란 무엇인가?

1. React에서의 Hooks

 - 클래스 없이 상태를 관리할 수 있도록 도와주는 기능

 - https://honken.tistory.com/140 (예전 React Hooks 정리글)

2. Flutter에서의 Hooks

 - HookWidget을 사용하여 여러 개의 Hook을 한 위젯에 연결 가능

 - 각 Hook은 자체적으로 초기화 및 해제를 처리하므로, 개발자가 직접 initState()나 dispose()를 작성할 필요가 없음

 - 코드의 가독성을 높이고 중복을 줄이며, 특히 대규모 프로젝트에서 유용

 

아래의 예제 코드로 더 자세히 알아보자.

 

1. 기존 StatefulWidget 방식

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
  AnimationController animationController;

  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 1000),
      value: 1,
    )..addListener(() {
        if (animationController.status == AnimationStatus.completed) {
          animationController.repeat();
        }
        setState(() {});
      });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTap: animationController.forward,
        onDoubleTap: animationController.reverse,
        child: FadeTransition(
          opacity: animationController,
          child: Center(child: FlutterLogo(size: 300)),
        ),
      ),
    );
  }

  @override
  void dispose() {
    animationController.dispose();
    super.dispose();
  }
}
  • initState()dispose()에서 AnimationController를 초기화하고 해제해야 함
  • 코드가 복잡해지고, 특히 여러 화면에서 동일한 로직을 반복해야 할 경우 유지보수가 어려움

2. HookWidget 방식

class Home extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final hookAnimation = useAnimationController(
      duration: Duration(milliseconds: 1000),
      initialValue: 1,
    );

    return Scaffold(
      body: GestureDetector(
        onTap: () => hookAnimation.forward(),
        onDoubleTap: () => hookAnimation.reverse(),
        child: FadeTransition(
          opacity: hookAnimation,
          child: Center(child: FlutterLogo(size: 300)),
        ),
      ),
    );
  }
}
  • useAnimationController()를 사용하여 간단히 AnimationController 생성.
  • initState() dispose()가 필요없음
  • 전자에 비해 훨씬 간결하고 가독성이 높아진 코드

Custom Hook 만들기

여러 화면에서 동일한 스크롤과 애니메이션 로직이 필요하다면 이를 매번 작성하는 것은 비효율적일 것이다. 따라서 이런 경우, Custom Hook을 만들어서 재사용을 할 수 있다.

 

1. Hook 클래스 정의

class HookScroller extends Hook<ScrollController> {
  final AnimationController controller;

  HookScroller(this.controller);

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

class _HookScrollerState extends HookState<ScrollController, HookScroller> {
  ScrollController scroller;

  @override
  void initHook() {
    super.initHook();
    scroller = ScrollController()
      ..addListener(() {
        if (scroller.position.userScrollDirection == ScrollDirection.forward) {
          hook.controller.forward();
        } else if (scroller.position.userScrollDirection == ScrollDirection.reverse) {
          hook.controller.reverse();
        }
      });
  }

  @override
  ScrollController build(BuildContext context) => scroller;

  @override
  void dispose() {
    scroller.dispose();
    super.dispose();
  }
}

 

2. 사용자 정의 메서드 추가

ScrollController scrollController(AnimationController controller) {
  return Hook.use(HookScroller(controller)); // Hook을 위젯트리에 연결하는 역할
}

 

3. Hook 클래스에서 사용

class Home extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final hookAnimation = useAnimationController(duration: Duration(milliseconds: 500));
    final hookScroll = scrollController(hookAnimation);

    return Scaffold(
      body: ListView.builder(
        controller: hookScroll,
        itemCount: 20,
        itemBuilder: (context, index) => Container(
          margin: EdgeInsets.all(10),
          child: FadeTransition(
            opacity: hookAnimation,
            child: FlutterLogo(size: 100),
          ),
        ),
      ),
    );
  }
}

 

Hooks의 장점을 다시 정리하면,

1. 코드 간소화

  • initState()dispose() 없이 상태 관리 가능
  • 중복된 로직 제거로 코드 가독성 향상

2. 재사용성

  • Custom Hook을 만들어 여러 화면에서 동일한 로직 재사용 가능

3. 유지보수 용이

  • 대규모 프로젝트에서 상태 관리와 UI로직이 분리되어 유지보수가 쉬워짐

 

주의사항

  • 모든 Hooks는 반드시 build() 메서드 내부에서 호출해야 한다.
  • 작은 화면에서 StatefulWidget이 더 간단할 수 있으므로 무조건적인 사용은 지양하는 것이 좋다.
  • Custom Hook은 초기 설정이 복잡할 수 있다. (다만 반복 작업이 많을 경우, 결국은 효율적인 방법)

 

결론

Flutter Hooks는 특히 대규모 프로젝트나 복잡한 UI 상태 관리를 요구하는 경우 매우 유용하며, Custom Hook을 적절히 잘 활용한다면 코드 재사용성을 극대화하고 유지보수를 용이하게 할 수 있을 것이다. 개인적으로는 거의 GetX를 사용하긴 하는데, GetX는 상태 관리와 의존성 주입에 중점을 둔 패키지고, Flutter Hooks는 위젯 상태 관리 로직을 간결하게 만드는데 포커스를 두기 때문에 약간 결이 다르긴하다. 기회가 된다면 현업에서도 적용해보면 좋을 것 같다는 생각이 든다.

 

아래 링크의 포스팅을 참고하여 정리한 내용입니다. 원문을 확인하고 싶으시면 아래 링크를 확인해주세요.

출처 - https://flutterexperts.com/hooks-in-flutter/