본문 바로가기
App Programming/Flutter

[Flutter] main.dart 분석하기

by 젠틴 2022. 4. 3.

1. import

import 'package:flutter/material.dart';

Flutter의 핵심이라고 할 수 있는 material 라이브러리를 불러옵니다.

material 라이브러리 안에는 Material Design 구현을 위한 많은 코드들이 들어있습니다.

그렇다면, Material Design이란 무엇일까요?

https://material.io/

 

Material Design

Build beautiful, usable products faster. Material Design is an adaptable system—backed by open-source code—that helps teams build high quality digital experiences.

material.io

Material Design은 Mobile, Desktop 등의 다양한 디바이스 플랫폼을 아우르는 Google의 디자인 가이드라인입니다. 플랫 디자인의 장점을 살리면서도 빛에 따른 종이의 그림자 효과를 이용하여 입체감을 살리는 디자인 방식을 말합니다.

2. void main()

void main() {
  runApp(const MyApp());
}

main 함수는 App을 시작할 때 처음 순서로 실행되는 함수입니다.

main 함수의 선언부를 살펴보면, 반환 타입은 void로 반환하는 값이 없으며 함수 이름은 main입니다.

입력받는 매개변수(parameter)는 없습니다.

 - 참고로 Dart 프로그래밍 언어에서는 접근제어자가 없습니다. 언더바 기호 _로 public과 private을 구분합니다.

 

다음으로 main 함수의 구현부를 살펴보겠습니다.

구현부는 중괄호 {}로 감싸진 블록을 의미하며, 함수의 고유 기능을 수행하는 명령문의 집합입니다.

블록의 내용을 보면, runApp 함수에 MyApp Widget을 인자(Argument)로 입력합니다. Widget은 하나의 클래스입니다.

 - const는 간단히 설명드리면, 변하지 않고 고정된 값을 의미합니다.

   여기선 MyApp Widget 값이 변할 일 없이 사용된다는 정도로 이해하시면 좋을 거 같습니다.

runApp 함수는 입력받은 Widget으로 Widget Tree의 최상위 Widget(Root Widget)을 만드는 역할을 합니다.

그럼 다음으로 Flutter의 Widget Tree 구조에 대해 알아보도록 하겠습니다.

3. Widget Tree

Flutter에서 Widget은 Text, Icon, Image, Button, List, ListView와 같은 UI Component를 구성하는 클래스입니다. Flutter App은 Widget과 Layout을 조합하여 Widget Tree를 구성하고 이를 UI로 보여줍니다.

main.dart의 코드는 다음과 같은 Widget Tree의 형태로 구성됩니다.

<main.dart의 Widget Tree / 출처 : https://baransel.dev>

앞서 설명드린 대로, main 함수의 runApp 함수는 MyApp Widget으로 최상위 Widget(Root Widget)을 생성합니다.

최상위 Widget을 생성한 후에는 하위 Widget들을 생성하면서 App을 개발해야 합니다. Widget의 종류는 다양하지만, State를 관리하지 않느냐 하느냐를 기준으로 StatelessWidgetStatefulWidget으로 구분할 수 있습니다. main.dart 코드를 통해 이 두 가지 Widget에 대해 알아보도록 하겠습니다.

4. StatelessWidget

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

MyApp Widget은 StatelessWidget을 상속받습니다.

그렇다면 StatelessWidget이 무엇인지부터 알아보겠습니다.

 

StatelessWidget은 변경되지 않는 Widget입니다. 상태를 관리하기 위한 기능이 필요하지 않고 내용의 변화가 필요없는 정적인 페이지에서 활용할 수 있습니다.

 

그럼 StatelessWidget을 상속받은 MyApp Widget의 중괄호 {} 구현부를 살펴보겠습니다.

먼저, 생성자로 key 매개변수를 받습니다. 이 key 매개변수는 Widget의 State를 보존하거나 Widget을 유니크하게 식별할 때 주로 사용됩니다.

 

다음으로 상속받은 StatelessWidget의 build 함수를 override(덮어쓰기)한 코드를 확인해보겠습니다.

build 함수의 선언부를 살펴보면, 반환 타입은 Widget이며, 함수 이름은 build입니다.

입력받는 매개변수는 BuildContext 타입의 context입니다.

 

이어서 중괄호 {}로 감싸진 build 함수의 구현부를 살펴보겠습니다.

우선, MaterialApp이라는 Widget이 보입니다.

MaterialApp Widget도 하나의 클래스로써, 속성(attribute)과 메소드(method)를 가지고 있습니다.

title, theme, home 속성에 대해 값을 설정하는 것을 확인할 수 있습니다.

각 속성별 MaterialApp의 코드를 보면 다음과 같습니다.

final String title;

title 속성의 데이터 타입은 String입니다. 그렇기 때문에 Flutter Demo라는 문자열을 입력받을 수 있습니다.

final ThemeData? theme;

theme 속성의 데이터 타입은 ThemeData라는 Class 타입니다. 그렇기 때문에 ThemeData(primarySwatch: Colors.blue)라는 Class 데이터를 입력받을 수 있습니다. ThemeData Class도 primarySwatch라는 속성이 있고, 해당 속성은 Colors 클래스를 입력받을 수 있습니다.

 - 참고로 데이터 타입 마지막에 물음표 기호 ?를 붙여주면, 해당 변수는 null을 허용하겠다는 의미입니다.

   Flutter 2.0 버전이 되면서, Null Safety가 적용되었습니다.

   Null Safety란 원칙적으로 변수에 null 값이 들어오지 못하도록 함으로써 null로 인한 에러 발생을 예방하겠다는 것입니다.

   하지만 변수에 null 값이 들어오는 것을 허용해줘야 하는 경우도 있습니다. 그런 경우 ? 사용을 통해 null을 허용해줍니다.

final Widget? home;

home 속성의 데이터 타입은 Widget입니다. 앞서 설명드렸듯이 Widget도 Class 타입입니다. 그렇기 때문에 const MyHomePage(title: 'Flutter Demo Home Page')라는 Class 데이터를 입력받을 수 있습니다. MyHomePage도 title이라는 속성이 있고, 해당 속성은 String 타입을 입력받을 수 있습니다.

이렇게 속성이 설정된 MeterialApp이라는 Widget 타입을 최종 return(반환)하게 됩니다.

Emulator로 StatelessWidget이 구현된 화면을 확인해보겠습니다.

<StatelessWidget 구현 화면>

1) title: 'Flutter Demo'

- title은 OS별 다음과 같은 기능에서 표시가 됩니다.

  • 안드로이드 : 최근 사용한 앱
  • iOS : 앱 스위쳐 (20/02/11 업데이트 이후로 표시되지 않음)

2) theme: ThemeData(primarySwatch: Colors.blue)

- 파란색 테마가 적용된 것을 확인할 수 있습니다.

3) home: const MyHomePage(title: 'Flutter Demo Home Page')

- Flutter Demo Home Page라는 문자열이 표시된 것을 확인할 수 있습니다.

 

확인한 바와 같이, StatelessWidget은 한 번만 build하는 Widget입니다.

상태나 문자열 등의 변화가 필요없는 페이지에서 주로 사용됩니다.

그럼 다음으로 StatefulWidget에 대해 알아보도록 하겠습니다.

5. StatefulWidget

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

MyHomePage Widget은 StatefulWidget을 상속받습니다.

그렇다면 StatefulWidget이 무엇인지부터 알아보겠습니다.

 

StatefulWidget은 상태에 따라 변화하는 Widget입니다. setState 함수 호출을 통해 상태의 변화를 인지하고 반영합니다. 상태를 관리하기 위한 기능이 필요하고 변화가 필요한 동적인 페이지에서 활용할 수 있습니다.

 

그럼 StatefulWidget을 상속받은 MyHomePage Widget의 중괄호 {} 구현부를 살펴보겠습니다.

먼저, 생성자로 key와 this.title 매개변수를 받습니다. 여기서 this.title 앞에 required라는 것이 있습니다.

required는 의미 그대로 필수 매개변수라는 의미입니다. MyHomePage Widget을 사용할 때, 물음표 기호 ?(null 허용) 표시가 되어 있는 key 파라미터와 다르게, this.title 파라미터에 대해서는 인자를 꼭 입력해주어야 합니다.

입력된 this.title 인자는 final String title로 선언된 title 변수의 값으로 할당(저장)됩니다.

 

다음으로 상속받은 StatefulWidget의 createState 함수를 override(덮어쓰기)한 코드를 확인해보겠습니다.

createState 함수의 선언부를 살펴보면, 반환타입은 State이며 타입 매개변수는 MyHomePage입니다.

이는 MyHomePage를 상속받은 State 클래스가 반환 타입이라는 의미입니다. 함수 이름은 createState이며, 입력받는 매개변수는 없습니다. 

 

이어서 createState 함수의 구현부를 살펴보겠습니다.

앞서 살펴본 문법들과는 다르게 중괄호 {}가 보이지 않고, 화살표 기호 =>가 보입니다.

이를 화살표 문법(Arrow Function)이라 합니다. 함수를 보다 간결하게 표현하기 위해 사용합니다.

화살표 문법을 원래의 문법으로 변환해보겠습니다.

State<MyHomePage> createState() => _MyHomePageState();
State<MyHomePage> createState() {
  return _MyHomePageState();
}

이와 같이, 화살표 문법은 구현부를 나타내는 중괄호 {}와 return이 합쳐진 의미임을 알 수 있습니다.

내용을 보면, createState 함수는 별도 처리하는 기능은 없고 _MyHomePageState 클래스만 return하는 역할을 합니다.

 

그럼 이어서 _MyHomePageState 클래스에 대해 알아보도록 하겠습니다.

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

_MyHomePageState 클래스는 State<MyHomePage> 클래스를 상속 받습니다.

앞서 설명드린 대로 createState 함수는 _MyHomePageState 클래스를 return 합니다.

보시다시피 createState 함수에서 선언한 반환 타입과 return 값인 _MyHomePageState 클래스의 타입이 동일합니다.

이처럼 함수 선언부의 반환 타입과 구현부의 return 데이터 타입은 일치해야 하며, 그렇지 않으면 오류가 발생합니다.

 

_MyHomePageState 클래스는 _counter 멤버변수, _incrementCounter 함수, build 함수로 구성되어 있습니다.

먼저, _counter 멤버변수는 int 타입으로 숫자 0을 값으로 가지고 있습니다.

언더바 _ 기호가 되어 있는 변수나 함수는 이를 정의한 동일 파일(main.dart) 내에서만 접근하거나 사용 할 수 있습니다.

 

다음으로 _incrementCounter 함수를 살펴보겠습니다.

_incrementCounter 함수의 선언부를 살펴보면, 반환 타입은 void이며, 함수 이름은 _incrementCounter입니다.

입력받는 매개변수는 없습니다.

 

중괄호 {}로 감싸진 _incrementCounter 함수의 구현부를 살펴보겠습니다. 여기서 중요한 setState 함수가 나옵니다.

setState 함수는 비동기 함수이며, 인자로 콜백 함수(Callback Function)를 받습니다.

여기서 콜백 함수는 익명 함수의 형태인데, 익명 함수는 이름이 없는 함수로 즉시 실행이 필요한 경우에 사용합니다.

void _incrementCounter() {
  setState(() {
    _counter++;
  });
}

이를 기반으로 코드를 다시보면, _incrementCounter 함수가 호출되면 setState 함수를 호출하고 setState 함수는 _counter 멤버변수의 값을 1 증가시킵니다.

이 이후, setState의 가장 중요한 기능이 동작합니다. 바로 build 함수를 호출하는 것입니다.

앞서 StatelessWidget에 대해 설명드렸던 내용 중 하나가 바로 한 번만 build하는 Widget이였다는 점입니다.

setState 함수는 호출될 때마다, 화면을 구성하는 이 build 함수를 몇 번이고 호출하게 됩니다.

이 말은 setState 함수가 동작할 때마다 화면을 재구성하면서 변경사항을 반영한다는 뜻입니다.

이 점이 바로 StatelessWidget과 StatefulWidget의 가장 큰 차이점입니다.

 

마지막으로 중괄호 {}로 감싸진 build 함수의 구현부를 살펴보겠습니다.

우선, Scaffold라는 Widget이 보입니다.

Scaffold Widget도 하나의 클래스로써, 속성(attribute)과 메소드(method)를 가지고 있습니다.

appBar, body, floatingActionButton 속성에 대해 값을 설정하는 것을 확인할 수 있습니다.

각 속성별 코드를 간단히 살펴보면 다음과 같습니다.

appBar: AppBar(
  title: Text(widget.title),
),

appBar는 App 상단에 위치한 Bar를 의미합니다.

title 속성에 문자열을 할당하여 제목을 설정할 수 있습니다.

widget.title는 MyHomePage 클래스의 title 멤버변수를 의미합니다.

body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      const Text(
        'You have pushed the button this many times:',
      ),
      Text(
        '$_counter',
        style: Theme.of(context).textTheme.headline4,
      ),
    ],
  ),
),

body는 App의 본문을 의미합니다.

컬럼을 생성하여 하위 2개의 Text Widget을 추가했습니다.

첫 번째 Text에는 'You have pushed the button this many times : '라는 고정된 문자열을 작성했습니다.

두 번째 Text에는 _MyHomePageState 클래스의 _counter 멤버변수 값을 작성했습니다.

전체적으론 Center Widget으로 수직 가운데 정렬, mainAxisAlignment.center 속성 설정으로 수평 가운데 정렬을 했습니다.

floatingActionButton: FloatingActionButton(
  onPressed: _incrementCounter,
  tooltip: 'Increment',
  child: const Icon(Icons.add),
), //

floatingActionButton은 App 하단의 버튼 의미합니다.

onPressed 속성은 콜백 함수 데이터 타입으로, 누르면 동작하는 기능을 합니다. 버튼을 누르면 앞서 설명드린 setState 함수가 포함된 _incrementCounter 함수가 동작하면서, _counter 멤버변수 값이 1씩 증가하도록 설정했습니다.

하위 Widget으로 + 모양의 Icon Widget을 추가했습니다.

 

이렇게 속성이 설정된 Scaffold라는 Widget 타입을 최종 return(반환)하게 됩니다.

Emulator로 StatefulWidget이 구현된 화면을 확인해보겠습니다.

<+ 버튼 클릭 전>
<+ 버튼 클릭 후>

확인한 바와 같이, StatefulWidget은 setState 함수가 동작할 때마다 build를 하는 Widget입니다.

상태나 문자열 등의 변화가 필요한 페이지에서 사용됩니다.

 

이상으로 flutter에서 기본으로 제공하는 main.dart 코드 분석을 마치도록 하겠습니다.

다음 글부터는 App을 하나씩 개발해보면서 Flutter에 대해 알아가보도록 하겠습니다.

댓글