<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발자놀이</title>
    <link>https://ctoahn.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 4 Aug 2026 16:02:34 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>윈드.</managingEditor>
    <image>
      <title>개발자놀이</title>
      <url>https://tistory1.daumcdn.net/tistory/4762003/attach/01cd6789037b4929b9043116250e09d1</url>
      <link>https://ctoahn.tistory.com</link>
    </image>
    <item>
      <title>[Flutter] Navigator와 Route - 2부 Route 상태 이벤트의 활용</title>
      <link>https://ctoahn.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;화면의 설정 정보를 담는 Route는 여러 Callback methods를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드들은 자신이 Navigator에 push/pop 될 때 호출되고,&amp;nbsp; nextRoute가 push/pop될때도 호출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 개발할 때 현재 화면이&amp;nbsp; Natigator에서 가장 위에 표시되는 화면인지 알고 싶을 때가 있다.&amp;nbsp; 특히 다음 화면으로 넘어갔다가 다시 이전화면으로 돌아올 때 화면의 데이터를 갱신하거나 기타 필요한 동작을 해야 할 때가 있기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론&amp;nbsp; await Nativiator.push()를 처럼 await구분을 이용해서 다음 화면이 pop될때 까지 기다리는 방법도 있지만 화면이 복잡해지고 연결된 다음화면이 여러 개 있을 경우 일괄적인 처리가 필요할 때도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android에서는 Activity의 Lifecycle 이벤트를 이용해서 Resume/Pause 되는 이벤트를 수신할 수 있지만 Flutter에서는 그와 같은 이벤트를 발생키시지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Navigator는 내부적으로 _RouteEntry이라는 Route의 랩핑 클래스를 따로 만들어 Route를 관리하는데 _RouteEntry는 별도의 상태정보인 _RouteLifecycle를 사용한다. 이 값들은 Navigator에서 내부적으로 사용하기 위한 Inner class로&amp;nbsp; 외부에서 직접 사용하기에는 문제가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해서 우리가 할 수 있는 몇가지 방법이 있는데 그중 한 가지 방법을 소개하고자 한다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Route Callback Methods&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 Navigator/_RouteEntiry /Route/Widget(화면)의 관계를 나타 내고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없음.png&quot; data-origin-width=&quot;2185&quot; data-origin-height=&quot;1345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brXer4/btr8J0yNuii/YdfFErQZdCBMKrdqqZx0d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brXer4/btr8J0yNuii/YdfFErQZdCBMKrdqqZx0d1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brXer4/btr8J0yNuii/YdfFErQZdCBMKrdqqZx0d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrXer4%2Fbtr8J0yNuii%2FYdfFErQZdCBMKrdqqZx0d1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2185&quot; height=&quot;1345&quot; data-filename=&quot;제목 없음.png&quot; data-origin-width=&quot;2185&quot; data-origin-height=&quot;1345&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 직접 다루는 Widget에서 _RouteLifecycle과 유사한 화면의 Lifecycle정보가 필요할때가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 활용 할수 있는 방법은 Route의 Callback methods를 이용해서 Route를 Lifecycle정보를 직접 관리하고 이 값이 변경될 때 Widget에게 전달할 수 있도록 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 우선 Route에서 활용 가능한 Callback Method들에 대해서 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래코드는 Route의 전체 코드 중 Callback Method만 따로 정리한 것이다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;  TickerFuture didPush() { }

  void didAdd() { }

  void didReplace(Route&amp;lt;dynamic&amp;gt;? oldRoute) {  }

  bool didPop(T? result) {} 

  void didComplete(T? result) {}

  void didPopNext(Route&amp;lt;dynamic&amp;gt; nextRoute) { }

  void didChangeNext(Route&amp;lt;dynamic&amp;gt;? nextRoute) { }

  void didChangePrevious(Route&amp;lt;dynamic&amp;gt;? previousRoute) { }

  void dispose() {  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 명에서 어느 정도 의미를 파악할 수 있겠지만 Route가 Navigator에 push / pop 되는 상태에 따라 호출되는 didPush/didPop 같은 함수도 있고 다음 Route(nextRoute)가 Navigator에 push/pop 되었을 때 알려주는 didChangeNext / didPopNext과 같은 함수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Navigator가 Route의 Callback 함수를 호출하는 것을 이용해서 Route의 자체적인 상태를 정의해 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 안드로이드의 Activity의 상태를 참고해서 created/ resumed / paused의 상태가 있다고 가정해 보자&lt;/p&gt;
&lt;pre id=&quot;code_1680960427135&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Enum RouteLifecycle { created, resumed, paused, destroryed }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;created는 처음 Route의 상태이며, didPush가 발생하면 resumed로 didPop이 발생하면 destroryed로 변경된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 nextRoute가 push 되는 상태를 알려주는 didChangeNext가 발생하면 paused로 didPopNext가 발생하면 resumed로 변경된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 LifeCycle의 상태정의는 Flutter에서 제공하는 것이 아닌 개발자가 임의로 정해서 만든 것으로 Route의 Callback함수를 활용해서 정의한 것임에 유의하기 바란다. (필요에 따라 상태를 정의해서 사용하세요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 이제 위 이벤트에 따라 Route의 상태를 변경하는 CustomPageRoute를 만들어 보자.&lt;br /&gt;아래 Route는 몇 가지 Callback함수를 재정위 한 후는 ValueNotifier를 통해서 RouteLifecycle의 변경된 값이 외부로 전달될 수 있도록 구성하였다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class CustomPageRoute&amp;lt;T&amp;gt; extends PageRoute&amp;lt;T&amp;gt; with MaterialRouteTransitionMixin&amp;lt;T&amp;gt; {

  final ValueNotifier&amp;lt;RouteLifecycleState&amp;gt; lifecycleState = ValueNotifier(RouteLifecycleState.created);

  Route? _nextRoute;
 
  CustomPageRoute({required super.builder});
 
  @override
  void didAdd() {
    lifecycleState.value = RouteLifecycleState.created;
    super.didAdd();
  }

  @override
  TickerFuture didPush() {
    lifecycleState.value = RouteLifecycleState.resumed;
    return super.didPush();
  }

  @override
  void didChangeNext(Route? nextRoute) {
    if (_nextRoute == null &amp;amp;&amp;amp; nextRoute != null) {
      stateListener?.onRouteStateChanged(route: this, state: RouteState.paused);
    }
    _nextRoute = nextRoute;
    super.didChangeNext(nextRoute);
  }

  @override
  void didPopNext(Route nextRoute) {
    lifecycleState.value = RouteLifecycleState.resumed;
    _nextRoute = null
    super.didPopNext(nextRoute);
  }

  @override
  void dispose() {
    lifecycleState.dispose();
    _nextRoute = null
    super.dispose();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 lifecycleState의 값을 callback함수를 override 해 수정하는 코드만 추가된 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Route 상태 전달하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이렇게 변경된 lifecycleState를 Widget에게 전달하는 방법을 찾아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Route에서 생성하는 Widget의 build를 위임할 CustomPageBuilder 함수를 정의하자.&lt;/p&gt;
&lt;pre class=&quot;elm&quot;&gt;&lt;code&gt;typedef CustomPageBuilder = Widget Function(
  BuildContext context,
  ValueNotifier&amp;lt;RouteLifecycleState&amp;gt; lifecycleState,
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 CustomPageRoute를 통해 화면정보를 설정할 때에는 context와 lifecycleState를 모두 argument로 받아서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CustomPageRoute를 생성할 때는 CustomPageBuilder를 생성자 argument로 전달하도록 CustomPageRoute의 생성자를 추가해야 한다.&amp;nbsp; 그리고 buildContent함수를 override 해서 builder에서 생성한 Widget을 전달하도록 변경하자.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class CustomPageRoute&amp;lt;T&amp;gt; extends PageRoute&amp;lt;T&amp;gt; with MaterialRouteTransitionMixin&amp;lt;T&amp;gt; {
  final ValueNotifier&amp;lt;RouteLifecycleState&amp;gt; lifecycleState = ValueNotifier(RouteLifecycleState.created);

 final CustomPageBuilder builder;

  CustomPageRoute(this.builder);

  &amp;hellip;&amp;hellip;..

  @override
  Widget buildContent(BuildContext context) =&amp;gt; builder(context, lifecycleState);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 필요한 기본 준비는 완료되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Navigator에 새로운 화면을 push 하기 위해 CustomPageRoute를 사용하게 되면 builder함수의 argument로 들어온 lifecycleState를 화면을 표시할 Widget으로 전달해서 route의 lifecycle상태를 구독할 수 있도록 하자.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;final route = CustomPageRoute((context, routeLifecycleState) {
      return LoginPageWidget(routeLifecycleState: routeLifecycleState);
});

Navigator.push(context, route);


class LoginPageWidget extends StatefulWidget {
  final ValueNotifier&amp;lt;RouteLifecycleState&amp;gt;? routeLifecycleState;
  const LoginPageWidget({super.key, this.routeLifecycleState});

  @override
  State&amp;lt;LoginPageWidget&amp;gt; createState() =&amp;gt; _LoginPageWidgetState();
}

class _LoginPageWidgetState extends State&amp;lt;LoginPageWidget&amp;gt; {
  ValueNotifier&amp;lt;RouteLifecycleState&amp;gt;? routeLifecycleState;

  @override
  void initState() {
    super.initState();
    routeLifecycleState = widget.routeLifecycleState;
    routeLifecycleState?.addListener(() {
      switch (routeLifecycleState?.value) {
        case RouteLifecycleState.resumed:
          // do something
          break;
        case RouteLifecycleState.paused:
          // do something
          break;
        default:
          break;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    throw UnimplementedError();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Widget은 위에서 처럼 StatefulWidget으로 만들어 사용할 수도 있고 별도의 상태관리 라이브러리를 사용할 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 중요한 것은 위젯을 생성자를 통해 ValueNotifier인&amp;nbsp; RouteLifecycleState를 수신할 수 있다는 것이며 이것을 활용해 원하는 작업을 해줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Route의 상태 변경을 Widget로 전달할 수 있는 방법은 위에서 제시한 방법되에도 여러 가지 생각해 볼 수 있는 것 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 상태정보를 InheritedWidget으로 만든 후 Route의 상태가 변경될 때 하위 위젯에 변경된 정보를 전달하도록 하는 방법도 사용할 수 있고, 그 외 Provider와 같은 라이브러리를 사용해서 InheritedWidget처럼 하위로 위젯으로 상태변경 이벤트를 전파할 수도 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Route의 상태변경 이벤트를 Widget에서 알 필요가 없다고 생각하실 수도 있습니다. 하지만 Route에 정의된 Callback 함수들을 활용하면 상태정보를 알 수 있다는 것 정도는 알고 있으면 좋지 않을까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Flutter는 개발자의 성향에 따라 아주 많이 유연하게 프로그램을 구성할 수 있습니다. 그래서 다른 것들에 비해서 해결책이 다양하게 나올 수 있습니다.&amp;nbsp; &lt;/span&gt;&lt;span&gt;어쩌면 위에서 소개한 방법 외에도 Route의 상태 이벤트를 Widget으로 전파시키는 다양한 방법들이 있을 수 있으니 자신만의 방법을 찾아보시는 것도 좋을 것 같습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&quot;3부 Navigator 1.0 vs Navigator 2.0&quot; 에서 계속&lt;/span&gt;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>Flutter</category>
      <category>navigator</category>
      <category>Route</category>
      <author>윈드.</author>
      <guid isPermaLink="true">https://ctoahn.tistory.com/74</guid>
      <comments>https://ctoahn.tistory.com/74#entry74comment</comments>
      <pubDate>Tue, 28 Feb 2023 09:36:54 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Navigator와 Route - 1부 화면의 전환</title>
      <link>https://ctoahn.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 화면의 이동을 관리하는 위젯으로 Navigator가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter는 모든 것이 위젯이라는 말처럼 화면 전환을 관리하는 것 역시 위젯으로 만들어져 있다. 이 말은 Navigator를 앱 전체 화면의 전환으로 사용할 수도 있고 화면의 일부 영역에 배치해서 그 부분에 대한 전환만 담당하게 할 수도 있다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 내에서 영역별 화면의 전환을 위해 다양하게 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 MaterialApp을 가장 최상위 위젯으로 구성하게 되는데, 이때 MaterialApp은 내부적으로 하나의 전체 화면을 사용하는 Navigator를 하나 가지고 있다.&amp;nbsp; 이를 통해 앱의 전체 화면의 전환을 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 Navigator를 가져올 때 Navigator.of(context)로 가져와 사용하게 되는데, 이 정보는 위젯 트리에서 현재 context의 조상 중에서 가장 가까이에 있는 Navigator를 찾아서 반환하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Navigator.of(context, rootNavigator: true)로 지정하게 되면 가장 가까운 Navigator를 가져오는 것이 아닌 가장 최상의 Navigator를 가져오게 되는데, 보통은 MaterialApp에서 정의된 Navigator를 가져오게 된다. 이처럼 Navigator는 위젯으로 위젯 트리(정확히는 ElementTree)의 노드로써 계층 구조를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter의 위젯 트리에 Navigator가 여러 개 있을 수 있다는 점은 앱을 개발할 때 매우 중요한데, 개발자가 원하는 구조대로 유연하게 화면의 전환을 관리할 수 있다는 장점이 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 자칫 잘못 개발하면 타겟이 되는 Navigator를 엉뚱한 것으로 골라 오류가 발생하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 rootNavigator의 개념은 매우 중요한데, Flutter에서 기본적으로 제공하는 많은 기본 함수에서 rootNavigator를 타겟할 것인지 파라미터로 받는 것들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 직접 Navigator를 가져오는 함수에서 가장 가까운 Navigator를 가져올지 rootNavigator를 가져올지 선택할 수 있다. 다이얼로그를 표시하는 기본 함수에서는 useRootNavigator 파라미터를 이용해서 rootNavigator에 다이얼로그를 표시할지 가까운 Navigator에 다이얼로그를 표시할지 결정할 수 있다(기본값은 true이다).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Navigator를 잘못 타겟하게 될 때 발생할 수 있는 상황을 하나 예로 들어 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위에서&lt;/span&gt; &lt;span&gt;보여준&lt;/span&gt; Navigator.of&lt;span&gt;와&lt;/span&gt; showDialog &lt;span&gt;함수를&lt;/span&gt; &lt;span&gt;사용한&lt;/span&gt; &lt;span&gt;예이다&lt;/span&gt;.&lt;/p&gt;
&lt;pre id=&quot;code_1680495558316&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: Text('Information'),
        content: Text('Content'),
        actions: [
          TextButton(
            onPressed: () =&amp;gt; Navigator.of(context).pop(),
            child: Text('OK'),
          )
        ],
      );
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 'Flutter에서 dialog 표시하기'와 같은 것으로 검색을 하면 나올만한 코드일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적인 코드처럼 보이지만, 위 코드는 현재 context의 부모로 Navigator가 몇 개 있느냐에 따라 정상적으로 동작할 수도 있고, 잘못 동작할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Navigator가 rootNavigator 하나만 있는 경우에는 정상적으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Navigator가 두 개 있는 상황이라면 잘못된 형태로 동작하게 된다. 그 이유는 showDialog에서 사용한 navigator는 rootNavigator이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OK 버튼에서 사용한 navigator은 rootNavigator가 아닌 가장 가까운 Navigator이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 정상적으로 동작하기 위해서는 showDialog에서 useRootNavigator를 false로 주던지, Navigator.of에서 rootNavigator를 true로 주던지 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;보통&lt;/span&gt; &lt;span&gt;다이얼로그를&lt;/span&gt; &lt;span&gt;표시할&lt;/span&gt; &lt;span&gt;때&lt;/span&gt; rootNavigator&lt;span&gt;에&lt;/span&gt; &lt;span&gt;표시하는&lt;/span&gt; &lt;span&gt;경우가&lt;/span&gt; &lt;span&gt;많으므로&lt;/span&gt;, &lt;span&gt;아래와&lt;/span&gt; &lt;span&gt;같이&lt;/span&gt; Navigator.of(context, rootNavigator: true).pop() &lt;span&gt;이렇게&lt;/span&gt; &lt;span&gt;수정하자&lt;/span&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1680495580656&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: Text('Information'),
        content: Text('Content'),
        actions: [
          TextButton(
            onPressed: () =&amp;gt; Navigator.of(context, rootNavigator: true).pop(),
            child: Text('OK'),
          )
        ],
      );
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 앱의 경우 rootNavigator만 가지고 사용할 수 있을지 모르지만, 앱이 복잡해질수록 Navigator를 여러 개 두고 사용하는 경우가 많다. 이럴&lt;span&gt; &lt;/span&gt;때&lt;span&gt; &lt;/span&gt;위와&lt;span&gt; &lt;/span&gt;같은&lt;span&gt; &lt;/span&gt;상황이&lt;span&gt; &lt;/span&gt;발생한다면&lt;span&gt; &lt;/span&gt;잘못된&lt;span&gt; &lt;/span&gt;동작을&lt;span&gt; &lt;/span&gt;하게&lt;span&gt; &lt;/span&gt;됨으로&lt;span&gt; &lt;/span&gt;유의해서&lt;span&gt; &lt;/span&gt;사용해야&lt;span&gt; &lt;/span&gt;한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Multiple Navigator&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Navigator&lt;/span&gt;는&lt;span&gt; &lt;/span&gt;위젯이다&lt;span&gt;. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서&lt;span&gt; &lt;/span&gt;화면에서&lt;span&gt; &lt;/span&gt;유연하게&lt;span&gt; &lt;/span&gt;구성할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있는데&lt;span&gt;, &lt;/span&gt;다음과&lt;span&gt; &lt;/span&gt;같이&lt;span&gt; &lt;/span&gt;하나의&lt;span&gt; &lt;/span&gt;화면을&lt;span&gt; &lt;/span&gt;왼쪽과&lt;span&gt; &lt;/span&gt;오른쪽으로&lt;span&gt; &lt;/span&gt;나누어서&lt;span&gt; &lt;/span&gt;구성할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;(&lt;/span&gt;테블릿&lt;span&gt; &lt;/span&gt;화면에서&lt;span&gt; &lt;/span&gt;왼쪽에서는&lt;span&gt; &lt;/span&gt;목록이&lt;span&gt; &lt;/span&gt;표시되고&lt;span&gt;, &lt;/span&gt;오른쪽에는&lt;span&gt; &lt;/span&gt;상세&lt;span&gt; &lt;/span&gt;정보가&lt;span&gt; &lt;/span&gt;표시되는&lt;span&gt; &lt;/span&gt;것을&lt;span&gt; &lt;/span&gt;상상해&lt;span&gt; &lt;/span&gt;보자&lt;span&gt;.)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1680495635172&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final leftNavigatorKey = GlobalKey&amp;lt;NavigatorState&amp;gt;();
final rightNavigatorKey = GlobalKey&amp;lt;NavigatorState&amp;gt;();

Row(children: [
  Expanded(child: Navigator(key: leftNavigatorKey)),
  Expanded(child: Navigator(key: rightNavigatorKey)),
]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 LeftNavigator와 RightNavigator로 나누어 화면을 구성하고 필요에 따라 아래와 같이 key&lt;span&gt;를&lt;/span&gt; &lt;span&gt;이용해서&lt;/span&gt; Navigator&lt;span&gt;를&lt;/span&gt; &lt;span&gt;가져와&lt;/span&gt; push / pop&lt;span&gt;을&lt;/span&gt; &lt;span&gt;한다&lt;/span&gt;.&lt;/p&gt;
&lt;pre id=&quot;code_1680495666882&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; rightNavigatorKey.currentState?.push();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런&lt;span&gt; &lt;/span&gt;방법을&lt;span&gt; &lt;/span&gt;적절히&lt;span&gt; &lt;/span&gt;잘&lt;span&gt; &lt;/span&gt;사용하면&lt;span&gt; &lt;/span&gt;하나의&lt;span&gt; &lt;/span&gt;앱으로&lt;span&gt; &lt;/span&gt;스마트폰용과&lt;span&gt; &lt;/span&gt;테블릿용을&lt;span&gt; &lt;/span&gt;유연하게&lt;span&gt; &lt;/span&gt;구성할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Navigator&lt;span&gt;와&lt;/span&gt; Route&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Navigator는 내부적으로 Stack으로 화면을 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 push / pop과 같은 단순한 함수를 제공하는 이것들을 이용해서 화면에 필요한 위젯을 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Navigator에 push / pop하는 정보는 Widget을 직접 넣는 것이 아니라 이동할 화면에 대한 설정 정보를 담는 Route이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; . push: route를 스택의 맨 위에 추가하고, route에 설정된 build로 widget을 가져와 화면에 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;. pop: 스택의 맨 위에 있는 route를 제거하고, 이전 화면이 가장 최상위에 표시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하게 정리하자면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 1. Route에는 화면에 대한 설정 정보(표시할 위젯 / 전환 효과)가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2. Navigator에 route를 push / pop해서 표시할 화면 정보를 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 3. Navigator는 stack의 가장 위에 있는 route 정보를 이용해 화면에 Widget를 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Navigator&lt;span&gt;에서&lt;/span&gt; Route&lt;span&gt;를&lt;/span&gt; push&lt;span&gt;하는&lt;/span&gt; &lt;span&gt;방법은&lt;/span&gt; &lt;span&gt;아래와&lt;/span&gt; &lt;span&gt;같다&lt;/span&gt;.&lt;/p&gt;
&lt;pre id=&quot;code_1680495955286&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final route = MaterialPageRoute(builder: (context) =&amp;gt; SecondScreen());
Navigator.of(context).push(route);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;보통 처음 Flutter을 접하게 되면 Route의 존재를 잘 모르고 개발하는 경우를 보게 되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그&lt;span&gt; &lt;/span&gt;이유는&lt;span&gt; Navigator&lt;/span&gt;에서&lt;span&gt; pushNamed&lt;/span&gt;와&lt;span&gt; &lt;/span&gt;같이&lt;span&gt; route&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;직접&lt;span&gt; &lt;/span&gt;생성해서&lt;span&gt; &lt;/span&gt;전달하지&lt;span&gt; &lt;/span&gt;않고&lt;span&gt; &lt;/span&gt;문자열로&lt;span&gt; &lt;/span&gt;정의된&lt;span&gt; &lt;/span&gt;값을&lt;span&gt; &lt;/span&gt;사용하는&lt;span&gt; &lt;/span&gt;경우가&lt;span&gt; &lt;/span&gt;예제로&lt;span&gt; &lt;/span&gt;많이&lt;span&gt; &lt;/span&gt;보기&lt;span&gt; &lt;/span&gt;때문이다&lt;span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;아래와&lt;span&gt; &lt;/span&gt;같이&lt;span&gt; MaterialApp&lt;/span&gt;에&lt;span&gt; routes&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;설정하고&lt;span&gt; &lt;/span&gt;이름을&lt;span&gt; &lt;/span&gt;지정한&lt;span&gt; &lt;/span&gt;후&lt;span&gt; &lt;/span&gt;원하는&lt;span&gt; &lt;/span&gt;위젯을&lt;span&gt; &lt;/span&gt;반환하도록&lt;span&gt; &lt;/span&gt;하면&lt;span&gt; &lt;/span&gt;쉽게&lt;span&gt; &lt;/span&gt;사용할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1680496003051&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; MaterialApp(
  routes: {
    '/': (context) =&amp;gt; HomeScreen(),
    '/second': (context) =&amp;gt; SecondScreen(),
  })
&amp;hellip;..
Navigator.of(context).pushNamed('/second');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간편하게 사용하는 방법으로는 위 방법도 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Route에는 우리가 생각하는 것보다 조금 더 많은 정보를 설정할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을&lt;span&gt; &lt;/span&gt;조금&lt;span&gt; &lt;/span&gt;더&lt;span&gt; &lt;/span&gt;잘&lt;span&gt; &lt;/span&gt;다루기&lt;span&gt; &lt;/span&gt;위해서&lt;span&gt; &lt;/span&gt;필요한&lt;span&gt; &lt;/span&gt;이벤트들이&lt;span&gt; &lt;/span&gt;있음으로&lt;span&gt; route&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;대해서&lt;span&gt; &lt;/span&gt;조금&lt;span&gt; &lt;/span&gt;더&lt;span&gt; &lt;/span&gt;상세하게&lt;span&gt; &lt;/span&gt;알고&lt;span&gt; &lt;/span&gt;넘어&lt;span&gt; &lt;/span&gt;갈&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있으면&lt;span&gt; &lt;/span&gt;좋을&lt;span&gt; &lt;/span&gt;것&lt;span&gt; &lt;/span&gt;같다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Navigator와 Route의 기본 개념과 사용법은 간단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 실무에서는 화면 전환에 대한 다양한 요구 사항을 마주하게 되고, 때로는 단순한 push와 pop만으로 해결하기 어려운 상황에 직면하게 됩니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Push와 pop을 통한 Route의 상태 변화를 알아야 하는 경우, 화면의 정보를 이전 화면으로 전달하거나 한 번에 여러 화면을 pop해야 하는 경우 등이 이에 해당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 상황에서는 Navigator와 Route의 동작 원리를 좀 더 깊이 이해할 필요가 있습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 앱에서 Navigator가 오직 하나만 존재하는 것이 아니라 개발자의 요구에 따라 여러 개를 구성하여 사용할 수 있는 유연한 Widget이라는 점을 명심하면 좋습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를&lt;span&gt; &lt;/span&gt;통해&lt;span&gt; &lt;/span&gt;사용자&lt;span&gt; &lt;/span&gt;경험을&lt;span&gt; &lt;/span&gt;개선하고&lt;span&gt;, &lt;/span&gt;애플리케이션의&lt;span&gt; &lt;/span&gt;구조와&lt;span&gt; &lt;/span&gt;관리를&lt;span&gt; &lt;/span&gt;보다&lt;span&gt; &lt;/span&gt;효율적으로&lt;span&gt; &lt;/span&gt;할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;Navigator&lt;span&gt;과&lt;/span&gt; Route - 2&lt;span&gt;부&lt;/span&gt; Route &lt;span&gt;상태&lt;/span&gt; &lt;span&gt;이벤트의&lt;/span&gt; &lt;span&gt;활용&lt;/span&gt;&amp;rdquo; &lt;span&gt;에서&lt;/span&gt; &lt;span&gt;계속&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>Flutter</category>
      <category>navigator</category>
      <category>Route</category>
      <author>윈드.</author>
      <guid isPermaLink="true">https://ctoahn.tistory.com/73</guid>
      <comments>https://ctoahn.tistory.com/73#entry73comment</comments>
      <pubDate>Fri, 17 Feb 2023 09:23:40 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] TDD로 가는 길 - 2부 TDD 가능한 구조로 설계하기</title>
      <link>https://ctoahn.tistory.com/37</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;TDD을 적용하기 위해서 가장 필수적인 부분은 테스트가 용이한 구조로 설계되어야 한다는 것이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 구조적으로 만들려면 아키텍처의 각 구성요소들의 역할이 정확하게 정의되야 하고, 그들간의 의존관계가 명확해야 한다. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 부터 어떻게 하면 앱을 테스트하기 좋은 구조 만들 수 있는지 StatefulWidget으로 만들어진 간단한 화면을 예로 설명해보도록 하겠다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 화면 하나를 만들어 보자 .&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;로그인 화면이 있고 이 화면에서는 유저로 부터 Id와 password를 입력받아 서버로 인증요청을 보내고 그 결과를 화면에 표시 한다.&lt;br /&gt;- ID는 이메일 형식만 허용한다&lt;br /&gt;- PW는 최소 5자 이상이다.&lt;br /&gt;- 모든 입력이 정상적으로 완료되면 로그인 버튼이 활성화 된다&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;아직 까지 우리는 구조적으로 분리하지 않았기 때문에 모든 기능을 StatefulWidget에 추가하였고&amp;nbsp; 아래와 같은 코드로 개발하였다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1679462458842&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State&amp;lt;StatefulWidget&amp;gt; createState() =&amp;gt; _LoginScreenState();
}

class _LoginScreenState extends State&amp;lt;LoginScreen&amp;gt; {
  String _loginId = '';
  String _loginPw = '';
  bool get _enableLoginButton =&amp;gt; _loginId.isNotEmpty &amp;amp;&amp;amp; _loginPw.isNotEmpty;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          TextFormField(
              initialValue: _loginId,
              onChanged: (value) {
                if (_isValidUserId(value)) setState(() =&amp;gt; _loginId = value);
              }),
          TextFormField(
              initialValue: _loginPw,
              onChanged: (value) {
                if (_isValidUserPw(value)) setState(() =&amp;gt; _loginPw = value);
              }),
          TextButton(onPressed: _enableLoginButton ? () =&amp;gt; _login() : null, child: const Text('Login'))
        ],
      ),
    );
  }

  void _login()  =&amp;gt;  throw UnimplementedError();
  bool _isValidUserId(String value) =&amp;gt; true;
  bool _isValidUserPw(String value) =&amp;gt; value.length &amp;gt;= 5;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 테스트하기 위해서 우리는 어떻게 테스트 코드를 작성해야 할까?&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 위에서 정의한&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;인풋값(id , pw)의 유효성을 검사하고 로그인 버튼의 활성화 여부를 결정하는 기능의 테스트를 작성해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1679464023922&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;testWidgets(&quot;Should enable the login button only...&quot;,
        (WidgetTester tester) async {
      const myWidget = MaterialApp(home: LoginScreen());
      await tester.pumpWidget(myWidget);
      var loginButton = tester.widget&amp;lt;TextButton&amp;gt;(find.byType(TextButton));
      expect(loginButton.enabled, false);

      final inputFinder = find.byType(TextFormField);
      await tester.enterText(inputFinder.first, 'user01@example.com');
      await tester.enterText(inputFinder.last, 'pw');

      await tester.pump();
      loginButton = tester.widget&amp;lt;TextButton&amp;gt;(find.byType(TextButton));
      expect(loginButton.enabled, false);

      await tester.enterText(inputFinder.last, 'pw0001');
      await tester.pump();
      loginButton = tester.widget&amp;lt;TextButton&amp;gt;(find.byType(TextButton));
      expect(loginButton.enabled, true);
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼가 그럴듯한 테스트 코드가 작성된 것처럼 보인다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 로그인 버튼의 활성화상태 여부와 loginId와 loginPw를 테스트하기 위해 그것와 연결된 UI를 모두 만들어야 하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드에서는 화면의 상태를 확인하기 위해&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;위젯타입으로 위젯을 찾은 후 그 위젯을 통해 직접 상태를 확인해야 한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 보여주는 것처럼 간단한 예제일 때는 상관이 없을 수 있지만 인풋으로 사용하는 위젯은 TextFormField일수도 있지만 다른 커스텀 위젯으로 변경될 수도 있고, 활성화해야 하는 버튼이 하나가 아닌 2개 일수도 있다.&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;만약 이미 만들어진 화면을 테스트하는 것이 아니라 TDD로 테스트 코드를 먼저 작성해야 한다면 어떤 위젯을 사용할지 test code에서 미리 결정해야 하기 때문에&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;test code를 작성하기 더욱 어려울 것이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트하기 쉬운 구조로 리팩토링&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 화면의 상태를 따로 관리하도록 View와 ViewModel로 분리해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;- View에서는 오직 화면을 구성하고 정보를 표시하며, 필요한 상태 정보는 ViewModel로부터 가져온다.&lt;br /&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;- ViewModel에서는 View의 상태를 저장하고 로직을 처리한 후 상태를 변경한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;LoginViewModel과 테스트 코드&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1679729752264&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//  LoginViewModel
class LoginViewModel with ChangeNotifier {
  String _loginId = '';
  String get loginId =&amp;gt; _loginId;
  set loginId(String value) {
    if (_isValidUserId(value)) {
      _loginId = value;
      notifyListeners();
    }
  }
  String _loginPw = '';
  String get loginPw =&amp;gt; _loginPw;
  set loginPw(String value) {
    if (_isValidUserPw(value)) {
      _loginPw = value;
      notifyListeners();
    }
  }

  bool get enableLoginButton =&amp;gt; _loginId.isNotEmpty &amp;amp;&amp;amp; _loginPw.isNotEmpty;
  void login() =&amp;gt; throw UnimplementedError();
  bool _isValidUserId(String value) =&amp;gt; true;
  bool _isValidUserPw(String value) =&amp;gt; value.length &amp;gt;= 5;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 코드인 LoginScreen에서 정보를 표시하는 부분을 제외한 나머지 로직만 따로 분리한 ViewModel을 만들어 보았다.&lt;br /&gt;가능하면 StatefulWidget으로만 만들었던 코드와 비교해보기 바란다. 단순히&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;변수와 함수를 분리시킨 것처럼 보일지 모르겠지만 이런 분리는 개발과 테스트에 큰 이점을 준다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 로직을 분리하였으니 이 부분만 따로 테스트를 해보자.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StatefullWidget로 만든 코드를 테스트했던 것과 동일한&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;인풋값에 대한 유효성과 로그인 버튼의 활성화 여부 대해서 테스트를 작성해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1679729955151&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   test('&quot;enabledLoginButton&quot; must be true only ....', ()  {
      final loginViewModel = LoginViewModel();
      expect(loginViewModel.enableLoginButton, false);
      loginViewModel.loginId = 'user01@example.com';
      loginViewModel.loginPw = 'pw';
      expect(loginViewModel.enableLoginButton, false);
      loginViewModel.loginPw = 'pw0001';
      expect(loginViewModel.enableLoginButton, true);
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 테스트 코드에 비해 좀 더 명확하고 간결해진 것을 알 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI와 UI로직을 분리하면 각자의 역할이 명확해지기 때문에 의미를 파악하기 좋은 코드가 되고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드 역시 좀 더 명확하게 작성할 수 있다.&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;(특히 위젯에 대한 의존 없이 기능을 테스트할 수 있다는 것에 주목하자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;LoginView와 테스트 코드&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 로직을 분리시키고 남은 View부분을 다음과 같이 작성해 보자.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1679730027960&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginView extends StatelessWidget {
  final LoginViewModel viewModel;
  const LoginView({super.key, required this.viewModel});
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: viewModel,
      builder: (BuildContext context, Widget? child) {
        return Scaffold(
          body: Column(
            children: [
              TextFormField(
                initialValue: viewModel.loginId,
                onChanged: (value) =&amp;gt; viewModel.loginId = value,
              ),
              TextFormField(
                initialValue: viewModel.loginPw,
                onChanged: (value) =&amp;gt; viewModel.loginPw = value,
              ),
              TextButton(
                  onPressed: viewModel.enableLoginButton ? () =&amp;gt; viewModel.login() : null,
                  child: const Text('Login'))
            ],
          ),
        );
      },
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 Statefulwidget으로 작성되었던 부분이 StatelesWidget으로 변경되었고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewModel에 저장된 상태를 화면에 표시하는 것에 중점을 두고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 코드에 비해서 코드가 깔끔해지고 의미가 명확해 짐을 알 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;AnimatedBuilder를 이용해서 viewModel의 변화를 감지 한 후 그 하위 builder 부분이 다시 생성되는 코드를&amp;nbsp;&lt;br /&gt;Provider나 Reverpod, Bloc, Cubit, GetX와 같은 상태 관리 라이브러리를 활용하는 것으로 변경할 수 있도 있다.&amp;nbsp;&lt;br /&gt;하지만 특정 상태 관리 라이브러리에 의존적인 코드를 배제하고 현재 설명하고 하고자 하는 의미를 명확하게 하기 위해서 Flutter에서 기본으로 제공하는 AnimatedBuilder을 사용했다.&amp;nbsp;&lt;br /&gt;(물론 별도의 상태관리 라이브러리 없이 AnimatedBuilder을 활용해서 앱을 만드는것도 가능하다.)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 코드에서 눈여겨 볼만한 부분은 ViewModel을 생성자를 통해서 외부에서부터 주입받는 부분이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 ViewModel을 내부에서 생성하지 않고 외부에서 주입받아 사용하게 되면 ViewModel를 추상화한 후 Mock객체를 이용해서 View을 테스트하는 것이 가능하다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 View는 ViewModel에 직접 의존 없이 자체 적으로 테스트도 가능한다. &lt;br /&gt;(예를 들면 특정 상태로 초기화된 MockViewModel을 주입한 후 화면을 원하는 모습대로 정상적으로 표시 가능한지 확인할 수 있다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드와 같이&amp;nbsp; MockViewModel를 주입한 후 View만 따로 테스트 코드를 작성할 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1679730260597&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; testWidgets('Should enable the login button when viewmodel.enabledLoginButton is true',
        (WidgetTester tester) async {
      final mockViewModel = MockLoginViewModel();
      when(mockViewModel.enableLoginButton).thenReturn(true);
      when(mockViewModel.loginId).thenReturn('user0001');
      when(mockViewModel.loginPw).thenReturn('pw0001');
      var myWidget = MaterialApp(home: LoginView(viewModel: mockViewModel));
      await tester.pumpWidget(myWidget);
      var loginButton = tester.widget&amp;lt;TextButton&amp;gt;(find.byType(TextButton));
      expect(loginButton.enabled, true);
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MockViewModel를 만들어서 실제 구현된 ViewModel과 상관없이 View에 대한 테스트가 가능하다는 것에 주목하자.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LoginViewModel를 추상화한 후 MockViewModel를 만들어서 실제 구현된 구현체와 상관없이 View를 테스트하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 가능한 최종 코드&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 UI의 상태와 로직이 추상화된 LoginViewModel,&amp;nbsp; LoginViewModel의 구현체 LoginViewModelImpl, UI를 표시하는 LoginView가 완성되었고 코드는 아래와 같다&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1679730492490&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// LoginViewModel
abstract class LoginViewModel implements Listenable {
  String get loginId;
  set loginId(String value);
  String get loginPw;
  set loginPw(String value);
  bool get enableLoginButton;
  void login();
}

/// LoginViewModelImpl
class LoginViewModelImpl with ChangeNotifier implements LoginViewModel {
  String _loginId = '';
  @override
  String get loginId =&amp;gt; _loginId;
  @override
  set loginId(String value) {
    if (_isValidUserId(value)) {
      _loginId = value;
      notifyListeners();
    }
  }

  String _loginPw = '';
  @override
  String get loginPw =&amp;gt; _loginPw;
  @override
  set loginPw(String value) {
    if (_isValidUserPw(value)) {
      _loginPw = value;
      notifyListeners();
    }
  }

  @override
  bool get enableLoginButton =&amp;gt; _loginId.isNotEmpty &amp;amp;&amp;amp; _loginPw.isNotEmpty;

  @override
  void login() =&amp;gt; throw UnimplementedError();

  bool _isValidUserId(String value) =&amp;gt; true;
  bool _isValidUserPw(String value) =&amp;gt; value.length &amp;gt;= 5;
}

/// LoginView
class LoginView extends StatelessWidget {
  final LoginViewModel viewModel;
  const LoginView({super.key, required this.viewModel});
  
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: viewModel,
      builder: (BuildContext context, Widget? child) {
        return Scaffold(
          body: Column(
            children: [
              TextFormField(
                initialValue: viewModel.loginId,
                onChanged: (value) =&amp;gt; viewModel.loginId = value,
              ),
              TextFormField(
                initialValue: viewModel.loginPw,
                onChanged: (value) =&amp;gt; viewModel.loginPw = value,
              ),
              TextButton(
                  onPressed: viewModel.enableLoginButton ? () =&amp;gt; viewModel.login() : null,
                  child: const Text('Login'))
            ],
          ),
        );
      },
    );
  }
}

/// ViewModel 테스트 
test('&quot;enabledLoginButton&quot; must be true only ....', ()  {
  final loginViewModel = LoginViewModelImpl();
  expect(loginViewModel.enableLoginButton, false);
  loginViewModel.loginId = 'user01@example.com';
  loginViewModel.loginPw = 'pw';
  expect(loginViewModel.enableLoginButton, false);
  loginViewModel.loginPw = 'pw0001';
  expect(loginViewModel.enableLoginButton, true);
});

/// View Widget test
testWidgets('Should enable the login button when viewmodel.enabledLoginButton is true',
      (WidgetTester tester) async {
  final mockViewModel = MockLoginViewModel();
  when(mockViewModel.enableLoginButton).thenReturn(true);
  when(mockViewModel.loginId).thenReturn('user0001');
  when(mockViewModel.loginPw).thenReturn('pw0001');
  var myWidget = MaterialApp(home: LoginView(viewModel: mockViewModel));
  await tester.pumpWidget(myWidget);
  var loginButton = tester.widget&amp;lt;TextButton&amp;gt;(find.byType(TextButton));
  expect(loginButton.enabled, true);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 StatefulWidget으로 작성한 코드 보다 조금 더 길어진 것처럼 보이지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드의 의미는 더 명확해졌고 테스트하기 좋은 코드가 되었다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 ViewModel의 추상화를 통해 저주순 구현체의 구현과 상관없이 View 분리해서 테스트할 수 있으며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewModel 역시 View와 관계없이 테스트하는 가능해졌다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 많은 개발자들이 무의식 중에 혹은 이미 짜인 코드를 반복하면서 위에서 보여준 예시와 같은 구조로 개발을 하고 있을 것이라 생각합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트하기 좋은 코드를 만들기 위해서는 각 레이어 혹은 모듈의 역할을 명확하게 해야 하고&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;추상화 한&lt;/span&gt; 후 결합도를 낮춰서 의존하는 구현체의 구현과 상관없이 테스트가 가능해야 해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 UI를 포함하는 코드의 경우 테스트 하기 까다롭기 때문에 가능하면 정보를 표시하는 부분과 로직을 명확하게 구분할 필요가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서는 View와 ViewModel를 분리하고 구조화하는 것에 대한 예시를 보여 주었지만 역할을 분리시키고 추상화를 하는 과정을 통해 ViewModel의 기능들을 다른 Layer (DomainLayer나 DataLayer)로 분리시킬 수 있습니다.&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>Flutter</category>
      <category>Flutter TDD</category>
      <author>윈드.</author>
      <guid isPermaLink="true">https://ctoahn.tistory.com/37</guid>
      <comments>https://ctoahn.tistory.com/37#entry37comment</comments>
      <pubDate>Tue, 1 Nov 2022 12:22:59 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] TDD로 가는 길 - 1부 앱 개발에 TDD를 적용해야 하는 이유</title>
      <link>https://ctoahn.tistory.com/36</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;내가 작성한 소스코드를 신뢰할 수 있을까?&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 동료가 작성한 소스코드를 신뢰할 수 있을까?&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 직접 작성한 모든 소스코드는&lt;span&gt;&amp;nbsp; &lt;/span&gt;결함을 가질 수 밖에 없다. &lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리가 작성한&lt;span&gt;&amp;nbsp; &lt;/span&gt;코드가 올바르게 동작함을 증명할 필요가 있다. &lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 개발 일정에 쫓기다보면 테스트 코드를 작성하지 않고 기능을 만들기에 급급할 수도 있다. &lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그렇게 급하게 만든면 완성도가 떨어지고 결함을 가진 프로그램이 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 오류를 찾느라 더 오랜 시간을 허비하기도 한다. &lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드를 작성하는 것은 다음과 같은 이유로 여러움을 겪는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 개발 시간의 증가&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음 테스트코드를 작성해 보면 시간이 더 많이 필요한 것이 사실이다.&lt;br /&gt;실제로도 개발 로직에 작성된 코드 수보다 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;테스트코드를 더 많이 작성해야 하는 경우가 많이 발생한다. &lt;br /&gt;&lt;/span&gt;게다가 테스트 코드 작성이 익숙하지 않다면 더 많은 시간이 필요할 것이다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;테스트코드 없이 개발을 하게 되면 단위별로 개발한 기능을 전체 UI까지 완성하고 나서야 정상 동작을 확인 할 수 있다.&amp;nbsp;&lt;br /&gt;&lt;/span&gt;그러다 보면 작은 단위로&amp;nbsp; 완성도 있게 개발하지 못하고&amp;nbsp; 전체 기능을 개발한 후에나 오류를 확인하고 수정해야 하기&amp;nbsp; 때문에 프로젝트 전체로 테스트 코드를 작성하면서 개발하는 것보다 더 많은 시간이 필요할지도 모른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 테스트코드 관리(업데이트)의 어려움&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발 소스가 수정되면 테스트 코드도 함께 수정해야 한다.&amp;nbsp;&lt;br /&gt;당연한 이야기처럼 들리겠지만 이것 때문에 테스트 코드가 유지되지 못하고 망가지는 경우를 많이 보았다.&amp;nbsp;&lt;br /&gt;예를 들어 클래스 생성자에 하나의 초기 parameter가 추가되었다고 가정해 보자.&amp;nbsp; 이때 관련된 소스만 수정하는 것이 아니라 이 클래스를 사용하는 테스트 코드와 Mock오브젝트 까지 수정해야 한다. 사실 이 정도 수정이야 별거 없어 보이지만 수정이 많아지면 많아질수록 특별히 로직이 변경된 것도 아닌데 수정해야 하는 코드가 기하급수적으로 늘어나는 것을 볼 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt; 테스트코드를 실제개발 소스만큼 지속적으로 잘 관리해야 한다. &lt;br /&gt;관리해야 하는 코드가 개발소스뿐만 아니라 테스트 코드까지 확장되기 때문에 실제로 신경 써야 할 것이 더 많은 것이 사실이다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. UI 테스트의 어려움&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특히 모바일 앱과 같이 UI를 포함하는 프로젝트의 경우 UI관련된 동작이나 표시여부등을 확인하는 것이 쉽지 않다.&amp;nbsp;&lt;br /&gt;(이것을 할 수 있는 기능을 제공하고 있지만 일반적인 로직만 테스트하는 것에 비해서 해야 하는 일도 많고, 특정 Widget의 찾고 가져오는 방법도 명확하지 않은 경우가 많다. - Widet의 위치가 부모/자식 관계가 조금만 바뀌어도 찾지 못하는 경우도 생긴다.)&lt;/li&gt;
&lt;li&gt;UI는 개발 중에도 수시로 변경되는 경우가 많기 때문에 더욱 테스트하기가 쉽지 않고, 이미 작성한 테스트 코드의 수정도 빈번하게 발생한다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;화면의 표시되는 정보와&amp;nbsp; 화면과 관련된 일련의 로직이 잘 분리되게 설계되지 않은 경우 테스트가 어렵다.&amp;nbsp;&lt;br /&gt;이런 경우에는 UI의 로직 부분을 잘 분리해서 따로 테스트하는 것이 바람직하다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;그래서 UI에서&amp;nbsp; View(화면에 정보를 표시)와 ViewModel(화면의 정보를 표시하는 상태와 로직)의 역할을 명확하게 분리하는 것이 가장 중요하다.&lt;br /&gt;( View부분은 오직 정보의 표시하는 것에 중점을 두고, ViewModel에서 로직의 처리와 데이터의 관리를 담당한하면 View의 테스트 없이 ViewModel의 테스트 만으로도 충분할 수 있다고 생각한다. )&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 조직(팀) 내 동료들의 이해와 참여부족&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 개발자가 참여하는 프로젝트에서는&amp;nbsp;함께 참여하는 동료들 간의 소통과 통일된 구조에 대한 이해와 약속이 무엇보다 중요하다.&amp;nbsp; &lt;br /&gt;그래서 프로젝트를 시작하기 전에는 코드컨벤션(Code Convention)을 확정하고 시작하곤 한다.&lt;/li&gt;
&lt;li&gt;테스트 역시 마찬가지이다. 테스트 코드에 대한 구조와 테스트 방법에 대해 정확한 규칙을 정하는 것이 필요하다.&lt;br /&gt;특히 테스트 코드 작성이 익숙하지 않은 개발자가 있을 때는 팀에서 정한 규칙에 따라 테스트코드를 작성하는 방법을 익힐 필요가 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;그리고 참여하는 모든 개발자가 빠짐없이 자신의 테스트를 잘 작성해야 테스트가 쓸모 있는 것이 된다. 만약 한 사람이라도 빼먹게 된다면 점점 더 테스트 코드를 작성하지 않게 되고, 결국 그전에 작성한 테스트코드들도 쓸모없는 것이 되기도 한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;모든 프로젝트가 마찬가지겠지만 테스트도 개발자들의 일정 수준 이상의 이해와 참여가 꼭 필요하다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 위와 같은 어려움 때문에 테스트 코드를 작성을 생략하고 개발을 하거나 처음에는 테스코드를 작성하다 나중에는&amp;nbsp;포기하고 형식적인 형태의 테스트 코드만을 남기는 경우를 많이 보게 된다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 잘 작성된 테스트 코드가 줄 수 있는 장점은 분명히 있다. &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 코드 품질의 향상&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자가 코드를&amp;nbsp; 더 깊이 이해하고, 더 나은 코드를 작성할 수 있도록 도와줄 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;테스트하기 쉬운 구조로 더욱 간결하고 명확한 개발을 하게 되기 때문이다.&lt;br /&gt;그리고 개발(테스트)하고자 하는 스펙을 명확히 이해하게 된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 버그의 감소 &amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 코드를 작성하면 코드가 예상대로 작동하는지 미리 확인할 수 있다.&amp;nbsp; 단위별 테스트가 가능하기 때문에 다른 의존하는 기능들의 구현에 의지하지 않고 테스트가 가능하기 때문에 단일 동작에 대한 버그를 확인하고 수정할 수 있다.&lt;/li&gt;
&lt;li&gt;그리고 코드의 일부분을 변경하거나 수정할 때에도 더욱 안전하게 개발이 가능하다.&amp;nbsp;&lt;br /&gt;특히 Side effect가 발생하는 것을 미리 확인 할 수 있기 때문에 더욱 안전한 서비스를 만들 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3.&amp;nbsp; 개발자와의 소통: 코드 기능에 대한 명확한 전달&amp;nbsp;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자들은 테스트 코드를 통해 코드의 기능 및 구조를 더 빠르게 파악할 수 있다.&lt;br /&gt;코드만 보는 것보다는 그것을 테스트한 과정을 보면 기능에 대한 더 명확히 이해할 수 있다.&lt;/li&gt;
&lt;li&gt;코드 리뷰에서도 테스트 코드가 있으면 코드에 대한 설명을 더욱 명확하게 할 수 있고 해당 코드를 다른 개발자가 확인하고 직접 테스트할 수 있어 이해를 높이는데 도움을 준다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;정말 잘 작성된 테스트 코드와 Description은&amp;nbsp; 코드의 스펙들을 명확하게 설명할 수 있기 때문에 스펙 문서처럼 기능을 확인하는데 도움을 준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;b&gt;4.&amp;nbsp; 구조의 명확성 강화&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 가능한 구조로 설계되고 테스트하기 좋은 형태로 기능을 분리해서 개발하게 된다.&lt;/li&gt;
&lt;li&gt;결합도가 높을수록 테스트하기가 어렵다.&amp;nbsp;&lt;br /&gt;결국 테스트하기 쉬운 형태를 만들다 보면 추상화게 되고, 결합도가 낮은 클래스들을 만들게 된다.&lt;/li&gt;
&lt;li&gt;결국 개발자는 단순한 기능의 개발을 넘어 프로젝트의 설계(아키텍처)까지 더욱 구조적이고 명확하게 만들게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 코드의 신뢰&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;본인이 작성한 코드가 아닌 이상 작성된 코드만 보면 이것이 정상동작하는지 알기가 쉽지 않다.&amp;nbsp; 코드를 하나씩 다 분석해 보지 않고서는 정상 동작의 유무를 알 수 없다. (본인이 작성한 코드도 나중에는 이해하기 못하는 경우도 많다)&lt;/li&gt;
&lt;li&gt;개발자가 작성한 코드의 동작을 신뢰 할 수 있다는 것을 테스트코드가 증명해 줄 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 장점들 때문에 많은 조직에서 테스트 코드의 작성을 권장하거나 강제하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 역시 현실은 그리 호락호락하지 않다. 여러 이유로 지속적인 테스트코드가 잘 관리되지 못하고 형식적인 테스트 코드만 남게된 프로젝트를 보게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 제대로 된 테스트 코드를 작성하며 프로젝트를 완성할 수 있는 방법은 무엇일까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로는 그 해결책이 될 수 있는 방법이 TDD가 아닐까 생각한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;TDD?&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 케이스를 먼저 작성하고, 그 테스트 케이스를 통과하는 코드를 작성하는 개발 방법론으로 다음과 같은 과정을 반복하며 기능과 테스트코드를 완성하는 개발 방법론이다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3508&quot; data-origin-height=&quot;2480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OfbNV/btr3lEfgvyt/goXzDoqSPc7y2GqwLKyBc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OfbNV/btr3lEfgvyt/goXzDoqSPc7y2GqwLKyBc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OfbNV/btr3lEfgvyt/goXzDoqSPc7y2GqwLKyBc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOfbNV%2Fbtr3lEfgvyt%2FgoXzDoqSPc7y2GqwLKyBc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;510&quot; height=&quot;361&quot; data-origin-width=&quot;3508&quot; data-origin-height=&quot;2480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 기능(스펙) 정의&lt;br /&gt;&amp;nbsp; &amp;nbsp;. 개발할 단위기능에 대한 스펙을 명확하게 정의하고, 스펙을 정의한 추상화된 클래스(인터페이스)나 함수를 선언한다.&lt;span&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 테스트 코드 작성&lt;br /&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;. 정의된 기능(스펙)을 검증할 테스트 코드를 작성한다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 테스트 실행 - 실패&lt;br /&gt;&amp;nbsp; &amp;nbsp;. 테스트 코드는 실패한다.&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;- (최초): 구현이 필요한 빈 구현체만 존재.&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;- (코드작성) : 코드가 올바르게 동작하지 않을 경우.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 코드 작성&lt;br /&gt;&amp;nbsp; &amp;nbsp; . 실패한 테스트 코드를 성공시킬 수 있는 코드를 작성한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 테스트 실행&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; . 테스트 코드를 반복수행하며 테스트에 성공하는 코드를 완성한다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 리팩토링&lt;br /&gt;&amp;nbsp; &amp;nbsp; . 작성한 코드를 리팩터링 하여 더 나은 코드로 개선한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD는 프로젝트를 진행하는 동안 위 과정을 반복하며 단위 기능을 만들어 가며 개발하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드를 먼저 작성한 후 기능을 구현해야 하기 때문에 테스트 코드의 작성이 강제된다.&amp;nbsp; 이런 이유로 팀에서 TDD를 도입하고 이 방법에 익숙해지게 되면 테스트 코드 작성을 지속하며 개발하는 것이 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%85%8C%EC%8A%A4%ED%8A%B8_%EC%A3%BC%EB%8F%84_%EA%B0%9C%EB%B0%9C&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ko.wikipedia.org/wiki/%ED%85%8C%EC%8A%A4%ED%8A%B8_%EC%A3%BC%EB%8F%84_%EA%B0%9C%EB%B0%9C&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1679037443519&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;테스트 주도 개발 - 위키백과, 우리 모두의 백과사전&quot; data-og-description=&quot;위키백과, 우리 모두의 백과사전. --&amp;gt;&quot; data-og-host=&quot;ko.wikipedia.org&quot; data-og-source-url=&quot;https://ko.wikipedia.org/wiki/%ED%85%8C%EC%8A%A4%ED%8A%B8_%EC%A3%BC%EB%8F%84_%EA%B0%9C%EB%B0%9C&quot; data-og-url=&quot;https://ko.wikipedia.org/wiki/%ED%85%8C%EC%8A%A4%ED%8A%B8_%EC%A3%BC%EB%8F%84_%EA%B0%9C%EB%B0%9C&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jnKPa/hyRXzonubA/Pnp1KzDUKnhoqLKFFkueik/img.png?width=1200&amp;amp;height=704&amp;amp;face=0_0_1200_704,https://scrap.kakaocdn.net/dn/GW9kz/hyRXqdTL6J/5qSNYPITg1KRMmfBorkLDK/img.png?width=800&amp;amp;height=470&amp;amp;face=0_0_800_470,https://scrap.kakaocdn.net/dn/uO5UR/hyRXqyb4P9/JVcGIkMCgkYEObJjyBSZqK/img.png?width=640&amp;amp;height=376&amp;amp;face=0_0_640_376&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%85%8C%EC%8A%A4%ED%8A%B8_%EC%A3%BC%EB%8F%84_%EA%B0%9C%EB%B0%9C&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ko.wikipedia.org/wiki/%ED%85%8C%EC%8A%A4%ED%8A%B8_%EC%A3%BC%EB%8F%84_%EA%B0%9C%EB%B0%9C&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jnKPa/hyRXzonubA/Pnp1KzDUKnhoqLKFFkueik/img.png?width=1200&amp;amp;height=704&amp;amp;face=0_0_1200_704,https://scrap.kakaocdn.net/dn/GW9kz/hyRXqdTL6J/5qSNYPITg1KRMmfBorkLDK/img.png?width=800&amp;amp;height=470&amp;amp;face=0_0_800_470,https://scrap.kakaocdn.net/dn/uO5UR/hyRXqyb4P9/JVcGIkMCgkYEObJjyBSZqK/img.png?width=640&amp;amp;height=376&amp;amp;face=0_0_640_376');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;테스트 주도 개발 - 위키백과, 우리 모두의 백과사전&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;위키백과, 우리 모두의 백과사전. --&amp;gt;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ko.wikipedia.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마지며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;작은 단위의 프로젝트나 간단한 기능은 테스트 코드 없이도 쉽게 개발할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이런 개발 습관을 지속하다 보면 크고 작은 개발의 실수들을 개발 중에 쉽게 찾아낼 수 없고&amp;nbsp; 화면을 보면서 오류를 찾아내거나 배포 후에나 알 수 있게 되는 경우가 많습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 개발자들에게 잘 작성된 테스트 코드를 지속적으로 만들어 내는 일은 쉽지 않은 일입니다. 테스트 작성에 익숙하지 않고 처음에는 시간도 많이 필요하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 우리가 개발자로서 앞으로도 오랜 시간일을 해야 하고 좀 더 완성도 높은 서비스와 코드들을 만들어 나가야 한다면 더 이상 미루지 말고 테스트 코드를 작성하는 것이 습관이 될 수 있도록 노력해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글부터 본격적으로 Test Driven Development(TDD)를&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Flutter 프로젝트에 적용하는 방법에 대해서 상세하게 설명해보겠습니다.&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>Flutter</category>
      <category>TDD</category>
      <category>testing</category>
      <author>윈드.</author>
      <guid isPermaLink="true">https://ctoahn.tistory.com/36</guid>
      <comments>https://ctoahn.tistory.com/36#entry36comment</comments>
      <pubDate>Tue, 18 Oct 2022 12:21:04 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Widget과 Element - 4부 결론</title>
      <link>https://ctoahn.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;지금까지 Widget과 Element 관계에 대해서 알아보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;이런 관계가 만들어지는 근본적인 이유는 Flutter의 속도를 높이고 Rendering에 필요한 연산을 최소화 하기 위해서 이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;그래서 Element는 결국 최종적으로 화면에 정보를 표시하기 위한 정보를 담는 RenderObject들과 연결되고 이 정보들을 재활용 함으로써 Widget들의 잦은 빌드 이벤트를 처리한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;Widget Tree&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;개발자가 build함수에서 Widget들을 구성해서 반환하면 이것들이 모여 하나의 큰 Widget tree를 만든다. &lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Widget tree는&amp;nbsp; 실제로 존재한다기 보다는 개발자가 인지할 수 있도록 편의상 만든 개념에 가깝다 &lt;br /&gt;(dart devtool의 widget inspector에서 볼 수 있는 widget tree는 개발의 편의를 제공해 준다.)&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;Element Tree&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;Flutter는 선언된&amp;nbsp; Widget들을 이용해서 실제로 사용할 Element Tree로 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Element tree는 Element들이 서로 부모 자식 관계를 가지는 논리적인 Tree구조로 되어 있으며,&amp;nbsp;&lt;br /&gt;Flutter에서 Element tree를 이용해서 화면에 표시할 정보와 상태를 관리한다.&lt;br /&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;하나의 화면에서 Element tree의 상태를 유지 하며 widget의 변환에 따라 Element tree를 재구성한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;Flutter는 Element의 상태를 확인해서 화면에 표시할 정보를 표시한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;Element는 크게 RenderObjectElement와 ComponentElement, InheritedElement가 있다.&lt;/span&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;&lt;/span&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt;ComponentElement &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;개발자가 주로 다루는 StatelessWidget과 StatefulWidget의 Element는 ComponentElement를 상속받는다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;위 두 위젯(StatelessWidget/StatefulWiget)을 보면 Widget을 반환하는 Build함수를 구현해야 하는데 여기서 반환하는 Widget의 구성이 하위 Tree로 배치한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;ComponentElement는 화면에 정보를 직접 표시하지 않고 자식 Element가 Tree로 구성(부모/자식관계의 설정)될 수 있도록 도와주는 역할을 한다. &lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt;InheritedElement&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;InheritedWidget의 Element로 Tree중간에서 하위 Tree의 Child Element에서 사용할 수 있는 정보를 가지고 있으며, 이 정보가 변경되면 정보를 사용하고 있는 Child Element들에게 정보의 변경을 알려주는 역할을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;이런 형태는 InheritedWidget의 데이터를 자식들이 쉽게 구독하고 사용할 수 있도록 한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;(참고로 상태관리 라이브러리인 Provider는 내부는 실제로 InheritedWidget를 사용한다. )&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt;RenderObjectElement &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;실제로 화면에 레이아웃을 비롯한 각종 그래픽적인 요소를 표시할 수 있는 RenderObject을 포함하고 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;이 Element를 통해 RenderObject는 Rendering을 위한 별도의 RenderObject Tree를 구성하고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;Flutter는 이 RenderObject Tree를 화면에 표시한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;Render Tree&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;실제 화면에 표현가능한 정보만을 가지고 Tree를 구성하는 것으로 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;Widget중 RenderObjectWidget에 구현된 createRenderObject함수에서 생성한 RenderObject들로 Tree를 구성한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;그렇기 때문에 RenderObjectWidget에 대해서만&amp;nbsp; Widget -&amp;gt; RenderObjectElement -&amp;gt; RenderObject의 관계를 가진다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;Widget - Element - RenderObject 관계 &lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드로 부터 생성되는 Widget-Element-RenderObject의 관계를 예로 들어 보자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674553953380&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SafeArea(
  child: Scaffold(
    appBar: AppBar(
      title: Text('Title'),
    ),
    body: Container(
      child: Center(
        child: Text('Body Message'),
      ),
    ),
  ),
 );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드로 부터 Widget를 만들 수 있다.&amp;nbsp; 위에서 우리가 작성한 위젯이 build되면서 실제로 사용하는 위젯들까지 포함하는 위젯트리가 만들어 진다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-02-20 오후 10.48.28.png&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;1304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bASrrd/btrZ6jsTe5H/mwA9kSg63tCKRywy4kXFs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bASrrd/btrZ6jsTe5H/mwA9kSg63tCKRywy4kXFs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bASrrd/btrZ6jsTe5H/mwA9kSg63tCKRywy4kXFs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbASrrd%2FbtrZ6jsTe5H%2FmwA9kSg63tCKRywy4kXFs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;325&quot; height=&quot;374&quot; data-filename=&quot;스크린샷 2023-02-20 오후 10.48.28.png&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;1304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Element Tree는 Widget Tree에 의해 정확히 1:1관계로 만들어진다. &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-02-20 오후 10.54.02.png&quot; data-origin-width=&quot;2480&quot; data-origin-height=&quot;1576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vRPgx/btrZYR42Ecf/lcw8O8DzLNVOqj570V9ecK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vRPgx/btrZYR42Ecf/lcw8O8DzLNVOqj570V9ecK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vRPgx/btrZYR42Ecf/lcw8O8DzLNVOqj570V9ecK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvRPgx%2FbtrZYR42Ecf%2Flcw8O8DzLNVOqj570V9ecK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2480&quot; height=&quot;1576&quot; data-filename=&quot;스크린샷 2023-02-20 오후 10.54.02.png&quot; data-origin-width=&quot;2480&quot; data-origin-height=&quot;1576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Render Tree는 Element중 RenderObjectElement에 대해서만 만들어진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-02-20 오후 11.00.06.png&quot; data-origin-width=&quot;3516&quot; data-origin-height=&quot;1608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmbI8N/btrZ5kMm1SR/NCoz6OF93LFYChxtekzMu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmbI8N/btrZ5kMm1SR/NCoz6OF93LFYChxtekzMu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmbI8N/btrZ5kMm1SR/NCoz6OF93LFYChxtekzMu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmbI8N%2FbtrZ5kMm1SR%2FNCoz6OF93LFYChxtekzMu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3516&quot; height=&quot;1608&quot; data-filename=&quot;스크린샷 2023-02-20 오후 11.00.06.png&quot; data-origin-width=&quot;3516&quot; data-origin-height=&quot;1608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter는 Element를 이용해 상태를 저장/확인하고, RenderObject로 화면 렌더링에 필요한 정보를 계산한 후 유지한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 Widget -&amp;gt; Element -&amp;gt; RenderObject로 갈수록 생성과 계산에 더 많은 시간이 필요하게 된다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 구조 때문에 Flutter는 생성에 더 많은 자원이 필요한 Element와 RenderObject를 최대한 재사용하는 구조로 만들어진 것이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 Flutter Widget을 구성할 때 RenderObject까지 고민할 필요는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 Widget으로 화면을 구성하면 많은 부분을 Flutter가 최적화해서 Element들을 재사용해서 화면을 그려주기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 만약 자신이 원하는 것과 다르게 화면이 표시되거나 화면 표시가 느리거나 버벅거림이 발생한다면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Widget-Element-RenderObject&lt;span&gt;의&lt;/span&gt; &lt;span&gt;관계를 다시한번 확인&lt;/span&gt; &lt;span&gt;후&lt;/span&gt; &lt;span&gt;불필요한&lt;/span&gt; Element&lt;span&gt;의&lt;/span&gt; &lt;span&gt;생성이&lt;/span&gt; &lt;span&gt;있는&lt;/span&gt; &lt;span&gt;것은&lt;/span&gt; &lt;span&gt;아닌지 확인해 보시기바랍니다&lt;b&gt;.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고자료&lt;/b&gt;&lt;br /&gt;&lt;a href=&quot;https://docs.flutter.dev/resources/architectural-overview#build-from-widget-to-element&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.flutter.dev/resources/architectural-overview#build-from-widget-to-element&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1676901416009&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter architectural overview&quot; data-og-description=&quot;A high-level overview of the architecture of Flutter, including the core principles and concepts that form its design.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/resources/architectural-overview#build-from-widget-to-element&quot; data-og-url=&quot;https://docs.flutter.dev/resources/architectural-overview&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/VSNhK/hyRHjsORVt/sjNx34XvwejdkcuICCksG1/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/da6LJQ/hyRHwFGfUR/hbVRvRddE0cTdZx7CuP8XK/img.png?width=2883&amp;amp;height=1383&amp;amp;face=0_0_2883_1383,https://scrap.kakaocdn.net/dn/xH9ql/hyRHrqPe0B/DQTK2QqYtnnTxYIymZpAjK/img.png?width=2013&amp;amp;height=1053&amp;amp;face=0_0_2013_1053&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/resources/architectural-overview#build-from-widget-to-element&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/resources/architectural-overview#build-from-widget-to-element&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/VSNhK/hyRHjsORVt/sjNx34XvwejdkcuICCksG1/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/da6LJQ/hyRHwFGfUR/hbVRvRddE0cTdZx7CuP8XK/img.png?width=2883&amp;amp;height=1383&amp;amp;face=0_0_2883_1383,https://scrap.kakaocdn.net/dn/xH9ql/hyRHrqPe0B/DQTK2QqYtnnTxYIymZpAjK/img.png?width=2013&amp;amp;height=1053&amp;amp;face=0_0_2013_1053');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Flutter architectural overview&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A high-level overview of the architecture of Flutter, including the core principles and concepts that form its design.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/996ZgFRENMs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://youtu.be/996ZgFRENMs&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=996ZgFRENMs&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/h0lCl/hyRHyDx7Ub/V0Fg9jyYTrqhdgy1AJ9Kc0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=674_200_1220_440&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/996ZgFRENMs&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>element</category>
      <category>Flutter</category>
      <category>RenderObject</category>
      <category>Widget</category>
      <category>Widget Tree</category>
      <author>윈드.</author>
      <guid isPermaLink="true">https://ctoahn.tistory.com/34</guid>
      <comments>https://ctoahn.tistory.com/34#entry34comment</comments>
      <pubDate>Thu, 29 Sep 2022 20:36:42 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Widget과 Element - 3부 GlobalKey와 LocalKey</title>
      <link>https://ctoahn.tistory.com/33</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt;Widget에게 Key란?&lt;/span&gt;&lt;/b&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt; &lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;Widget.key property에 부여되는 값으로 Element의 재사용을 판단하기 위해 사용된다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;빌드 과정 중 생성된 newWidget이 oldWidget의 Element를 재사용할 수 있는지를 판단할 때 사용된다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;Flutter는 기본적으로 Element를 재사용하는 것을 전제로 만들어져 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;우리가 Widget를 생성할때 별도의 key값을 주지 않아도 Element가 재사용되는데 oldWidget의 Key와 newWidget의 key가 모두 nul l일 때도 같은 Key라고 판단하기 때문이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;아래 코드는 Element가 재사용 가능한지 확인하는 코드인데 위에서 설명한 내용을 간략하게 잘 보여준다.   &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1673788686685&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
               &amp;amp;&amp;amp; oldWidget.key == newWidget.key;
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;canUpdate가 true이면 oldElement 재사용되는데 oldElement.update를 호출해서 oldElement._widget이 newWidget으로 변경되도록 한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;그리고 StatefulElement는 이과정 중 내부적으로 state.didUpdateWidget 를 호출한다.  &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1673788750401&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;_widget = newWidget;
...
state.didUpdateWidget()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;canUpdate가 false이면 oldWidget에 연결된 oldElement는 tree에서 제거되고,&amp;nbsp; newWidget의 newElement가 tree에 삽입된다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt;LocalKey &lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;font&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;.AppleSDGothicNeoI-Regular&amp;quot;,&amp;quot;pointSize&amp;quot;:0},&amp;quot;fontHints&amp;quot;:0}&quot;&gt;위에서&lt;/span&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:0}&quot;&gt; 언급한 Key는 기본적으로 LocalKey에 대한 내용이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:0}&quot;&gt;물론 GlobalKey도 같은 Key지만 GlobalKey는 조금 다른 용도로 사용하기 때문에 일반적인 Key는 다르다고 할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;font&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;.AppleSDGothicNeoI-Regular&amp;quot;,&amp;quot;pointSize&amp;quot;:0},&amp;quot;fontHints&amp;quot;:0}&quot;&gt;우선&lt;/span&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:0}&quot;&gt; 기본적인 Key라고 할 수 있는 LocalKey에 대해서 좀더 알아 도록 하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;생성된 하나의 Local Key는 같은 부모 아래에 하나의 자식 Element에 만 부여할 수 있고,&amp;nbsp; 동일한 LocalKey를 같은 부모의 다른 자식에게 부여해서는 안된다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:0}&quot;&gt;LocalKey에는&lt;b&gt; ObjectKey, UniqueKey, ValueKey&lt;/b&gt; 가 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;font&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;.AppleSDGothicNeoI-Regular&amp;quot;,&amp;quot;pointSize&amp;quot;:0},&amp;quot;fontHints&amp;quot;:0}&quot;&gt;이&lt;/span&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:0}&quot;&gt; Key들에서 가장 중요한 부분은 key의 == (같음) 여부 이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:0}&quot;&gt; 각 Key가 같음을 비교하기 위해서 ObjectKey와 ValueKey는 생성 시 전달받은 Object나 Value을 비교하고, UniqueKey는 KeyObject 자체를 비교대상으로 한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:0}&quot;&gt;LocalKey를 사용할때 가장 유의해야 하는 점은 사용 목적과 방법을 명확히 인지하고 사용해야 한다는 것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:0}&quot;&gt;특히 UniqueKey는 build함수내에서 생성하지 않도록 주의해야 하고, 다른 Key들도 생성자에 전달하는 Object나 Value가 다른 Key에서 사용하는 값들과 중복되지 않도록 유의해야 한다. &lt;/span&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt;아래 ValueKey를 사용하는 간단한 예를 들어 보겠다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt;ListView로 목록을 표시하고 목록의 각 Row를 표시하기 위해 별도의 StatefulWidget을 사용할 경우에는 State의 상태유지를 지켜주기 위해서 ValueKey를 부여할 필요가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt;여기서 사용할 value는 목록으로 표시할 모든 items들의 구분할 수 있는 유일한 값인 id로 지정하였다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt;이렇게 해야 중복되는 ValueKey가 없어지고, 다음 빌드 과정중 같은 id를 가진 item을 표시하고자 할 때 이전에 사용했던 state를 가져와 사용할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1673788976749&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ListView.builder(itemBuilder: (context, index) {
  final item = items[index];
  return StatefulListTile(key: ValueKey(item.id), item: item);
});&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;위에서 설명한 내용은 &lt;a href=&quot;https://www.youtube.com/watch?v=kn0EOS-ZiIc%20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.youtube.com/watch?v=kn0EOS-ZiIc&lt;/a&gt; 에 잘 설명하고 있으니 참고하세요!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt;GlobalKey &lt;/span&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt; &lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;기본적으로 Element는 부모를 변경할 수 없다. 부모를 기준으로 자식을 판단하기 때문에 자식에 변경이 있을 경우 자식을 다시 생성할 뿐 자식이 다른 부모의 자식으로 들어가도록 허용하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;하지만 자식이 GlobalKey를 가지고 있을 경우에는 부모가 변경될 수 있다.&amp;nbsp; &lt;/span&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;만약 Global Key를 가진 새로운 위젯이 자식으로 들어오면 이전 Tree구성에서 다른 곳에 위치하고 있던 같은 Global Key를 가지는 Element를 가져와 자신의 자식으로 위치시킬 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;위와 같은 과정을 Reparenting이라고 부르는데 성능상의 문제를 야기할 수 있음으로 꼭 필요한 상황이 아니라면 자주 사용하지 않는 것이 바람직하다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;그렇다면 GlobalKey는 어떻게 활용하는 것이 좋을까?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;실무에서 GlobalKey는 Reparenting을 위해서 사용하기 보다&amp;nbsp; 위젯의 내부 상태를 다른 곳에서 제어할 필요가 있을 때 사용하는 경우가 더 많다.&amp;nbsp; &amp;nbsp;GlobalKey는 이름에서 알수 있듯이 특정 Widget을 Global하게 접근할 수 있는 방법을 제공하는 ID와 유사하다.&amp;nbsp; Global Key를 이용하면&amp;nbsp; StatefulWidget의 State에 쉽게 접근할 수 있으며 이를 이용해서 외부에서 위젯의 상태를 쉽게 제어 할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;필자의 경우 Root Navigator를 다른 곳에서 제어할 때 주로 사용하곤 하는데 아래와 같이 전역적으로 접근 할 수 있는 rootNavigatorKey를 하나 만든 &lt;/span&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;후 이 값을 MaterialApp의 navigatorKey에 부여한다.  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;그리고 앱에서 RootNaviagor를 이용해서 화면에 변화를 주고자 할때 &lt;/span&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;rootNavigatorKey에 접근해서 사용한다.   &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1673789047150&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GlobalKey&amp;lt;NavigatorState&amp;gt; rootNavigatorKey = GlobalKey&amp;lt;NavigatorState&amp;gt;();
&amp;hellip;
return MaterialApp(
  navigatorKey: rootNavigatorKey,
  home: const HomePage(),
);
&amp;hellip;.
rootNavigatorKey.currentState.push(route);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;물론 Naviagator.of(context)를 이용할 수 있지만 navigator를 가져오기 위해 context에 의존해야 하기 사용하기 불편할 때가 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;이럴 때는 context에 의존하지 않고 rootNavigatorKey를 이용해서 RootNaviator를 직접 사용하면 좀더 유연하게 프로그램이 가능하다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;(물론 이럴때는 아무 곳에서 막 사용하는 것이 아니라 잘 구성된 아키텍처에서 약속된 목적에 의해서만 사용해야 한다.)&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Context에 의존하여 Naviagor에 접근해야 하는 경우 불편한 상황을 자주 만나게 되는데 GlobalKey를 활용하면 좀더 쉽게 Navigator에 접근해 화면의 이동을 제어할 수 있다.&amp;nbsp;&amp;nbsp;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;startingListItemNumber&amp;quot;:1},&amp;quot;font&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;.AppleSDGothicNeoI-Bold&amp;quot;,&amp;quot;pointSize&amp;quot;:0,&amp;quot;fontHints&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt;마치며&lt;/span&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;startingListItemNumber&amp;quot;:1}}&quot;&gt; &lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;startingListItemNumber&amp;quot;:1}}&quot;&gt;Key는 기본적으로 Element 재사용 판단을 위해 사용합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;startingListItemNumber&amp;quot;:1}}&quot;&gt;LocalKey와 GlobalKey는 같은 Key이지만 그 실제 그것을 사용하는 목적이 다름에 유의해서 사용하셔야 합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;startingListItemNumber&amp;quot;:1}}&quot;&gt;그리고 Key가 서로 중복되지 않도록 생성시점과 전달값에 유의해서 사용하셔야 오류 없이 원하는 결과를 얻을수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1},&amp;quot;font&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;.AppleSDGothicNeoI-Bold&amp;quot;,&amp;quot;pointSize&amp;quot;:0,&amp;quot;fontHints&amp;quot;:1},&amp;quot;fontHints&amp;quot;:1}&quot;&gt;참고자료&lt;/span&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;[Key&amp;nbsp;class] (&lt;a href=&quot;https://api.flutter.dev/flutter/foundation/Key-class.html&quot;&gt;https://api.flutter.dev/flutter/foundation/Key-class.html&lt;/a&gt;) &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;[key&amp;nbsp;property](&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/Widget/key.html&quot;&gt;https://api.flutter.dev/flutter/widgets/Widget/key.html&lt;/a&gt;) &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;[LocalKey class](&lt;a href=&quot;https://api.flutter.dev/flutter/foundation/LocalKey-class.html&quot;&gt;https://api.flutter.dev/flutter/foundation/LocalKey-class.html&lt;/a&gt;) &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;[GlobalKey class](&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/GlobalKey-class.html&quot;&gt;https://api.flutter.dev/flutter/widgets/GlobalKey-class.html&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>Flutter</category>
      <category>GlobalKey</category>
      <category>LocalKey</category>
      <author>윈드.</author>
      <guid isPermaLink="true">https://ctoahn.tistory.com/33</guid>
      <comments>https://ctoahn.tistory.com/33#entry33comment</comments>
      <pubDate>Tue, 20 Sep 2022 15:59:50 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Widget과 Element - 2부 Widget Tree와 Element Tree 그리고 LifeCycle</title>
      <link>https://ctoahn.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;보통 안드로이드 개발을 하다 플러터를 접하게 되면 가장 먼저 찾아보는 개념이 LifeCycle이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드에서는 Activity나 Fragement의 LifeCycle과 상태 변화에 따라 적절한 함수들이 호출된다. &amp;nbsp;이 함수들을 잘 이용해서 상황에 맞는 프로그램을 기능들을 추가하게 되는데 &amp;nbsp;플러터에서도 이와 유사한 개념이 없을까 하고 찾아보게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 가장 먼저 접하게 되는 정보들이 &amp;nbsp;StatefulWidget의 Lifecycle일것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screen Shot 2022-08-31 at 1.18.03 PM.png&quot; data-origin-width=&quot;2050&quot; data-origin-height=&quot;1970&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ri19k/btrKZfyTo2q/bBTvfyYCAlHhQnEsG21fxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ri19k/btrKZfyTo2q/bBTvfyYCAlHhQnEsG21fxK/img.png&quot; data-alt=&quot;StatefuleWidget LifeCycle&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ri19k/btrKZfyTo2q/bBTvfyYCAlHhQnEsG21fxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fri19k%2FbtrKZfyTo2q%2FbBTvfyYCAlHhQnEsG21fxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2050&quot; height=&quot;1970&quot; data-filename=&quot;Screen Shot 2022-08-31 at 1.18.03 PM.png&quot; data-origin-width=&quot;2050&quot; data-origin-height=&quot;1970&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;StatefuleWidget LifeCycle&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 다이어그램만 보면 StatefulWidget에 그럴듯한 LifeCycle이 있는것 처럼 보인다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 정확히 이야기 하자면 StatefulWidget은 LifeCycle을 가질 수 &amp;nbsp;없다. &amp;nbsp;왜냐 하면 모든 Widget은 Immutable 하며 build과정에 항상 새롭게 생성되기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screen Shot 2022-08-31 at 12.37.55 PM.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vHFGR/btrKZ8y21MV/YAdFTw9XS1LtSQDzGvnrhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vHFGR/btrKZ8y21MV/YAdFTw9XS1LtSQDzGvnrhk/img.png&quot; data-alt=&quot;&amp;amp;lt;StatefuleWidget과 State&amp;amp;gt;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vHFGR/btrKZ8y21MV/YAdFTw9XS1LtSQDzGvnrhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvHFGR%2FbtrKZ8y21MV%2FYAdFTw9XS1LtSQDzGvnrhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;606&quot; height=&quot;267&quot; data-filename=&quot;Screen Shot 2022-08-31 at 12.37.55 PM.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;452&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;&amp;lt;StatefuleWidget과 State&amp;gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 StatefulWidget의 LifeCycle은 State의 LifeCycle이 되는데 State도 StatefulElement의 LifeCycle에 직접적인 영향을 받기 때문에&amp;nbsp; LifeCycle에 대해서 이야기하려면 Widget이 아닌 Element의 LifeCycle를 알아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Element의 LifeCycle&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Element는 아래와 같이 부모 Element에 의해 생성/재사용/제거 된다.&amp;nbsp;&lt;br /&gt;이 과정은 ElementTree에 mount/unmount 되거나 rebuild 되는 것을 의미한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screen Shot 2022-10-05 at 12.56.12 PM.png&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;1986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pLTsd/btrNYul0ZxW/87tj2KyNOl2aRE5VVKsGZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pLTsd/btrNYul0ZxW/87tj2KyNOl2aRE5VVKsGZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pLTsd/btrNYul0ZxW/87tj2KyNOl2aRE5VVKsGZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpLTsd%2FbtrNYul0ZxW%2F87tj2KyNOl2aRE5VVKsGZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1532&quot; height=&quot;1986&quot; data-filename=&quot;Screen Shot 2022-10-05 at 12.56.12 PM.png&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;1986&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ParentElement - UpdateChild&amp;nbsp;&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 부모 Element에서 부터 시작해 보자 부모 element는 build 될 때 자신의 자식도 buld 되도록 해야 하므로 updateChild을 호출해서 자식들이 빌드 되도록 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 자식 Element를 새롭게 생성해서 mount거나, 기존에 이미 사용하던 element 재사용 하기도한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Element의 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;1.&amp;nbsp; 생성 / mount()&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;부모 element는 자식 element가 재사용 가능한지 확인 후 재사용이 불가능할 경우&amp;nbsp; 새로운 Element를 생성한 후 Mount 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1663040457202&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final child = newWidget.createElement();
child.mount(this, slot);

// mount될때 _firstBuild가 수행된다.
@override
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  _firstBuild();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;2. _firstBuild()&amp;nbsp; : 최초 빌드되기 전&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Element가 처음 mount 된 후 빌드되기 전에 호출되는 함수로 Element가 첫 빌드되기 전에 수행해야 하는 로직이 있을 경우 하위 구현체(StatelessElement / StatefulElement 등)에서 추가 동작을 구현해 주고 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1663041201035&quot; class=&quot;livescript&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// mount된 element는 _fristBuild를 호출해 자신의 첫 빌드를 수행한다. 
// _firstBuild()는 
_firstBuild()


// StatefulElement의 _fisrstBuild()와 build()
// state의 초기함수들을 호출한 후 state의 build를 호출함
@override
void _firstBuild() {
  state.initState();
  state.didChangeDepencencies();
  super._firstBuild();
  // _firstBuild를 호출하면 아래 함수들이 호출됨
  // _firstBuild()-&amp;gt; rebuild() -&amp;gt; performRebuild() -&amp;gt; build()
}
@override
Widget build() =&amp;gt; state.build(this);



// StatelessElement의 _firstBuild()와 build()
// 별도의 동작없이 widget의 build를 호출함 
@override
void _firstBuild() {
  super._firstBuild();
  // _firstBuild를 호출하면 아래 함수들이 호출됨
  // _firstBuild()-&amp;gt; rebuild() -&amp;gt; performRebuild() -&amp;gt; build()
}

@override
Widget build() =&amp;gt; widget.build(this);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&amp;nbsp;Element의&amp;nbsp; 재사용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;element의 재사용 가능 여부를 확인&amp;nbsp; (Widget.canUpdate) 후 재사용이 가능한 경우&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;1. update()&lt;/b&gt;&lt;br /&gt;element의 update함수를 호출한다.&amp;nbsp;&lt;br /&gt;이 함수는 내부적으로 rebuild를 호출해서 element가&amp;nbsp; 다시 빌드되도록 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&amp;nbsp;Element&amp;nbsp; 제거와 소멸&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 이상 사용하지 않는 child element는 이제 element tree에서 제거되어야 한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. deactivate()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build 과정에&amp;nbsp; _InactivateElements객체에 불필요한 element들을 추가하게 되는데&lt;br /&gt;여기에 추가되는 시점에 element은 deactivate 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;2. unmount()&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;위 과정에서&amp;nbsp; &lt;/span&gt;&lt;/span&gt;_InactivateElements에 모아 둔 deactivate된 element을 build마지막 과정에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 unmount 하게 되며, element는 더 이상 사용할 수 없는 상태가 되어 소멸하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;State의 LifeCycle은 Element LifeCycle의 그림자다.&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 element의 생성, 재사용, 소멸 과정을에 대해서 간단하게 알아보았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 lifecycle이 복잡하지 않지만 여러 element구현체들에서 추가 기능이 포함되면서&amp;nbsp;조금 더 복잡한 내부 과정을 가지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 우리가 잘 알고 있는 state의 LifeCycle의 경우 StatefulElement에서 Element의 함수들을 재정의 해서 사용하고 있는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Element LifeCycle과 State LifeCycle을 연결시켜 보면 Element와 State의 관계를 좀더 명확하게 알수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screen Shot 2022-10-05 at 12.54.43 PM.png&quot; data-origin-width=&quot;1958&quot; data-origin-height=&quot;1986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bssRF6/btrNZG0EGE0/7iKVrPvWbKlDkePQdshMxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bssRF6/btrNZG0EGE0/7iKVrPvWbKlDkePQdshMxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bssRF6/btrNZG0EGE0/7iKVrPvWbKlDkePQdshMxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbssRF6%2FbtrNZG0EGE0%2F7iKVrPvWbKlDkePQdshMxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1958&quot; height=&quot;1986&quot; data-filename=&quot;Screen Shot 2022-10-05 at 12.54.43 PM.png&quot; data-origin-width=&quot;1958&quot; data-origin-height=&quot;1986&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마치며&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Widget Immutable하기 때문에 LifeCycle을 가질 수 없습니다. 그리고 StatefulWidget의&amp;nbsp; State 역시 LifeCycle을 가지는 것처럼 보이지만&amp;nbsp;사실은 Element LifeCycle의 그림자입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 개발 중 원치 않는 동작이 발생하거나 Element(state)가 재사용되지 않고 항상 생성되어 속도 저하가 발생한다면 위 과정을 한 번쯤 생각해 보시고 Element를 재사용할 수 있는 형태로 Widget들이 구성되어 있는지 살펴볼 필요가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 3부 GlobalKey와 LocalKey 에서 계속&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>Element LifeCycle</category>
      <category>Flutter</category>
      <category>Flutter LifeCycle</category>
      <category>StatefulWidget LifeCycle</category>
      <author>윈드.</author>
      <guid isPermaLink="true">https://ctoahn.tistory.com/31</guid>
      <comments>https://ctoahn.tistory.com/31#entry31comment</comments>
      <pubDate>Wed, 24 Aug 2022 21:49:04 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Widget과 Element - 1부 Element를 알아야 하는 이유</title>
      <link>https://ctoahn.tistory.com/30</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Flutter는 모든 것이 Widget으로 이루어져 있다&quot;라는 말을 들어 보았을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 개발자들은 Widget만 있으면 앱의 화면을 만들고 기능을 구현할 수 있다. &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 StatelessWidget과 StatefulWidget을 만들 수 있고, 기본으로 제공되는 Widget의 사용법만 숙지한다면 간단한 화면 정도는 쉽게 만들어 낼 수 있다. 하지만 Flutter는 정말 Widget만으로 이루어진 것일까?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Widget의 반복적인 생성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 처음 Flutter을 접했을 때 가장 궁금했던 것 중 한 가지가 build함수가 재호출 되어 함수 내부에서 Widget들을 다시 생성하는데 어떻게 60 프레임을 보장할 정도로 빠른 화면 구성이 가능할까였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로만 보면 build함수 내부에서는 항상 새로운 Widget을 만들어 반환하고 있다. &amp;nbsp;항상 새로운&lt;span&gt;&amp;nbsp; &lt;/span&gt;Widget을 생성하는 것은 분명 비효율적이다. 객체의 생성 비용이 적지 않을 것이며 화면을 구성하기 위해 위치나 크기 등을 계산하는데 우리가 생각하는 것보다 훨씬 많은 연산이 필요할 것이기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StatefulWidget을 예로 들어 보자. State의 setState()를 호출하면 build() 함수가 다시 호출된다. &amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1660034816945&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Class TestState extends State&amp;lt;TestWidget&amp;gt; {
  late String title;
  
  void initState() {
       this.title = widget.title;
  }

  Widget build() {
     return Column(children: [
       Text(&amp;lsquo;Counter : $_counter&amp;rsquo;),
       TextButton( onPressed: () =&amp;gt; updateTitle(DateTime.now().toString()), Text(&amp;lsquo;+&amp;rsquo;));  
     ]);
   }

  void updateTitle(String newTitle) {
    setState((){
         title = newTitle;
    });
  }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build함수는 화면 갱신이 필요할 때마다Text, TextButton, Column을 새롭게 생성 후 반환한다. &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 위와 같은 코드를 보면서 이런 생각을 한 적이 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;정말 이렇게 항생 새로운 객체를 생성해도 문제가 없을까?&lt;span&gt;&amp;nbsp; &lt;/span&gt;build함수에서 위젯을 생성하지 않고 미리 생성해둔 Widget을 반환하면 좀 더 속도면에서 빠르고 좋지 않을까?&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아래와 같이 미리 Widget을 생성한 후 이 위젯을 재사용하도록 만들어 본 적도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1660034900491&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Class TestState extends State&amp;lt;TestWidget&amp;gt; {

  lint _counter = 0;
  late Widget _button;

  void initState() {
      super.initState();
      button = TextButton( onPressed: () =&amp;gt; updateTitle(DateTime.now().toString()), Text(&amp;lsquo;+&amp;rsquo;));
  }

  Widget build() {
     return  Column(children: [
         Text(&amp;lsquo;Counter : $_counter&amp;rsquo;),  _button)
      ]);
  }

  void updateTitle(String newTitle) {
    setState((){
         title = newTitle;
     });
  }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 미리 생성한 button을 계속해서 재사용하고 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 만들 경우 변경이 발생하는 Text 위젯은 새로 생성하고 button은 재사용하기 때문에 button 위젯의 생성 비용을 줄일 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 button을 재사용하는 대신 코드 가독성은 떨어지고 추가적으로 해야 하는 일도 많아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과연 이렇게 까지 해야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Button 위젯을 새로 생성하는데 정말 많은 비용이 발생한다면 당연히 한번 생성한 객체를 재사용할 수 있도록 해야 한다. &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(성능 최적화를 위해 한번 생성한 위젯을 재사용하는 경우도 있으나 자주 있는 일은 아니다 - 성능 최적화 관련 포스팅에서 따로 다루도록 하겠다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그 비용이 현저하게 적다면 그리고 Flutter에서 위젯의 생성 비용을 최소화하고 내부적으로 연산이 복잡한 과정들(위젯의 위치, 크기 결정 등 렌더링에 필요한 연산)을 최적화해두었다면? 우리는 굳이 Widget을 재사용하기 위해서 읽기 힘든 복잡한 코드를 만드는 대신 위젯을 항상&lt;span&gt;&amp;nbsp; &lt;/span&gt;생성해서 사용하면 될지도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금부터 Flutter에서 위젯 생성과 관련된 위 이슈를 어떻게&lt;span&gt;&amp;nbsp; &lt;/span&gt;해결하고 있는지 확인해 보도록 하겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Element가 Flutter 구조의 핵심이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서는 화면 구성과 동작을 위해 필요한 중요한 정보를 Widget이 아닌 Element를&lt;span&gt;&amp;nbsp; &lt;/span&gt;통해서 유지하고 사용한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드와 같이 모든 위젯은 자신과 연결될 Element를 생성(createElement) 해야 한다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;물론 재사용 가능한 element가 있을 경우 생성 없이 그 element을 재사용한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재사용 가능 여부는 class method인 canUpdate()를 통해 확인하는데 이 값이 true인 경우 element를 재사용하고 그렇지 않은 경우 새로 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;element를&lt;span&gt;&amp;nbsp; &lt;/span&gt;재사용한다는 것은 tree에 위치한 element를 그대로 사용한다는 뜻이며, &lt;span&gt;&amp;nbsp; &lt;/span&gt;다시 생성한다는 것은 tree에서 기존 element를 제거(unmount) 하고 새로운 element를 추가(mount) 한다는 뜻이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1660034935936&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@immutable
abstract class Widget {
   &amp;hellip;
  @factory
  Element createElement();
   &amp;hellip;
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType &amp;amp;&amp;amp; oldWidget.key == newWidget.key;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;canUpdate 부분의 코드를 좀 더 유심히 살펴볼 필요가 있는데 widget의 타입과 key를 비교해서 같은지 검사하는 단순한 코드이지만, flutter에서 widget와 element의 관계를 이해하는데 중요한 개념을 담는 코드이다. (runtimeType이 같으며 key(null인 경우 포함)가 같으면 같은 위젯으로 보고 연결된 element를 그대로 다시 사용한다는 뜻)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명했듯이 모든 Widget은 Element를 가진다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Element의 Lifecycle이 Widget보다 길기 때문에 Element가 Widget을 가진다로 표현해야 할지도 모르겠지만 서로 연결되어 있다가 더 정확한 표현인 것 같다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Widget은 단지 Element을 생성하고 변경하기 위해&lt;span&gt;&amp;nbsp; &lt;/span&gt;개발자가&lt;span&gt; &lt;/span&gt;&amp;nbsp;사용하기 쉬운 형태로 만들어진 설정 정도로 정의 하는 것이 Widget에 대한 올바른 정의일지도 모르겠다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Widget만 알아도 Flutter로 앱을 만드는데 아무런 지장이 없습니다.&lt;span&gt;&amp;nbsp; &lt;/span&gt;커스텀 Widget과 기본 Widget들만 가지고도 충분히 사용할 만한 앱을 만들 수 있습니다.&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Element에 대한 이해가 없이 앱을 만들다 보면 의도치 않은 화면의 갱신이나 오동작을 만나게 됩니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Element에 대해서 알고 있다면 그 문제들을&amp;nbsp; 쉽게 해결할 수 있으며, 처음부터 그런 오류가 없는 앱을 만들 수 도 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; 2&lt;span&gt;부 -&lt;/span&gt;&amp;nbsp;Widget Tree&lt;span&gt;와&lt;/span&gt; Element Tree &lt;span&gt;그리고&lt;/span&gt; Lifecycle&lt;span&gt;에서&lt;/span&gt; &lt;span&gt;계속&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>Flutter</category>
      <category>flutter element</category>
      <category>flutter widget</category>
      <category>widget element</category>
      <author>윈드.</author>
      <guid isPermaLink="true">https://ctoahn.tistory.com/30</guid>
      <comments>https://ctoahn.tistory.com/30#entry30comment</comments>
      <pubDate>Tue, 9 Aug 2022 22:01:07 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Multiple Flutter : FlutterEngine의 다양한 생성과 활용</title>
      <link>https://ctoahn.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter로 처음 앱을 만들면&amp;nbsp; 특별한 설정 없이도 flutter화면이 정상적으로 잘 나오는 것을 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 보통은 그 첫번째 화면에서부터 앱을 만들어 가기 시작한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 android에서 flutter화면을 띄우기 위해서 어떤 일을 하는지 알지 못해도 우리가 앱을 개발하고 실행하는데 아무런 문제가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Flutter가 구동되는 원리를 조금만 더 이해하고 개발한다면 Android Native와&amp;nbsp; Flutter을 유연하게 구성해서 다양한 요구사항을 처리할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FlutterEngine의 생성&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;FlutterEngine&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;생성하는&lt;span&gt; &lt;/span&gt;기본&lt;span&gt; &lt;/span&gt;코드를&lt;span&gt; &lt;/span&gt;확인하면&lt;span&gt; &lt;/span&gt;각&lt;span&gt; OS&lt;/span&gt;에서&lt;span&gt; Dart&lt;/span&gt;의&lt;span&gt; &lt;/span&gt;코드를&lt;span&gt; &lt;/span&gt;어떻게&lt;span&gt; &lt;/span&gt;실행하게&lt;span&gt; &lt;/span&gt;되는지&lt;span&gt; &lt;/span&gt;알수&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를&lt;span&gt; &lt;/span&gt;알면&lt;span&gt; FlutterEngine&lt;/span&gt;을&lt;span&gt; 좀더 &lt;/span&gt;다양하게&lt;span&gt; &lt;/span&gt;설정해서&lt;span&gt; &lt;/span&gt;사용하는&lt;span&gt; &lt;/span&gt;것이&lt;span&gt; &lt;/span&gt;가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1657723263476&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val flutterEngine = FlutterEngine(this)
flutterEngine
    .dartExecutor
    .executeDartEntrypoint(
        DartExecutor.DartEntrypoint.createDefault()
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 코드는 android에서 FlutterEngine을 생성하는 코드이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;일반 클래스처럼 flutterEngine 객체를 만들고 몇 가지 설정만 해주면 그대로 사용할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;생각보다 특별한 것 없이&amp;nbsp; 쉽게 생성할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 executeDartEntrypoint를 DartExecutor.DartEntrypoint.createDefault()를 사용하고 있는 것을 눈여겨보자&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분의 구현은 아래와 같이 되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1657723105053&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@NonNull
public static DartEntrypoint createDefault() {
  FlutterLoader flutterLoader = FlutterInjector.instance().flutterLoader();
  if (!flutterLoader.initialized()) {
    throw new AssertionError(
        &quot;DartEntrypoints can only be created once a FlutterEngine is created.&quot;);
  }
  return new DartEntrypoint(flutterLoader.findAppBundlePath(), &quot;main&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 DartEntirypoint를 생성하기 위해 bundlePath와 &quot;main&quot;을 인자로 넣어주고 있는 것을 확인할 수 있는데 이는 기본 설정인 /lib/main.dart 파일과 main() 함수를 기본 실행 포인트로 설정하는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 위에서 &quot;main&quot;대신 &quot;main02&quot;로 함수명을 변경하면 main.dart 파일에서 main02 함수를 실행할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 flutterEngine.dartExecutor.executeDartEntrypoint의 기본 설정이&amp;nbsp; /lib/main.dart 파일의 main()이기 때문에 우리가 만든 프로젝트의 main.dart 파일 속&amp;nbsp; main() 함수가 실행되면서 flutter가 시작되는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 executeDartEntrypoint를 다른 것으로 변경하면 어떻게 될까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 앱에서 main01()과 main02()로 구분해서 Flutter화면을 따로 실행하도록 할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 가능하다. 실제로 시작 포인트를 다르게 주고 여러 FlutterActivity를 이용해서 앱을 구성할 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FlutterEngine이 생성 후 entryPoint가 설정되고 나면 그 즉시 main.dart의 main() 함수가 호출된다.&amp;nbsp; 하지만 이렇게 dart코드가 호출되더라도 화면에 바로 표시되지는 않는다.&amp;nbsp; FlutterEngine은 어디까지나 Dart와 Flutter코드를 구동하고 해석해서 Native에서 활용할 수 있도록 해주는 엔진이기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FlutterView 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter가 화면에 표시되기 위해서는 FlutterView을 생성한 후 FlutterEngine과 연결해주는 과정이 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FlutterView의 생성은 FlutterActivityFragmentDelegate.onCreateView() 함수에서 쉽게 확인할 수 있다.&amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;View onCreateView(
      LayoutInflater inflater,
      @Nullable ViewGroup container,
      @Nullable Bundle savedInstanceState,
      int flutterViewId,
      boolean shouldDelayFirstAndroidViewDraw) {

    if (host.getRenderMode() == RenderMode.surface) {
      FlutterSurfaceView flutterSurfaceView =
          new FlutterSurfaceView(
              host.getContext(), host.getTransparencyMode() == TransparencyMode.transparent);
      host.onFlutterSurfaceViewCreated(flutterSurfaceView);
      flutterView = new FlutterView(host.getContext(), flutterSurfaceView);
    } else {
      FlutterTextureView flutterTextureView = new FlutterTextureView(host.getContext());
      flutterTextureView.setOpaque(host.getTransparencyMode() == TransparencyMode.opaque);
      host.onFlutterTextureViewCreated(flutterTextureView);
      flutterView = new FlutterView(host.getContext(), flutterTextureView);
    }

    flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
    flutterView.attachToFlutterEngine(flutterEngine);
    flutterView.setId(flutterViewId);
	
    return flutterView;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 FlutterView가  surfece 모드 texture모드 중 하나로 생성되는 것을 확인 할 수 있는데 기본 설정은 surface모드이며 이때는 FlutterView위에 다른 View를 표시할 수 없으며 texture모드 일 때는&amp;nbsp; FlutterView위에 다른 View를 표시할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 FlutterEngine와 FlutterView는 아래와 같이 view를 engine에 attach 해주는 것으로 연결시킬 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1657807029740&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flutterView.attachToFlutterEngine(flutterEngine);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 FlutterView와 FlutterEngine이 연결되었고, 화면에서 우리가 알고 있는 FlutterUI들을 볼 수 있게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FlutterActivity로 FlutterEngine과 View 표시하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FlutterEngine과 FlutterView를 직접 생성하고 서로 연결하는 것은 많이 번거로운 일이다.&amp;nbsp; 기본적인 생성외에도 여러 가지 listener들을 설정해야 하는 번거로움도 있다.&amp;nbsp; 그래서 실무에서는 FlutterActivity나 FlutterFragment를 활용해서 Engine과 View를 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1657808367751&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 새로운 FlutterEngine을 생성해서 사용하는 FlutterActivity를 실행한다. 
// FlutterEngine의 기본설정인 main.dart의 main함수를 실행할것이다.
val intent = FlutterActivity
            .withNewEngine()
            .build(this)
startActivity(intent)

// 미리 생성해둔 engine02를 사용하는 FlutterActivity를 실행한다.
val intent = FlutterActivity
  .withCachedEngine(&quot;engine02&quot;)
  .build(this)
startActivity(intent)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FlutterActivity나 FlutterFragment를 사용해서 engine을 띄울 때는 새로운 engine을 생성해서 사용하거나 이미 생성된 engine을 이용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 새로운 engine을 생성할 경우 FlutterActivity가 생성되는 시점에 engine이 만들어지는데, engine이 생성될 때 1초 정도의 시간이 소요되기 때문에 잠깐 화면이 멈춘 것 같은 부자연스러운 느낌을 받게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해서는 미리 engine을 생성해두고 이것을 활용해서 Activity을 시작하는 방법이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FlutterEngineCache를 활용한 Engine사용&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FlutterActivity를 이용해 새로운 화면을 띄울 때 미리 생성해둔 engine을 이용하면 훨씬 빠르고 자연스럽게 화면이 이동한다. &amp;nbsp;보통 앱이 처음 시작할 때 Application에서 Engine을 미리 만들어 두고 그것을 재사용한다.&amp;nbsp; 한번 만들어진 Engine는 그 참조값이 유지되는 한 메모리에서 해제되지 않고 다시 불러와 사용하더라도 이전에 사용하던 모습(Flutter내부 화면)을 그대로 표시할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1657809189891&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기본설정을 따르는 enigne01
val engine01 = FlutterEngine(this)
engine01
    .dartExecutor
    .executeDartEntrypoint(
        DartExecutor.DartEntrypoint.createDefault()
    )

// main.dart의 main02() 실행하는 engine02
val flutterLoader = FlutterInjector.instance().flutterLoader()
val engine02 = FlutterEngine(this)
engine02
  .dartExecutor
  .executeDartEntrypoint(
    DartEntrypoint(flutterLoader.findAppBundlePath(), &quot;main02&quot;)
}

// engine01과 engine02를 engineCache에 등록한다.
FlutterEngineCache.getInstance().put(&quot;engine01&quot;, engine01)
FlutterEngineCache.getInstance().put(&quot;engine02&quot;, engine02)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이제 enigne01과 engine02가 만들어졌고 우리는 필요에 따라 각 engine을 가져다 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱의 기능이 많아지고 여러 가지 요구사항들을 적용하다 보면 Flutter화면 하나만으로 해결이 되지 않을 때도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; Android용 라이브러리나 Activity를 직접 구현해서 사용해야 하는 경우도 있으며, 여러&amp;nbsp; Flutter화면과 Android Activity / Fragment를 복합적으로 활용해야 할때도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 앱에서 여러 Flutter화면과 Engine을 사용 하는 것이 가능하다는 것을 알고 있다면 우리가 만들 제품들을 좀 더 유연하고 다양한 방법으로&amp;nbsp; 만들 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고자료:&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/development/add-to-app/android/add-flutter-screen?tab=prewarm-engine-kotlin-tab&quot;&gt;https://docs.flutter.dev/development/add-to-app/android/add-flutter-screen?tab=prewarm-engine-kotlin-tab&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1657810244760&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Adding a Flutter screen to an Android app&quot; data-og-description=&quot;Learn how to add a single Flutter screen to your existing Android app.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/development/add-to-app/android/add-flutter-screen?tab=prewarm-engine-kotlin-tab&quot; data-og-url=&quot;https://docs.flutter.dev/development/add-to-app/android/add-flutter-screen&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jUTED/hyO5PUWtiP/2QBYeXcAxHtsiCon7G5ER0/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/YGgjs/hyO5PtRH98/g1sj2ATpHNr0qDFd7ReyCk/img.png?width=1440&amp;amp;height=595&amp;amp;face=0_0_1440_595,https://scrap.kakaocdn.net/dn/b0dbsc/hyO4DBPxDk/1Kky7YV9nJCWA5lUlvobQ0/img.png?width=1440&amp;amp;height=595&amp;amp;face=0_0_1440_595&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/development/add-to-app/android/add-flutter-screen?tab=prewarm-engine-kotlin-tab&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/development/add-to-app/android/add-flutter-screen?tab=prewarm-engine-kotlin-tab&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jUTED/hyO5PUWtiP/2QBYeXcAxHtsiCon7G5ER0/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/YGgjs/hyO5PtRH98/g1sj2ATpHNr0qDFd7ReyCk/img.png?width=1440&amp;amp;height=595&amp;amp;face=0_0_1440_595,https://scrap.kakaocdn.net/dn/b0dbsc/hyO4DBPxDk/1Kky7YV9nJCWA5lUlvobQ0/img.png?width=1440&amp;amp;height=595&amp;amp;face=0_0_1440_595');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Adding a Flutter screen to an Android app&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to add a single Flutter screen to your existing Android app.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developpaper.com/a-zero-intrusion-and-efficient-flutter-hybrid-stack-management-scheme-is-worth-having/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developpaper.com/a-zero-intrusion-and-efficient-flutter-hybrid-stack-management-scheme-is-worth-having/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1657810077790&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;A zero intrusion and efficient flutter hybrid stack management scheme is worth having - Develop Paper&quot; data-og-description=&quot;In the actual working scenario, it is difficult for us to build a project with pure fluent from scratch. It is precisely because of this that we have to consider the jump management of native + fluent hybrid stack first in hybrid development, because it is&quot; data-og-host=&quot;developpaper.com&quot; data-og-source-url=&quot;https://developpaper.com/a-zero-intrusion-and-efficient-flutter-hybrid-stack-management-scheme-is-worth-having/&quot; data-og-url=&quot;https://developpaper.com/a-zero-intrusion-and-efficient-flutter-hybrid-stack-management-scheme-is-worth-having/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/CH2MH/hyO4ASDI3b/3PYkKkhqDyKUDepil2BrW0/img.png?width=971&amp;amp;height=107&amp;amp;face=0_0_971_107,https://scrap.kakaocdn.net/dn/sxIuD/hyO5NbLtez/Xn4JgJjCxkye9rFXmUdp00/img.png?width=696&amp;amp;height=659&amp;amp;face=0_0_696_659,https://scrap.kakaocdn.net/dn/RkfTS/hyO5GX0MMG/Khft0H9jmF7zryPjFwHuVK/img.jpg?width=690&amp;amp;height=396&amp;amp;face=153_146_313_321&quot;&gt;&lt;a href=&quot;https://developpaper.com/a-zero-intrusion-and-efficient-flutter-hybrid-stack-management-scheme-is-worth-having/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developpaper.com/a-zero-intrusion-and-efficient-flutter-hybrid-stack-management-scheme-is-worth-having/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/CH2MH/hyO4ASDI3b/3PYkKkhqDyKUDepil2BrW0/img.png?width=971&amp;amp;height=107&amp;amp;face=0_0_971_107,https://scrap.kakaocdn.net/dn/sxIuD/hyO5NbLtez/Xn4JgJjCxkye9rFXmUdp00/img.png?width=696&amp;amp;height=659&amp;amp;face=0_0_696_659,https://scrap.kakaocdn.net/dn/RkfTS/hyO5GX0MMG/Khft0H9jmF7zryPjFwHuVK/img.jpg?width=690&amp;amp;height=396&amp;amp;face=153_146_313_321');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;A zero intrusion and efficient flutter hybrid stack management scheme is worth having - Develop Paper&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In the actual working scenario, it is difficult for us to build a project with pure fluent from scratch. It is precisely because of this that we have to consider the jump management of native + fluent hybrid stack first in hybrid development, because it is&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developpaper.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>Android</category>
      <category>Flutter</category>
      <category>FlutterEngine</category>
      <category>Mutiple Flutter</category>
      <author>윈드.</author>
      <guid isPermaLink="true">https://ctoahn.tistory.com/29</guid>
      <comments>https://ctoahn.tistory.com/29#entry29comment</comments>
      <pubDate>Wed, 13 Jul 2022 23:15:52 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Freezed : 우리는 왜 immutable 객체를 사용해야 하는가?</title>
      <link>https://ctoahn.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Immutable? 불변의 객체 이걸 왜 사용해야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart로 클래스를 만들면 기본적으로 mutable하다.&amp;nbsp; 하지만 이런 객체(특히 Model)를 사용하게 되면 원하지 않는 데이터의 변경이 발생하는 경우가 있다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 한번 보자&lt;/p&gt;
&lt;pre id=&quot;code_1657114611794&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Document {
    String id;
    String title;
    Document({required this.id, required this.title});  
}

void main() {
    final document = Documet(id:&amp;rsquo;1234&amp;rsquo;, title:&amp;rsquo;개발자놀이!&amp;rsquo;);
    final isValidTitle = checkTitleValidation(document);
	print(&amp;lsquo;${document.title}: ${isValidTitle}&amp;rsquo;);
}

bool checkTitleValidation(Document document) {
    Document.title =   &amp;rsquo;사용불가&amp;rsquo;;
    return false;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 유효한 제목인지를 검사하는 함수&amp;nbsp; checkTitleValidation&amp;nbsp;이 있다고 가정하자&lt;span&gt;&amp;nbsp; &lt;/span&gt;이 함수의 역할은 title이 유효한 값일 경우 true를 그렇지 않은 경우 false를 반환한다.&lt;span&gt;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 현재 잘못된 구현으로 함수내부에서 document.title을 다른 값으로 변경해 버렸다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;물론 조금은 극단적인 상황 이지만 간혹 인자로 전달받은 객체의 내부를 마음대로 변경하고 있는 코드를 볼 때가 있다. (특히 예전 c스타일의 코드를 보면 참조 값 전달 후 전달받은 인자의 내부를 마음대로 변경하는 코드를 아주 쉽게 볼 수 있다. 이건 C개발에서 어쩔 수 없는 요소일 것 같지만 요즘 언어에서는 이렇게 사용하면 안 된다. )&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 다른 함수 내부에서 document객체가 변경되어 버리면 main함수를 개발하던 개발자는 checkTitleValidation함수가 내부에서 마음대로 값을 변경했을 거라 생각하지 못한 채 다음 코드를 작성하다 자신의 의도와 다른 결과가 나온 것을 보고 당황하게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 함수의 인자로 전달받은 값을 변경해선 안된다. 하지만 우리는 이것을 강제할 수 없고 그렇게 하지 않을 것이라도 믿고 개발을 하는 수밖에 없다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다행히&lt;/span&gt; dart&lt;span&gt;에는&lt;/span&gt; final &lt;span&gt;키워드를&lt;/span&gt; &lt;span&gt;이용해서&lt;/span&gt; &lt;span&gt;변수가&lt;/span&gt; &lt;span&gt;생성된&lt;/span&gt; &lt;span&gt;후 더이상 변경할&lt;/span&gt; &lt;span&gt;수&lt;/span&gt; &lt;span&gt;없도록 강제 할 수&lt;/span&gt; &lt;span&gt;있고&lt;/span&gt;, @immutable anotiaion&lt;span&gt;을&lt;/span&gt; &lt;span&gt;이용해서&lt;/span&gt; immutable 하다는 것을 &lt;span&gt;개발자에게&lt;/span&gt; &lt;span&gt;알릴 수 도&lt;/span&gt; &lt;span&gt;있다&lt;/span&gt;. &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1657114853202&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@immutable
class Document {
    final String id;
    final String title;
    Document({required this.id, required this.title})   
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@immutable / final을 사용하는 것만으로 우리가 원하는 immutable객체를 만들 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이렇게 immutable객체를 만들게 되면 필연적으로 추가해야 하는 기능이 있는데 바로 copy / copyWith함수이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미&lt;span&gt; &lt;/span&gt;만들어진&lt;span&gt; docuemnt&lt;/span&gt;객체의&lt;span&gt; title&lt;/span&gt;을&lt;span&gt; 다른것으로 &lt;/span&gt;변경해야&lt;span&gt; 할때는 &lt;/span&gt;아래와&lt;span&gt; &lt;/span&gt;같이&lt;span&gt; &lt;/span&gt;기존&lt;span&gt; document&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값을&lt;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;생성자에&lt;span&gt;&amp;nbsp;&lt;/span&gt;전달해서&lt;span&gt; 새로운 객체를 &lt;/span&gt;만들어야&lt;span&gt; &lt;/span&gt;한다&lt;span&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1657115066183&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final document = Documet(id:&amp;rsquo;1234&amp;rsquo;, title:&amp;rsquo;개발자놀이!&amp;rsquo;)

// title을 변경하기 위해서는 newDocument객체를 다시 생성해야한다.
final newDocument = Document(id:document.id, title:&amp;rsquo;수정된 제목&amp;rsquo;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 와 같이 Model내부의 값 하나를 변경하기 위해 새로운 객체를 생성하는 것은 매우 번거로운 일이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;특히&amp;nbsp; Model내부 property가 많으면 코드도 길어져서 불필요한 일을 많이 해야 한다.&amp;nbsp; 그래서 보통은 아래와 같은&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;copyWith&lt;/span&gt;라는&lt;span&gt; &lt;/span&gt;함수를&lt;span&gt; &lt;/span&gt;만들어서&lt;span&gt; &lt;/span&gt;사용한다&lt;span&gt;. &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1657114984289&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@immutable
class Document {
    final String id;
    final String title;
    Document({required this.id, required this.title}) ;
   
    Document copyWith({String? id, String? title})  {
        return Document(id : id ?? this.id, name: name ?? this.name);
    }
}

final document = Documet(id:&amp;rsquo;1234&amp;rsquo;, title:&amp;rsquo;개발자놀이!&amp;rsquo;)

// copyWith함수를 이용해서 쉽게 title이 변경된 새로운 Document를 생성한다.
final newDocument = document.copyWith(title: &amp;lsquo;&amp;rsquo;수정된 제목);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 우리의 inmutable class인 Document가 완성되었다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이  Document 클래스만으로도&amp;nbsp; immutable 객체에게 원했던&amp;nbsp; 것을 얻을 수 있다.&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;immutable 객체를 사용하지 않으면?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다 보면 immutable 하게 만드는 것보다 mutable 한 model를 사용하는 것이 편리하게 느껴질 때가 많다.&lt;span&gt;&amp;nbsp; 번거롭게 새로운 객체를 생성하는 것 보다 그냥 내부의 값만 변경하는 게 더 좋아 보이고 직관적으로 보일 때가 많은 것도 사실이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;immutable 한 객체를 사용하지 않을 경우 우리의 코드가 어떻게 될지 한번 상상해 보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리의 Document클래스에 다음과 같은 요구사항이 추가되었다고 가정해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;level라는 property가 하나 더 추가되었다.&amp;nbsp; 이것의 초기 값은 1인데&amp;nbsp; 유저는 화면에서 그 값을 1에서 10까지 변경할 수 있다 그리고 이 값이 변경되면 DB에 변경된 Document 정보를 저장해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 개발의 편의를 위해 위에서 만들었던 immutable 한 Document를 아래와 같이 mutable 하게&amp;nbsp; 바꿔 버렸다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아래 코드는 다음과 같은 상황을 가정해서 작성하였다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 문서 목록 화면 (ListViewModel)에서 openFirstDocument함수를 호출하면 문서 1번의 상세 정보 화면으로 이동한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이때 documents의 0번째 값을 그대로 인자로 넣어서 문서 상세 정보를 볼 때 활용하도록 하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 문서 상세화면 (DetailViewModel)에서는 유저에 의해 level이 변경되고, 이 값은 인자로 전달받은 document에 반영하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 그리고 이 document의 변경사항을 DB에 저장하기 위해&amp;nbsp; Repository의 updateDocument함수를 통해 DB에 저장하였다. (그리고 이때 불행하게도 DB에 저장하고 난 후 document.level를 10으로 변경하는 코드가 들어가 버렸다. - 역시 좀 극단적인 상황이긴 하지만 이런 실수가 아니더라도 개발된 코드 중 인자로 전달받은 값을 변경하는 경우를 자주 접할 수 있다.)&lt;/p&gt;
&lt;pre id=&quot;code_1657116564702&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// level를 변경해야 하기 때문에 @imutable을 제거하고 level은 final로 선언하지 않았다.
class Document  {
    final String id;
    final String title;
    int level;
    Document({required this.id, required this.title, this.level = 1}) ;
}

class ListViewModel {
   final documents = [
       Document(id: '001', title: '문서1번', level: 5),
       Document(id: '002', title: '문서2번', level: 7)   
   ]
   
   void openFirstDocument() {
     ... 
     _goDocuemntDetail(documents[0])
     ...
   }
}

class DetailViewModel {
  document document;
  DocumentRepository _documentRepository;
  
  DocumentViewModel(this.document);
  
  void updateLevel(int level) async {
      document.level = level;
      await _documentRepository.updateDocument(document);
  }
}

class DocumentRepository {
   Future&amp;lt;void&amp;gt; updateDocument(Document document) {
      // Save document ....
      document.level = 10
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정을 한번 보자&amp;nbsp; 어떠한가. 조금 코드도 간결하고 쉬워 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 document라는 객체 (listViewModel.documents [0] 이였던 객체)는 화면 기준으로 ListViewModel / DetailViewModel 이렇게 두 화면에 걸쳐서 존재하고 있고,&amp;nbsp; 2개의 class ( DetailViewModel / Repository)에서 값 변경이 일어나고 ListViewModel / DetailViewModel 모두에게 영향을 끼치게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떠한가&amp;nbsp; 우리는 이제 document 객체의 값을 신뢰할 수 있는가?&amp;nbsp; document가 내가 진짜 원하던 값을 가지고 있다고 보장할 수 있는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들기 위한 조금은 극단적인 상황이기는 하지만, 개인적으로 이와 유사한 코드를 생각보다 꽤 많이 보았던 기억이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 쉽게 개발하기 위해 필드에서 final를 제거하면&amp;nbsp; 편하게 개발하는 것이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이렇게 되면 그 값을 신뢰할 수 없고,&amp;nbsp; 여러 개발자들이 함께 개발을 한다고 가정했을 때 Document클래스를 어떻게 다루어야 할지 혼란스럽게 만들 수 도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;immutable 객체를 사용한다면?&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위와 똑같은 상황을 immutable 한 Document를 이용해서 개발해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1657118558357&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@immutable
Class Document  {
    final String id;
    final String title;
    final int level;
    Document({required this.id, required this.title, required this.level = 1}) ;
     
    Document copy() {
        return Document(id: this.id, name: this.name, level: this.level);
    }
    Document copyWith({String? id, String? title, int? level})  {
        return Document(id : id ?? this.id, name: name ?? this.name, level: level ?? this.level);
    }
    
}

class ListViewModel {
   final documents = [
       Document(id: '001', title: '문서1번', level: 5),
       Document(id: '002', title: '문서2번', level: 7)   
   ]
   
   void openFirstDocument() {
     ... 
     // 다른 화면으로 값을 전달할때는 참조값을 그대로 전달하기 보다는 deepCopy한 객체를 전달해야 한다. 
     _goDocuemntDetail(documents[0].copy())
     ...
   }
}

class DetailViewModel {
  document document;
  Repository _repository;
  
  DocumentViewModel(this.document);
  
  void updateLevel(int level) async {
      final newDocument = document.copyWith(level: level)
      await _repository.updateDocument(newDocument);
      document = newDocument;
  }
}

class Repository {
   Future&amp;lt;void&amp;gt; updateDocument(Document document) {
      // Save document ....
      // document.level = 10  // 개발자가 실수로 작성했던 이 코드는 컴파일 오류가 발생했을것이다.
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 어떤가? 그냥 보기에는 크게 달라 보이지 않는다 하지만 아래 사항들이 변경되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Document를 immnutable 하게&amp;nbsp;만듬&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. document [0]을 직접 전달했던 것을 새로운 객체로 만들어 DetailView로 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. repository.updateDocument에 document를 직접 전달했던 것에서 document의 level를 변경한 새로운 객체를 만들어 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. updateDocument함수 내에서 인자로 받은 document를 수정하려 할 때 오류 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 클래스들에서 사용되던 document는 이제 자신이 속해 있는 class내에서만 유효하게 되었고, 다른 클래스들에 의해 영향을 받지 않게 되었다.&amp;nbsp; 이제 document는 자신이 속한 클래스 내에서는 신뢰할 수 있는 값을 가진 객체로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시 상황이 조금은 극단적일 수도 있다 하지만 실무에서는 이보다 더 복잡한 상황이 많고 하나의 객체가 여러 클래스와 화면에 걸쳐 영향을 끼치게 되면 우리가 예상하지 못했던 많은 문제들을 만들게 된다. (명확하게 여러 화면에서 함께 공유하고 수정하기 위해서 사용하는 것이 아니라면 자신이 속한 영역 내에서만 객체가 유효하도록 만들어야 한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Freezed&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 freezed 라이브러리를 소개하려고 작성하던 글인데 immutable객체에 대한 설명이 훨씬 길어진 느낌이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명했던 것처럼 immutable객체를 쉽게 만들기 위해서 사용할만한 라이브러리가 freezed이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/freezed&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://pub.dev/packages/freezed&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1657120423384&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;freezed | Dart Package&quot; data-og-description=&quot;Code generation for immutable classes that has a simple syntax/API without compromising on the features.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/freezed&quot; data-og-url=&quot;https://pub.dev/packages/freezed&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tkvLg/hyO0PgoOI2/ZkKEsgPuMuAkivWSKlkVS0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/d5UYKY/hyOZHRLvlU/bLLldvCvyt2Wx0cbBhh0Kk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/7DbnE/hyOZGrNugc/MkmtXb647bcTbtJmLRG4b0/img.png?width=958&amp;amp;height=2590&amp;amp;face=0_0_958_2590&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/freezed&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/freezed&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tkvLg/hyO0PgoOI2/ZkKEsgPuMuAkivWSKlkVS0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/d5UYKY/hyOZHRLvlU/bLLldvCvyt2Wx0cbBhh0Kk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/7DbnE/hyOZGrNugc/MkmtXb647bcTbtJmLRG4b0/img.png?width=958&amp;amp;height=2590&amp;amp;face=0_0_958_2590');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;freezed | Dart Package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Code generation for immutable classes that has a simple syntax/API without compromising on the features.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;freezed는 json_serializable + Equatable + Immutable를 하나로 합쳐 놓은 듯한 느낌의 라이브러리이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. toJson / fromJson 함수를 제공해 json으로 쉽게 serialize / deserialize 할 수 있도록 돕는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. equals (==)와 hashCode를 자동으로 작성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 선언된 필드들의 getter 만들어서 외부에서 값을 변경할 수 없도록 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. copy와 copyWith을 자동으로 구현해주고,&amp;nbsp; 종속성을 가지는 하위 클래스들에 대해서도 쉽게 deepCopy 할 수 있도록 도와준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 나열한 기능들을 실무에서 사용하는 모든 Model에 직접 코드로 구현한다면 아주 많은 시간이 필요할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 freezed는 이것을 아주 간편하게 설정하고 자동으로 코드를 generate 해주기 때문에 아주 고마운 라이브러리라고 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;라이브러리의 상세한 사용법은 공식문서에서 아주 잘 다루고 있음으로 필요하신 분들은 공식 문서를 참고하세요&amp;nbsp;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 라이브러리는 상황에 따라 자신에게 필요한 것을 찾아서 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 freezed보다 더 좋은 라이브러리가 나와서 우리를 편하게 해 줄지도 모릅니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 우리가 왜 immutable객체를 사용해야 하는지 알고서 사용하는 것이&amp;nbsp; 특정 라이브러리를 사용하는 것보다 더 중요할 것입니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>DART</category>
      <category>Flutter</category>
      <category>freezed</category>
      <category>immutable</category>
      <category>json_serializable</category>
      <category>mutable</category>
      <author>윈드.</author>
      <guid isPermaLink="true">https://ctoahn.tistory.com/28</guid>
      <comments>https://ctoahn.tistory.com/28#entry28comment</comments>
      <pubDate>Wed, 6 Jul 2022 22:33:42 +0900</pubDate>
    </item>
  </channel>
</rss>