Why?
Flutter는 구글에서 개발한 크로스 플랫폼 앱 개발 프레임워크입니다. Flutter는 Android, iOS 앱 제작과 웹페이지 제작이 모두 가능합니다. Flutter는 자체적인 엔진을 사용해 UI를 그리기 때문에 OS에 관계없이 일정한 디자인을 화면에 그려냅니다. 그리고 위젯이라는 개념을 사용합니다. 시중에 출시된 모바일 앱을 살펴보면 화면을 구성하는 요소가 대체적으로 비슷합니다. 예를 들어, 사진과 글이 포함된 카드, 격자 형태의 사진 타일, 하단 화면 이동 버튼 등 공통적으로 사용되는 요소들이 있는데, Flutter는 이러한 요소들을 위젯이라는 객체로 만들어 두었습니다. 개발자는 필요한 위젯을 가져와 세부적인 디자인 요소를 추가하는 식으로 화면을 구성하게 됩니다.
Flutter 구현 예시
언어: Dart
Flutter는 Dart라는 언어를 사용합니다. Dart는 한때 프로그래머들에게 최악의 언어로 선정된 적도 있습니다. 코드를 작성해 보면 괄호가 늘어지면서 가독성이 떨어진다는 느낌을 많이 받았습니다. 게다가 Flutter는 HTML처럼 위젯 안에 위젯 안에 위젯을 넣는 형태로 작성되기 때문에 코드가 길어질수록 구조를 파악하는데 어려움이 있었습니다. 다행인 점은 Dart가 객체 지향 언어라서 위젯을 여러 개의 객체로 나누어 코드를 작성하면 체계적인 코드를 구성할 수 있었습니다.
코드 예시
Flutter 프로젝트를 생성하면 기본적으로 제공되는 코드가 있습니다. 해당 코드의 주석과 내용은 아래와 같습니다.
// Code: Flutter
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// 해당 위젯은 프로젝트의 root 위젯입니다. (처음 실행되는 위젯)
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// 이곳은 프로젝트의 테마를 설정합니다.
//
// "primarySwatch"는 MaterialApp 내에서 사용할 주요 색상을 설정합니다.
// 실행해보면 상단의 툴바가 파란색(blue)로 설정되는 것을 볼 수 있습니다.
// "green"으로 수정해보면 초록색으로 바뀌는 것을 확인할 수 있습니다.
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
// 이 위젯은 앱의 홈페이지입니다. 동적인 위젯(StatefulWidget)이며, 화면에 보이는
// 요소들에 영향을 미치는 상태(state)를 가집니다.
//
// 해당 클래스는 상태(state)를 가지고 있습니다. 상태(state)는 부모 클래스에 필요한
// 요소로, 동작(method)을 실행하는데 사용됩니다. 이 코드의 경우, "MyHomePage"(부모)에
// 필요한 "title"을 뜻합니다. 그리고 이러한 요소는 항상 "final"로 선언합니다.
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// "setState"는 상태(state)가 바뀐다는 것을 의미합니다. 아래의 메서드가 실행되면
// 변경된 요소가 화면에 반영됩니다. 만약 "_counter"를 "setState" 없이 사용한다면
// 메서드가 다시 생성되지 않게 되고. 결국 화면에는 아무 반응도 일어나지 않습니다.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// 해당 메서드는 "setState"가 호출될 때마다 실행됩니다. (위 "_incrementCounter"가
// 그 예시입니다.)
//
// 플러터는 메서드를 빠르게 업데이트하는데 최적화되어 있습니다. 그래서 위젯을 하나하나
// 바꿀 필요 없이 업데이트가 가능합니다.
return Scaffold(
appBar: AppBar(
// "App.build" 메서드로 만들어진 "MyHomePage" 객체로부터 값을 받아와
// "AppBar"의 제목을 설정할 수 있습니다.
title: Text(widget.title),
),
body: Center(
// "Center"는 레이아웃을 설정하기 위한 위젯입니다. 하나의 값(child)를 받으며
// 부모 요소의 중앙에 위치하게 됩니다.
child: Column(
// "Column"도 레이아웃을 위한 위젯입니다. 리스트 형태의 자식(children)
// 요소들을 수직으로 정렬합니다. 가로는 자식 요소를 담을 수 있는 최소한의
// 크기로 생성되며, 세로는 부모 요소와 같은 높이로 설정됩니다.
//
// "Column"은 스스로를 배치할 수 있는 여러 설정을 가지고 있으며, 자식 요소의
// 크기를 조정하는 다양한 설정도 포함하고 있습니다. "Column"의 경우,
// "mainAxisAlignment"를 이용해 자식 요소를 세로 방향으로 중앙 정렬할 수
// 있습니다. Column(세로)의 주축(main axis)이 세로 방향이기 때문에 세로
// 정렬에 대한 설정을 담당하는 것입니다. ("cross axis"(반대 축)에 대한
// 설정은 가로 방향이 됩니다.)
//
// **추가 설명**
// "Column"과 반대되는 요소로 "Row"가 있으며, "Row"는 가로 정렬을 담당합니다.
// "Row"(가로)의 경우, "mainAxisAlignment"를 이용해 자식 요소의 가로 방향에
// 대한 설정을 할 수 있으며, "cross axis"는 세로 축이 됩니다.
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),
),
);
}
}