Front-end/Flutter

[글또] React 개발자와 Flutter 찍먹하기

성중 2023. 3. 6. 17:21
JavaScript 개발자와 Dart 찍먹하기를 먼저 읽고 와 주시면 더욱 좋습니다.

 

안녕하세요! 지난번 글에 이어 이번에는 React 개발자와 Flutter 찍먹하기로 돌아왔습니다

본의 아니게 시리즈처럼 되어버렸는데.. 다음 주제는 아직 생각한 것이 없네요 😅

 

이번 글도 Flutter에 흥미가 있는 개발자, 거기에 React 사용 경험까지 있다면 더 재미있게 읽으실 것 같아요!

물론 React에 대한 지식이 없더라도 이해 불가능한 수준은 아닙니다

 

그럼 거두절미하고 Flutter 찍먹, 시작해보겠습니다!

 


Flutter

flutter.dev

구글에서 개발한 Flutter 프레임워크는 Dart 언어를 사용하며, 하나의 코드베이스로 다양한 플랫폼에서 동작하는 오픈소스 프레임워크입니다! 처음에는 Android와 iOS 정도만 지원하다가 웹 및 다양한 운영체제, IoT까지 영역을 확장하고 있으며, Flutter / Dart / Firebase / Android / Chrome 모두 구글에 소속되어 시너지 효과를 내며 활발하게 개발되고 있습니다 😎

 

Architectural layers

 기존 네이티브 프레임워크(iOS, Android)의 개발이 운영체제와 직접 소통하는 구조라면, Dart 코드는 Flutter 프레임워크 상에 자체 포함된 애니메이션, Painting, Gestures 등을 이용합니다! 비디오 게임과 비슷하게 운영체제를 거치지 않고 C/C++로 작성된 엔진이 동작하며 UI를 렌더링하고, 운영체제는 단순히 엔진을 동작시키는 역할만 합니다

 

진주 (= Dart 코드 및 Flutter 프레임워크 앱) / 조개 껍데기 (= 엔진, VM) / 바다 (= 플랫폼)

조금 복잡하지만 근본적으로 Flutter와 React는 모두 UI를 렌더링하고 상호작용하는 방식에서 컴포넌트 기반 아키텍처를 따르며 각각의 방식으로 성능을 최적화합니다

 

즉, 동작 원리상의 차이점은 언어 및 UI 렌더링 방식이라고 볼 수 있습니다! Flutter는 Dart 언어와 Skia라는 그래픽 엔진을 사용하고 React는 JavaScript 언어와 가상 DOM을 사용합니다. 두 기술 모두 웹 앱 개발에 적합하며 Flutter는 모바일 앱 개발 및 다양한 멀티 플랫폼 지원 + 개발 생산성이 높은 기술들(다양한 위젯과 패키지, Code Action 등)을 갖추고 있다면 React 역시 거대한 커뮤니티와 다양한 라이브러리로 인해 개발 생산성이 높다고 볼 수 있습니다 🤓

 

또한 많은 분들이 React Native와의 차이점에 궁금증을 가지는데요, 앞서 언급했듯이 Flutter는 운영체제를 단순히 엔진의 동작에만 활용하기 때문에 플랫폼의 Native Widget 사용이 불가능합니다. 하지만 React Native에 비해 고수준의 애니메이션 구현에 있어 편리한 기능들이 많이 내장되어 있어, 다음과 같은 기준으로 선택하면 좋지 않을까 생각합니다

 

React Native👍

  • 네이티브 앱 운영체제 상에서 가능한 위젯을 사용해야 하는 경우
  • 디자인이 iOS 혹은 Android 앱처럼 보이게끔 만들고 싶은 경우

 

Flutter👍

  • 세밀한 디자인 요구사항이 있거나 100% 커스터마이징하고 싶은 경우
  • 외부 패키지에 의존하지 않고 고수준의 애니메이션을 구현하고 싶은 경우

 

Widget

React를 경험해보신 분들은 컴포넌트의 개념이 매우 익숙하실 텐데요, 이와 비슷하게 Flutter에는 위젯(Widget)의 개념이 존재합니다! 컴포넌트를 조합해 UI를 만들듯이 Flutter 역시 위젯을 조합해 UI를 구성하며, 그 종류 또한 방대합니다. (개인적으로는 React에서 외부 라이브러리로 구현해야 하는 UI 및 기능들이 내부 위젯 + 플러터 공식 패키지로 대부분 지원된다고 느꼈습니다 😊)

 

Flutter 공식 Widget catalog🔽

 

Widget catalog

A catalog of some of Flutter's rich set of widgets.

docs.flutter.dev

 

상태(State) 라이프사이클 역시 거의 비슷하지만 실제 사용되는 방식에 있어서는 약간의 차이가 있습니다

 

아래는 React로 작성된 간단한 카운터 예제 코드입니다!

 

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  }

  const decrement = () => {
    setCount(count - 1);
  }

  return (
    <div>
      <h1>카운터</h1>
      <p>현재 카운트: {count}</p>
      <button onClick={increment}>증가</button>
      <button onClick={decrement}>감소</button>
    </div>
  );
}

export default Counter;

 

다음은 위 코드를 Flutter로 옮긴 예시입니다

import 'package:flutter/material.dart';

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  void _decrement() {
    setState(() {
      _count--;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('카운터'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '현재 카운트: $_count',
              style: TextStyle(fontSize: 20),
            ),
            SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                ElevatedButton(
                  onPressed: _increment,
                  child: Text('증가'),
                ),
                SizedBox(width: 20),
                ElevatedButton(
                  onPressed: _decrement,
                  child: Text('감소'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Flutter의 위젯은 단순히 build 메서드를 통해 정적인 UI를 출력하는 StatelessWidget과 위젯에 데이터를 저장하고 변화를 UI에 실시간으로 반영하는 StatefulWidget으로 분류되는데요, 위와 같이 State를 사용하는 것이 정석적인 방식이지만 API 비동기 함수 호출에 있어서 Flutter는 State 사용을 지양하고 FutureBuilder라는 개념을 사용합니다 👀 (뒤에서 예시와 함께 설명 드리겠습니다)

 

라이프사이클 개념은 비슷합니다. React 컴포넌트와 비슷하게 Flutter 위젯에도 크게 initState > build > dispose 단계가 있으며, 상태 변화를 감지해 업데이트를 수행하지만 구체적인 메서드 이름과 호출 시점에만 약간의 차이가 있습니다

 

API Fetching

Flutter에서의 비동기 API 호출을 간단하게 맛만 볼까요? 😋 지난 글에서 모델을 클래스로 분리하는 패턴 및 fromJson named constructor 등 약간의 언급이 있었는데요, 물론 Flutter 개발에도 feature-driven-development 등 여러 패턴이 있지만 이해를 돕기 위해 MVC 패턴을 응용한 가장 기본적인 형태로 설명 드리겠습니다

 

Flutter 프로젝트 폴더 구조

모든 개발은 React의 src 폴더와 비슷한 lib 폴더에서 이루어집니다!

폴더 구조 또한 React와 매우 유사하며, 각각의 역할은 다음과 같습니다

  • main.dart: 모든 dart 프로그램의 Entry Point
  • widgets: components 폴더와 유사하게 재사용 가능한 UI 로직을 추출한 위젯 파일들
  • models: 외부 API 모델을 fromJson 메서드로 가공해 반환하는 클래스 파일들
  • services: 비동기 통신 데이터를 모델을 거쳐 가공하고 인스턴스로 반환하는 클래스 파일들
  • screens: UI 위젯을 페이지 단위로 분리해 필요시 데이터를 렌더링하는 위젯 파일들

 

ChatGPT 형님에게 각각 예제 코드를 부탁드려 보겠습니다 🤖

 

1. API 데이터 모델을 fromJson named constructor로 정의한다 (models)

class Post {
  final int id;
  final String title;
  final String body;

  Post({required this.id, required this.title, required this.body});

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'],
      title: json['title'],
      body: json['body'],
    );
  }
}

 

2. API 통신으로 비동기 데이터를 받아 디코딩 및 인스턴스로 가공해 반환한다 (services)

class ApiService {
  static const String baseUrl = 'https://jsonplaceholder.typicode.com';

  static Future<List<Post>> getPosts() async {
    final response = await http.get(Uri.parse('$baseUrl/posts'));
    final List<dynamic> postsJson = jsonDecode(response.body);
    return postsJson.map((json) => Post.fromJson(json)).toList();
  }
}

 

3. API 로직을 불러와 FutureBuilder 위젯 내부에서 상태를 관리해 렌더링한다 (screens)

class PostsScreen extends StatelessWidget {
  final _apiService = ApiService();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Posts'),
      ),
      body: FutureBuilder<List<Post>>(
        future: _apiService.getPosts(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            final posts = snapshot.data!;
            return ListView.builder(
              itemCount: posts.length,
              itemBuilder: (context, index) {
                final post = posts[index];
                return ListTile(
                  title: Text(post.title),
                  subtitle: Text(post.body),
                );
              },
            );
          } else if (snapshot.hasError) {
            return Center(
              child: Text('${snapshot.error}'),
            );
          } else {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
        },
      ),
    );
  }
}

Flutter의 Future 타입은 JavaScript의 Promise 객체와 유사한 개념으로, 위와 같이 비동기 작업의 결과를 나타내는 Future 객체를 FutureBuilder에 넘겨 State를 사용하지 않고도 상태에 따른 UI 렌더링을 수행할 수 있습니다

 


 Flutter는 다양한 위젯을 조합하는 빠른 개발 속도와 크로스 플랫폼이 지원되는 특성으로 제품을 시장에 검증하며 피봇해야 하는 스타트업에서 채택이 많았습니다. 하지만 지속적인 업데이트로 거대 브랜드나 기업에서도 개발 생산성을 위해 Flutter를 채택하는 비율도 점차 늘어나고 있어 개인적으로는 포텐셜이 높은 기술이라고 생각합니다! (물론 국내 점유율이나 커뮤니티 크기는.. 🥲)

 

Flutter apps in production🔽

 

Showcase - Flutter apps in production

The world’s biggest businesses are building with Flutter. View the showcase and see Flutter apps in production.

flutter.dev

 

다루고 싶은 내용도 많고 React와의 비교도 더 해보고 싶었지만 생각처럼 쉽지 않았네요.. 최근 Flutter로 앱 개발을 하고 있어, 관련 경험을 담은 글은 지속적으로 작성해보려 합니다! 전문 분야는 아니지만 StableDiffusion이나 ChatGPT에도 흥미가 있는데 글로 잘 녹여낼 수 있을지 모르겠네요 🫠

 

이번에도 글 읽어 주셔서 감사합니다!!

 

재미있게 보셨다면 블로그나 글또 Slack에 감상을 남겨주세요!

새로운 의견이나 오류 정정, 피드백은 언제나 환영입니다 😽