본문 바로가기

안드로이드/Flutter

Flutter 코드 리팩토링

페이스북 뉴스피드 / Lo-fi 프로토타이핑

페이스북 뉴스피드와 같은 한 화면에 여러 기능을 하는 위젯이 여러개 있는 경우, 한 위젯만으로 관리하기가 매우 힘듭니다. 위 이미지의 우측과 같이 프로토타이핑 하더라도 300여라인의 코드와 수십개의 위젯이 한 dart 파일안에 있습니다.

위젯을 리팩토링할 때는 가장 먼저 분리할 위젯의 영역을 정해야합니다. 위 이미지에서는 아래와 같이 나눌 수 있습니다.

  • AppBar (뉴스피드 타이틀을 가진)
  • 5개 버튼이 있는 탭바
  • 뉴스피드 리스트
  • 하단의 버튼 리스트

각 영역을 새로운 위젯 또는 메소드로 분리할 수 있습니다. 가장 쉬운 접근 방법은 각 영역을 메소드로 분리하는 방법입니다. 하지만 이 방식은 안티패턴입니다.

이번에는 카운터 예제로 왜 메소드로 리팩토링 하는 것이 안티패턴인지 알아보겠습니다.

 

카운터 예제에 3개의 위젯을 3가지 방법으로 만들었습니다.

  • const + StatelessWidget
  • StatelessWidget
  • Method

각 위젯을 만드는 코드의 부분입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      const CounterInformationText(text: 'I am const Stateless Widget:'),
      CounterInformationText(text: 'I am Stateless Widget'),
      _buildCounterInfomationText(text: 'I am Method'),
      Text('You have pushed the button this many times:'),
      Text(
        '$_counter',
      ),
    ],
  ),
),

StatelessWidget은 한개를 만들었고, const 키워드 여부에 따라 어떤 다른 동작을 하는지 살펴봅니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
class CounterInformationText extends StatelessWidget {
  final TextStyle textStyle = const TextStyle(
    fontSize: 40,
    fontWeight: FontWeight.bold,
  );
  final String text;
  const CounterInformationText({this.text});
@override
  Widget build(BuildContext context) {
    debugPrint('$text, ${this.hashCode}');
    return Text(text, style: textStyle);
  }
}

build 메소드가 호출되면 text와 인스턴스의 해시 코드를 출력하도록 했습니다.

메소드로 위젯을 그리는 코드입니다.

1
2
3
4
_buildCounterInfomationText({String text}) {
  debugPrint('$text, ${this.hashCode}');
  return Text(text, style: textStyle);
}

내용이 매우 간단하고, 보기에도 가장 깔끔하게 리팩토링한 것 처럼 보입니다. 실제로 카운터가 변경되고, 어떤 결과를 보이는지 살펴봅니다.

카운터의 변경은 FloatingActionButton을 누르면 일어납니다 누르는 즉시 변경이 필요한 부분들의 build 메소드가 호출됩니다. debugPrint의 결과입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# 최초
I am Method, 544291526
I am const Stateless Widget:, 1055342562
I am Stateless Widget, 137000319
# 한번 눌렀을 때
I am Method, 544291526
I am Stateless Widget, 18505293
# 두번 눌렀을 때
I am Method, 544291526
I am Stateless Widget, 350460176
# 세번 눌렀을 때
I am Method, 544291526
I am Stateless Widget, 949599705

최초에는 메소드 / const Stateless 위젯 / Stateless 위젯 모두 build 메소드가 호출됩니다.

문제는 그 다음입니다. const Stateless 위젯의 build 메소드는 더이상 호출되지 않습니다. 그러나 나머지 메소드와 const가 없는 Stateless 위젯은 계속 호출됩니다. 특히 Stateless 위젯은 인스턴스를 매번 만들게 되므로 해시코드도 계속 변경됩니다.

Stateless 위젯은 이름 그대로 상태를 가지지 못하기 때문에 복잡한 연산이 들어갈 여지가 적습니다. 그러나 메소드로 리팩토링 한 경우에는 조금 문제가 됩니다.

 

메소드에서 무언가 계산을 한 다음 위젯을 리턴한다면 계산을 하는 동안 매번 UI 렌더링에 블러킹이 생기게 됩니다.

“메소드로 위젯을 분리하는 것이 안티패턴이다" 는 Flutter를 사용하는 사용자들에게 잘 알려진 안티패턴 중 하나입니다. 처음에는 리페인트가 많이 일어나서 그런 것으로 생각했으나 리페인팅뿐만 아니라 메소드라는 특성상 다른 연산 코드들이 끼어들 여지가 너무나 높습니다.

 

때문에 상태가 없는 긴 위젯을 리팩토링할 때는 다음 두가지만 유의하면 됩니다.

  • StatelessWidget일 것
  • const 키워드를 최대한 사용할 것

const 키워드의 경우 위젯 속성 외에도 생성자, TextStyle과 같은 속성 등에도 사용할 수 있습니다. 더 이상 바뀔 가능성이 없는 모든 곳에 const 키워드를 사용하면, 화면이 조금만 바뀌더라도 관계없는 위젯들이 다시 처음부터 렌더링 되는 일을 막을 수 있을 것 입니다.

 

 

출처:<ChangJoo Park> 위젯 리팩토링 : 긴 위젯은 어떻게 나누어야할까?