Coaspe

Flutter - Architectural overview #Widgets 본문

Flutter/근본

Flutter - Architectural overview #Widgets

Coaspe 2023. 2. 17. 14:03

Widgets

Flutter의 위젯은 결합의 단위입니다. 위젯은 플러터 앱의 유저 인터페이스를 만드는 블럭들이고, 각 위젯은 유저 인터페이스의 부분의 불변한 선언입니다.

 

위젯은 결합을 기반으로 계층을 형성합니다. 각 위젯은 부모들과 중첩되고 그 부모로부터 context를 받습니다. 이런 구조는 루트 위젯까지 이어집니다.(주로 MaterialApp, CupertinoApp 같은 Flutter app을 호스트하는 컨테이너)

 

앱은 사용자 상호작용과 같은 이벤트에 대응하여 사용자 인터페이스를 업데이트하기 위해 프레임워크에게 위젯 계층 구조에서 하나의 위젯을 다른 위젯으로 대체하도록 지시합니다. 그 후 프레임워크는 이전의 위젯과 새로운 위젯을 비교하고, 효율적으로 유저 인터페이스를 업데이트합니다.

 

Flutter는 UI 제어를 시스템에게 맡기지 않고, 각 UI 제어에 대한 자체적인 구현을 가지고 있다: 예를들어 iOS Switch control과 그에 대응하는 Android의 순수 Dart implementation가 존재합니다.

 

이런 접근은 몇 가지 이점이 있습니다:

 

  • 무제한적인 확장성을 제공합니다. Switch control의 변형을 구현하고 싶은 개발자는 다른 방법으로 만들 수 있고, OS가 제공하는 확장점에 제한되지 않습니다.
  • Flutter 코드와 플랫폼 코드를 왔다 갔다 하지 않고, Flutter가 전체 장면(scene)을 한 번에 결합할 수 있도록 하여 성능 병목 현상을 방지합니다.
  • 운영체제 의존성으로부터 어플리케이션의 행동을 분리합니다. OS가 제어에 대한 구현을 바꿀 지라도, 어플리케이션의 외관은 그 OS의 모든 버젼에서 똑같이 유지됩니다.

Composition

위젯은 일반적으로 작고 단일 목적의 여러 위젯으로 구성되며, 이들이 결합하여 강력한 효과를 만들어냅니다.

 

가능하다면 디자인 컨셉의 수는 최소로 유지되고 전체 vocabulary는 많도록 유지됩니다. 예를 들면, 위젯들의 레이어에서, Flutter는 화면을 그리기 위해 동일한 중심 컨셉(Widget)을 사용합니다: layout (포지셔닝과 사이징), 유저 상호작용, 상태 관리, theming, 애니메이션, 네비게이션. 애니메이션 레이어에서, 컨셉들의 한 쌍인 AnimationTween은 디자인의 대부분을 커버합니다. 렌더링 레이어에서, RenderObject는 layout, painting, hit testing 그리고 접근성을 설명하기위해 사용됩니다. 이런 경우에 각각, 대응하는 vocabulary는 커지게 됩니다:수백개의 위젯과 렌더 객체가 있고 수십개의 애니메이션과 트윈 객체가 있습니다.

 

클래스 계층은 각각 하나의 작업을 잘 수행하는 작고 구성 가능한 위젯에 초점을 맞추어 가능한 조합 수를 최대화하기 위해 의도적으로 얕고 넓습니다. 핵심이되는 기능들은 추상적이며, padding, alignment와 같은 기본 기능조차도 코어에 내장되어 있지 않고 별도의 구성 요소로 구현됩니다.(이런 점은 padding이 모든 레이아웃 컴포넌트의 코어에 내장되어있는 전통적인 다른 APIs들과는 대조됩니다). 그렇기때문에 하나 예를 들어보면, 위젯을 가운데에 놓고 싶다면, Align 속성을 조절하는 것이 아니라, Center 위젯을 사용하면 됩니다.

 

padding, alignment, rows, columns, grids를 지원하는 위젯들이 있습니다. 이런 위젯들은 스스로를 나타내는 비쥬얼적인 기능은 없습니다. 이 위젯들의 목적은 다른 위젯들의 레이아웃을 조절하는 것입니다. Flutter는 이런 결합적인 접근을 이용하는 유틸리티 위젯들도 가지고 있습니다.

 

예를들면, 가장 흔한 위젯인 Container는 layout, painting, positioning, sizing을 담당하는 많은 위젯들로 만들어집니다. 구체적으로, Container는 소스코드에서 볼 수 있듯이 LimitedBoxConstrainedBoxAlignPaddingDecoratedBox, 그리고 Transform로 만들어 집니다. 

 

Flutter의 정의적 특징은 위젯의 소스를 끝까지 파고들어가서 시험해 볼 수 있다는 것 입니다. 그렇기 때문에, 사용자화된 효과를 만들기 위해 Container를 서브클래싱하는 것 보다, 다른 위젯과 합치거나, Container를 기반으로 다른 새로운 위젯을 만드는 게 좋습니다.

Building widgets

새로운 element tree를 반환하기 위해 build() 함수를 오버라이딩하므로써 우리는 위젯의 외관을 결정지을 수 있습니다. 이 트리는 위젯의 유저 인터페이스 부분을 보다 구체적인 용어로 나타냅니다. 예를 들면, 툴바 위젯은 text 그리고 various buttons들로 구성된 horizontal layout을 반환합니다. 필요에 따라, 프레임워크는 트리가 렌더링 가능한 구체적인 객체에 의해 완전히 설명될 때까지 각 위젯을 빌드하도록 재귀적으로 요청합니다. 그 후 프레임워크는 그 랜더링 가능한 객체를 랜더링 가능한 객체의 트리에 합칩니다.

 

위젯의 build 함수는 부작용이 거의 없습니다. 함수가 build 하게끔 요청될 때마다, 위젯이 이전에 무엇을 반환했던지 상관 없이, 위젯의 새로운 트리를 반환해야합니다. 프레임워크는 렌더 객체 트리를 기반으로 호출해야 하는 build 메서드를 결정하기 위해 많은 리프팅 작업을 수행합니다. 이 과정에 대해 더 자세히 알고 싶다면 Inside Flutter topic을 참고하세요.

 

랜더된 프레임에서, Flutter는 위젯의 build() 메소드의 호출에 의해 변화된 상태가 변경시킨 UI 부분을 재생성 할 수 있습니다. 그러므로 build 메소드가 빨리 반환하는 것이 중요하고, 헤비한 계산 작업은 비동기적 방식으로 처리된 후 build 메서드에서 사용 할 수 있게 상태의 일부로 저장되어야 합니다.

 

비교적으로 나이브한 접근법에서도 이런 자동화된 비교는 좋은 성능을 내며 꽤 효과적입니다. 그리고 build 함수의 디자인은 유저 인터페이스를 한 상태에서 다른 상태로 업데이트하는 복잡한 작업 대신 위젯이 무엇으로 만들어졌는지 선언하는 데 초점을 맞추어 코드를 단순화합니다.

Widget state

이 프레임워크는 주로 사용되는 위젯 클래스인 stateful, stateless 위젯을 가지고 있습니다.

 

많은 위젯들은 변할 수 있는 상태를 가지지 못합니다: 그 위젯들은 시간이 지나도 변하는 속성을 가지지 못합니다.(예를 들면, 아이콘, 라벨). 이런 위젯들은 StatelessWidget을 서브클래스 합니다.

 

그러나, 만약 위젯의 유니크한 특성들이 다른 요소들이나 유저와의 상호작용에 의해 변화되어야 한다면, 그 위젯은 stateful 합니다. 예를 들어, 만약에 유저가 버튼을 누르면 증가하는 카운터가 있는 위젯에서 카운터의 값이 해당 위젯의 상태 입니다. 값이 변할 때, UI의 해당 값과 연관이 있는 부분이 다시 빌드 되어야 합니다. 이런 위젯들이 StatefulWidget을 서브클래스하고(그 위젯은 스스로 불변이기 때문에) 그 위젯들은 State을 서브클래스하는 분리된 클래스에 변할 수 있는 상태를 저장합니다. StatefulWidgets은 build 메소드를 가지지 않지만, 그 위젯들의 State 객체를 통해 유저 인터페이스가 빌드 됩니다.

 

State 객체를 변형시킬 때 마다, State의 build 메서드를 다시 호출하므로써, 프레임워크에게 유저 인터페이스를 업데이트하라고 알리기 위해 setState()를 반드시 호출해야 합니다.

 

분리된 state와 widget 객체들은 다른 위젯들이 상태를 잃어버릴 걱정없이 stateless, stateful 위젯을 정확히 같은 방식으로 다룰 수 있게 해줍니다. 상태를 유지하기 위해 자식을 잡아놓는 대신, 부모는 자식의 지속적인 상태를 잃지 않고 언제든지 자식의 새로운 인스턴스를 만들 수 있습니다. 이 프레임워크는 기존 상태 객체를 찾고 적절한 경우 재사용하는 모든 작업을 수행합니다.

State management

많은 위젯들이 상태를 가질 수 있다면, 시스템상에서 상태가 어떻게 관리되고, 전달될까요?

 

다른 클래스들에서와 마찬가지로, 해당 클래스의 데이터를 초기화 하기위해 생성자를 사용 할 수 있습니다, 그러므로써 build() 메서드는 모든 자식 위젯들이 필요로하는 데이터들과 함께 인스턴스화 될 수 있게 합니다.

@override
Widget build(BuildContext context) {
   return ContentWidget(importantState);
}

그러나, 위젯 트리가 깊어질수록 상태 정보를 트리 계층의 위 아래로 넘기는 것은 복잡한 일이 됩니다. 그렇기에, 세번째 위젯 타입인, InheritedWidget은 공유하는 조상 위젯의 데이터를 쉽게 가져올 수 있게 해줍니다. InheritedWidget을 사용하여, 다음 예 처럼 위젯 트리에서 공통된 조상을 감싸는 상태 위젯을 생성할 수 있습니다.

 

GradeWidgetExamWidget 객체들이 StudentState의 데이터를 원할 때마다, 다음과 같은 코드로 접근이 가능합니다.

final studentState = StudentState.of(context);

of(context) 호출은 build context(현재 위젯의 위치를 알려줌)를 가져오고, StudentState 타입과 매치되는 트리 안에서 가장 가까운 조상을 반환합니다. InheritedWidget들은 상태가 변할 때 Flutter가 이 함수를 사용하는 자식 위젯들의 재구성을 트리거 해야 할지 정하기 위해 호출하는 updateShouldNotify() 메소드를 제공합니다.

 

Flutter는 공유하는 상태를 위해 프레임워크의 부분으로 광범위하게 InheritedWidget색상이나 type styles 같은 속성들을 포함하며 어플리케이션의 전반적으로 사용하는 visual theme 같은 상태에 사용합니다. MaterialApp build() 함수는 빌드 할 때 트리에 theme을 삽입하고, 더 깊은 계층 구조에서 위젯은 .of() 메소드를 사용하여 관련된 테마 데이터를 사용할 수 있습니다.

Container(
  color: Theme.of(context).secondaryHeaderColor,
  child: Text(
    'Text with a background color',
    style: Theme.of(context).textTheme.headline6,
  ),
);

이런 접근법은 페이지 라우팅을 제공하는 Navigator에서도 사용되며, 방향, 차원, 밝기 같은 스크린 메트릭스들에 대한 접근을 제공하는 MediaQuery에서도 사용됩니다.

 

어플리케이션이 커지면, stateful 위젯을 사용하고, 생성하는 비용을 줄이는 더 진화된 상태 관리 접근법들을 더 사용하고 싶어집니다. 많은 Flutter 앱들은 InheritedWidget 주변을 감싸는 provider 같은 유틸리티 패키지를 사용합니다.

Flutter의 레이어드된 아키텍쳐에는 flutter_hooks 패키지 같이 상태의 변화를 UI에 구현하기 위한 대안이 되는 접근법의 사용도 가능합니다.

Comments