윤준서의 블로그

Flutter를 이용한 Android와의 협업 후기

결론부터 말하자면

이 글을 작성할까 말까 고민을 했는데, 혹시 기존 네이티브 프로젝트에 Flutter를 적용하려는 분들이 있을까봐 결론부터 말하자면 충분히 사용해도 된다.

간단하게 사용해봤다고 생각할 수 있지만, 기본적인 UI 적용과 로컬DB를 이용한 데이터 입/출력, 네트워크를 통한 데이터 가져오기와 화면 표시, 데이터 가공 및 네이티브 단으로 데이터 전달까지 모두 가능하다.

그 과정에서 필요한 추가적인 리소스는 Flutter와 네이티브 간의 인터페이스를 정의하는 일인데, 만약 독립적인 Flutter 화면이 필요하다면 더욱 적용할 가치가 있다고 생각한다.

기존의 네이티브 설계 및 개발 과정에 비해 단순해진 작업으로 효율이 높아지는 것을 느꼈다.

Flutter를 이용한 Android와의 협업 후기_01

해커톤 참여

2022년 8월 26일 금요일, 서울시에서 진행하는 해커톤에 참여했다. 고등학생 이후 처음 참여하는 해커톤이었는데, 연차를 사용하고 참가했다. 이전에 사이드 프로젝트를 함께 했던 디자이너분이 연락을 해주셔서 처음 보는 분들과 팀을 맺고 진행하게 되었다.

주제는 제목처럼 서울시에 있는 IoT 데이터를 활용하여 제한된 시간 안에 앱을 제작하고 발표한 후 시상하는 방식으로 진행되었다.

다행히 잘 만들어졌고, 발표도 무사히 마쳐서 우수상을 수상하고 200만원의 상금을 받았다(아직 안 들어왔다).

Flutter를 이용한 Android와의 협업 후기_02

Flutter를 이용한 협업

최근에 Flutter를 많이 사용하고 활용하려고 노력하고 있다. 개발을 진행하는 개발자 분에게 양해를 구하고 내 파트는 Flutter로 UI를 작업하여 라이브러리 형태로 임포트하여 바로 사용할 수 있도록 만들겠다고 했다. 이전 외주 작업을 진행할 때에는 .AAR 파일을 만들어서 진행했지만, 이번에는 속도가 중요할 것 같아 빌드 시 나오는 라이브러리 repo를 Github에 업로드하고, 업데이트될 때마다 Pull 받아 바로 적용할 수 있도록 하였다.

기본적인 연동 방법은 Flutter 웹사이트의 가이드를 따르면 금방 적용된다.

Flutter UI 작업

아래 이미지는 내가 작업을 진행했던 화면이다. Native 안드로이드로도 충분히 작업을 진행할 수 있었지만, Flutter를 이용하는 것이 UI를 개발하는 것이 더 빠를 것이라고 판단했다.

Flutter를 이용한 Android와의 협업 후기_03

디자이너를 위하여

이미지에서 보이는 것처럼 CollapsingToolbarLayout을 이용하면 쉽게 AppBar가 스크롤되면서 가려지고, Title이 중앙에 위치하게 작업을 진행할 수 있는데, Flutter에서는 조금 달랐기 때문에 코드를 수정해야 했다.

비슷한 기능으로 SliverAppBar를 이용하고, 애니메이션을 이용하여 비슷하게 보이도록 개발을 진행했다. SliverAppBar를 이용하면 충분히 비슷하게 만들 수 있지만, 디자이너가 열심히 만든 디자인을 그대로 구현하기 위해 더 찾아보았다.

역시 StackOverflow에 답이 나와있다. StackOverflow에 있는 코드를 수정하여 사용하기로 했다.

https://stackoverflow.com/questions/63231817/custom-flexiblespacebar-widget

SliverAppBarFlexible 위젯을 이용하여 Flexible 위젯의 height를 이용해 opacity를 계산하여 이미지를 보이게 함과 동시에 아래 주석 처리되어 있는 opacityReverse를 적용하여 스크롤이 되는 순간 숨겨져 있는 AppBar가 보이게 만들었다.

상세 코드는 아래에서 확인할 수 있다.

https://gist.github.com/lowapple/6f80e4549192617782c085737f15d3f0

import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'package:flutter/services.dart';
class ParkingHeader extends StatelessWidget {
  final String parkingTitle;
  final List<String> parkingTags;
  final String parkingAddress;
  const ParkingHeader(
      {Key? key,
      required this.parkingTitle,
      required this.parkingTags,
      required this.parkingAddress})
      : super(key: key);
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, c) {
        final statusHeight = MediaQuery.of(context).viewPadding.top;
        final settings = context
            .dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!;
        final deltaExtent = settings.maxExtent - settings.minExtent;
        final t =
            (1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent)
                .clamp(0.0, 1.0);
        final fadeStart = math.max(0.0, 1.0 - kToolbarHeight / deltaExtent);
        const fadeEnd = 1.0;
        final opacity = 1.0 - Interval(fadeStart, fadeEnd).transform(t);
        // final opacityReverse = 1.0 - opacity;
        // final c =
        //     (255 - (255 * Interval(fadeStart, fadeEnd).transform(t))).toInt();
        // final cc = Color.fromARGB(255, c, c, c);
        return Stack(
          children: [
            Container(
              color: Colors.white,
              height: 280,
              width: double.infinity,
              child: Opacity(
                opacity: opacity,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Flexible(
                      child: Container(
                        color: Colors.blue,
                        width: double.infinity,
                        child: Image.asset(
                          'assets/parking_image.png',
                          fit: BoxFit.cover,
                        ),
                      ),
                    ),
                    ...
                  ],
                ),
              ),
            ),
            ...
          ],
        );
      },
    );
  }
}

사실 위 부분이 제일 시간이 오래 걸렸다. 중간에 다시 Native로 할까 했는데, 생각보다 보이는 부분만 교묘하게 수정하여 실제 구현된 것처럼 보이게 하는 작업이 잘 맞아 떨어져서 해결이 가능했다.

협업 과정에서의 고민과 해결책

해커톤이라는 제한된 시간 안에 작업을 완료해야 했기 때문에, 효율적인 개발 방법을 고민했다. 처음에는 네이티브 안드로이드로 작업하는 것이 더 안정적일 것이라고 생각했지만, Flutter의 빠른 UI 개발 능력을 활용하면 더 많은 기능을 구현할 수 있을 것이라는 판단이 섰다.

특히 디자이너와의 협업 과정에서 디자인 변경이 자주 있었는데, Flutter의 Hot Reload 기능 덕분에 변경사항을 즉시 확인할 수 있어 개발 속도가 크게 향상되었다. 네이티브 개발자와의 인터페이스 정의도 처음에는 복잡할 것 같았지만, Flutter의 Method Channel을 이용해 쉽게 구현할 수 있었다.

결론

Flutter와 네이티브 안드로이드의 협업은 충분히 가능하며, 오히려 각자의 장점을 살려 더 효율적인 개발이 가능하다. Flutter는 UI 개발에 강점이 있고, 네이티브는 기존 코드베이스와의 통합에 강점이 있다. 두 기술을 적절히 조합하면 개발 시간을 단축하고 더 나은 사용자 경험을 제공할 수 있다.

이번 해커톤 경험은 Flutter와 네이티브 안드로이드의 협업 가능성을 확인하는 좋은 기회가 되었다. 앞으로도 기존 네이티브 프로젝트에 Flutter를 적용하는 경우가 많아질 것 같은데, 이 글이 그런 상황에서 도움이 되길 바란다.

#android #flutter