Flutter
구글에서 개발하는 크로스 플랫폼 프레임워크. 한글로는 '플러터'로 주로 쓴다. 안드로이드와 iOS를 지원하며, 2019년 구글 I/O에서는 웹 개발까지 할 수 있는 Flutter Web 프레임워크까지 들고 나왔다. 즉 안드로이드, 아이폰, 웹에서 돌아가는 앱을 하나의 코드로 전부 개발할 수 있다는 이야기다. 물론 정말 이렇게 할 수 있는 범위는 기기 공통으로 제공하는 기능으로 한정하며 특정 OS에서만 지원되는 기능이나 하드웨어를 컨트롤하려면 그에 맞는 네이티브 코드가 필요하다. 이 부분은 안드로이드라면 코틀린, iOS라면 스위프트를 사용해서 붙일 수 있도록 Flutter에서 지원하고 있다. 구글의 차세대 OS로 점쳐지고 있는 Fuchsia도 UI 단에서는 Flutter를 기반으로 할 것으로 보인다. 즉 현재의 행보는 모바일과 데스크톱을 걸친 다양한 환경들을 Fuchsia로 통합하기 위한 포석으로 볼 수 있다.
특징
기기에 독립된 사용자 인터페이스
지금까지 모바일을 겨냥한 크로스 플랫폼 프레임워크는 아파치 코르도바처럼 웹 뷰를 사용하되 여기에 사용되는 자원을 각 기기에 미리 심는 방식, 즉 앱이 로컬 웹 서버로 돌아가도록 해서 웹 앱보다는 빠르고 OS 관계 없이 똑같은 인터페이스를 제공하거나[1], 리액트 네이티브나 자마린처럼 각 OS의 네이티브 기능을 최대한 활용해서 인터페이스는 조금 차이가 나지만 빠른 속도를 추구하는 방법이 있는데, Flutter는 이들 둘과는 아예 다르다. 각 OS의 네이티브 그래픽 기능을 최대한 활용하되 각 OS의 유저 인터페이스를 무시하고 몽땅 Flutter가 자체 제공한다. 즉, 각 OS는 그림 그릴 캔버스만 제공하고 그 위에 뭘 표현할지는 Flutter의 렌더링 앤진인 skia가 다 그려버리는 것. 그 때문에 OS에 관계 없이 똑같은 모습의 유저 인터페이스를 제공하며, iOS에서 구글의 머티리얼 디자인을 100% 활용한 앱을 실행시킬 수 있으며 반대로 안드로이드에서 iOS 스타일의 쿠퍼티노 디자인 앱을 사용할 수도 있다. Flutter는 두 가지 디자인을 기본 제공한다.
네이티브 UI를 활용하는 것에 비해서 속도 면에서 불리하지 않은가 싶을 수 있는데 테스트 결과를 보면 상당히 준수하게 나온다. 렌더링 엔진이 무척 좋은 듯. 사실 모바일 게임들은 상당수가 자체 인터페이스를 사용한다. 다만 Flutter 웹은 HTML와 CSS를 사용한다. 이쪽은 이 두 가지만 가지고도 어지간히 다 할 수 있기도 하고[2] 웹 표준을 무시하면 매장당하는 문제도 있다. Flutter를 웹 개발에 쓰면 사실상 HTML나 CSS 코딩 없이 Dart만으로 다 만들 수 있다.[3]
핫 리로드
핫 리로드(Hot Reload) 기능을 제공하여 코드가 변경된 부분이 바로바로 디버그 모드의 앱에 반영되는 것도 특징이다. 네이티브도 코드가 변경되었을 때 전체 컴파일 및 패키징 없이도 변경 내용을 반영하는 기능이 있지만 Flutter의 핫 리로드는 이보다 훨씬 빠르고 간편하다. 애초에 Flutter 설계 때부터 이 기능을 염두에 두었다고 한다. 다만 핫 리로드로 바로 변경이 적용되는 부분은 위젯을 그리는 부분, 특히 Widget.build() 메서드 안에 있는 코드이며, 조건에 따라 완벽하게 올바른 실행을 보장하지는 않기 때문에 코드가 문제가 없는데도 오류가 나거나 이상하게 동작할 수 있다. 핫 리로드를 했을 때 오류가 나거나 코드는 바꿨는데 외관이나 동작이 달라지지 않았다면 핫 리로드가 아닌 핫 리스타트로 재실행을 시켜보는 게 좋다. 또한 핫 리로드나 핫 리스타트는 기기의 접속을 끊으면 변경된 내용이 반영되지 않으므로 변경된 내용을 기기에 완전히 심기 위해서는 실행을 중단시킨 다음에 다시 실행시키거나 프로파일 모드 또는 릴리즈 모드로 실행시켜야 한다.
위젯
Flutter에서는 모든 요소가 위젯(Widget)이다. 페이지조차도 위젯이다. 심지어는 아예 가장 기본이 되는 App 클래스[4] 조차도 StatelessWidget 클래스에서 상속 받는다! 따라서 Flutter의 UI는 위젯의 트리 구조다. 위젯은 상태 변화가 없는 Stateless Widget과 상태 변화를 감지하는 Stateful Widget 두 가지로 나뉜다. 여기서 위젯 자체는 불변(immutable)이다. 따라서 위젯 자체는 업데이트할 수 없다. 그렇다면 Stateful Widget은 상태 변화에 어떻게 대응하나? Flutter가 채용하는 방식은 위젯과 상태를 다른 클래스로 분리한다. 즉 위젯을 그리는 build() 메소드를 위젯이 아닌 상태 (state) 클래스가 가지고 있고, 이 상태 클래스를 immutable인 위젯에서 createState() 메소드에서 만들어 가지고 있는 방식으로 문제를 해결한다. 위젯의 상태가 변하면, 즉 위젯의 모양에 영향을 미치는 데이터에 변화가 생기면 상태 클래스에 있는 build()가 위젯을 다시 그린다.
어떤 상태 변화가 있을 때마다 build()를 불러서 전체 위젯을 다시 그린다면 굉장히 효율이 떨어질 것처럼 보이지만 의외로 좋은데, 실제로는 Flutter 엔진이 어떤 부분이 변했는가를 검사한 다음, 영향을 받는 위젯을 'dirty' 상태로 표시하고, 이 위젯들만 다시 그리는 방식으로 효율을 낸다. 만약 상태에 변화를 일으키는, 즉. 위젯을 다시 그려야 하는 dirty한 데이터 변경이 있다면 setState() 메서드를 호출해서 그 안에서 데이터를 변경해야 한다.
만약 UI에 영향을 미치는 데이터가 비동기적으로 변한다면 이는 Dart의 Future 또는 Stream 클래스를 이용해서 반영할 수 있다. 이 때에는 위젯의 build() 메서드 안에서 각각 FutureBuilder 또는 StreamBuilder 클래스를 사용해서 처리한다.
또한 UI를 구성하는 방식이 XML과 같은 외부 파일이 아니라 코드 자체 안에서 구현하는 방식이다. 페이지 자체도 위젯이기 때문에 위젯을 구성하는 build() 메소드 안에서 자식 위젯을 정의하고, 자식 위젯 안에서 자식 위젯을 정의하고... 이런 방식이다. 배치 방법, 마진이나 패딩 같은 것들도 모두 코드로 구현한다. 따라서 위젯의 구성이나 모양을 바꿀 때마다 코드를 다시 컴파일해야 하지만[5] 위에서 이야기한 핫 리로드 때문에 별 불편하지는 않긴 하다.
그밖에
각 OS의 네이티브에 붙어 있는 기능을 써야 할 때에는 그 부분만 네이티브로 개발해서 Flutter에 붙일 수도 있다. Flutter는 이를 위한 인터페이스를 제공한다. 단, 네이티브 부분은 안드로이드라면 자바나 코틀린, iOS라면 오브젝티브C나 스위프트와 같이 각자의 네이티브 개발 언어를 써야 한다. 카메라나 GPS처럼 공통으로 제공하는 하드웨어는 Flutter 제공 패키지로도 웬만큼은 다룰 수 있다.
Flutter의 사용자층이 넓어지면서 Flutter는 물론 Dart의 기능을 확장해 주는 패키지들도 빠른 속도로 늘어나고 있다. pub.dev 웹사이트에서 패키지를 검색해 볼 수 있다. 필요한 패키지는 pubspec.yaml 파일에 추가해서 flutter pub get 명령으로 받아올 수 있다.
성능
일반적으로 크로스 플랫폼으로 개발한 앱은 네이티브와 비교해서는 속도는 느리고, 메모리를 비롯한 자원은 많이 잡아먹는 것으로 알려져 있다. 이는 네이티브 UI를 최대한 활용하는 자마린이나 리액트 네이티브도 피해갈 수 없는 문제다. 그렇다면 Flutter는? 우크라이나에 소재를 둔 인베리타소프트라는 곳에서 크로스 플랫폼인 리액트 네이티브와 플러터, 그리고 네이티브로 iOS는 오브젝티브 C와 스위프트, 안드로이드는 자바와 코틀린을 사용해서 성능 실험을 해 보았다. 원문은 여기에서 확인할 수 있다. 메모리 위주 성능 측정을 위해서는 가우스-르장르드 알고리즘(Gauss–Legendre algorithm)을, CPU 위주 성능 측정에는 보웨인 알고리즘(Borwein algorithm)을 사용했다. 결과는?
iOS | 안드로이드 | |||
---|---|---|---|---|
가우스-르장드르 | 보웨인 | 가우스-르장드르 | 보웨인 | |
리액트 네이티브 | 2992.0 | 582.1 | 3289.3 | 821.9 |
Flutter | 188.7 | 179.5 | 272.5 | 285.2 |
스위프트 | 218.3 | 35.0 | — | — |
오브젝티브 C | 127.5 | 16.7 | — | — |
자바 | — | — | 222.0 | 143.0 |
코틀린 | — | — | 223.0 | 144.0 |
수치는 같은 알고리즘으로 만든 앱을 실행시켰을 때의 처리시간을 밀리초 단위로 보여주는 것이므로 숫자가 낮을수록 성능이 좋다.
일단 리액트 네이티브는 Flutter에 비해서 성능이 많이 떨어지는 모습을 보여준다. 메모리 위주의 테스트에서는 10배 이상 차이가 나며, CPU 위주 테스트에서도 3배 이상 느리다. Flutter와 네이티브를 비교해 보면 아무래도 네이티브가 좋긴 하지만 그래도 리액트 네이티브에 비하면 훨씬 낫다. 게다가, iOS에서 메모리 위주의 테스트를 했을 때에는 스위프트보다 오히려 빠르다! 스위프트 의문의 1패 부수적으로 iOS 환경에서는 스위프트가 오브젝티브C가 확실히 느린 것으로 나타나는 반면, 안드로이드 환경에서 자바와 코틀린은 별 차이가 없다. 이렇게 본다면 성능 면에서 조금이라도 속도가 빨라야 하는 정도가 아니라면 Flutter가 괜찮은 선택으로 보인다.
단점
문제점이라면 Flutter의 개발 언어인 Dart가 별 인기가 없다는 것... 원래는 자바스크립트를 대체할 언어로 들고 나왔지만 시장에서 완전히 파묻혔으며, 자바스크립트의 대안은 MS가 들고 나온 타입스크립트가 잡고 있는 실정이다. Dart나 타입스크립트나 간단한 컴파일을 통해 자바스크립트로 번역되기 때문에 이미 상당히 성숙해 있으며 개발자 저변도 넓은 자바스크립트를 대체한다기보다는 보완하는 역할 정도인데, 여기서도 타입스크립트한테 완전히 밀려서 쓰레기 취급 받던 언어다. 기능 확장을 위한 패키지들도 파이썬이나 자바스크립트에 비하면 형편없이 부족하다. 아무튼 구글에서 그냥 파묻기에는 아까웠던 건지 Flutter를 통해 Dart를 부활시킨 셈이다.
Dart의 문법은 자바와 비슷한 편이고, 진입장벽이 낮기 때문에 자바스크립트나 자바 같은 언어들을 잘 알고 있다면 Flutter를 쓰기 위해서 Dart를 배우기 위해 시간을 많이 들일 필요는 없다. 그리고 Dart 자체도 기능이 형편 없거나 배우기가 지랄맞거나 한 언어는 절대로 아니며 처음에는 쉬워보이지만 쓰면 쓸수록 헷갈리기 쉬운 자바스크립트에 비하면 에러가 적고 깔끔한 코드를 만들 수 있다. 단지 자바스크립트를 대체하겠다는 욕심이 너무 컸던 것도 있고, 다른 웹 브라우저 입장에서 보면 크롬 웹 브라우저를 가지고 있는 구글에게 좋은 일을 굳이 해줄 이유도 없으니 이들에게 외면 받은 탓이 크다. 자세한 내용은 Dart 항목 참조. Flutter 덕분에 Dart의 사용층이 넓어지고 있는 추세이고 이를 지원하는 개발 도구도 늘어나고 있으며, 여러 가지 기능을 손쉽게 추가시킬 수 있는 패키지도 빠르게 늘고 있기 때문에 Dart 언어 자체도 여러 가지로 쓰기가 좋아지고 있으며 악평도 줄어들고 있다.
또한 각각의 운영체제 및 하드웨어 쪽으로 특화된 기능을 활용하는 데에는 한계가 있다. 이건 Flutter만이 아닌 모든 크로스 플랫폼의 한계라고 봐야 할 문제. 이런 부분은 각 운영체제마다 필요한 대로 네이티브 코드를 붙일 수 있다. 다만 카메라나 위치정보처럼 공통적인 인터페이스를 제공하는 패키지들도 늘어나고 있다.
국제화 부분도 불편한 점으로, 문자열 자원을 언어별로 관리하는 게 안 되는 건 아니지만 안드로이드나 iOS 네이티브로 개발할 때보다는 많이 번거롭고 툴의 지원도 별로 좋지 않다. 이런 부분들은 시간이 지나면 해결이 되긴 하겠지만.
용량도 아직은 문제인데, 자체적으로 사용자 인터페이스를 렌더링하는 엔진이 들어가므로 네이티브보다는 클 수밖에 없다.
개발 환경
안드로이드 스튜디오, IntelliJ IDEA[6], 비주얼 스튜디오 코드에서 사용할 수 있다. 이들 모두는 기본 제공 기능이 아니며 플러그인을 설치하면 쓸 수 있다. 모두 크로스 플랫폼 개발 환경이므로 윈도우, 맥, 리눅스에서 개발할 수 있다. 다만 iOS용 실행 패키지를 빌드하려면 맥과 Xcode가 있어야 한다. 실행은 안드로이드라면 실제 기계가 없어도 안드로이드 SDK에서 제공하는 에뮬레이터를 사용하면 되지만 iOS는 에뮬레이터도 맥에서만 돌릴 수 있으므로 맥과 Xcode가 필요하다.
각주
- ↑ 하지만 운영체제의 기본 웹브라우저를 사용하므로 웹브라우저에 따라 모양이 좀 다를 수는 있다.
- ↑ 아파치 코르도바가 내장한 HTML을 UI에 사용하는 방식으로 크로스 플랫폼 모바일 앱을 구현할만큼 HTML과 CSS 만 있으면 웬만한 UI는 다 구현할 수 있다.
- ↑ 애초에 Flutter의 구조가 웹 구조에 힌트를 얻은 것이기도 하다. 자바 쪽으로 보면 모든 것을 위젯 구조로 보고 코드로 몽땅 처리하는 데다가 stateless와 stateful 개념을 가지고 있는 아파치 위켓 프레임워크와 많은 면에서 닮아 있다.
- ↑ App 클래스 객체를 main() 함수에서 runApp() 함수 매개변수로 전달하는 것이 모든 Flutter 앱의 시작 지점이다.
- ↑ UI 레이아웃을 XML로 분리했다고 해도 어차피 빌드를 다시 하긴 해야 하며, 이 과정에서 XML을 코드로 컴파일하는 과정이 있는 방식이 대다수라 컴파일 효율이 낫다고 보기도 어렵다.
- ↑ 무료로 쓸 있는 커뮤니티 에디션에서도 쓸 수 있다.