Flutter를 이용한 Android와의 협업 후기
결론부터 말하자면
기존 네이티브 프로젝트에 Flutter를 붙여보려는 분들을 위해 결론부터 말하자면 충분히 사용해도 된다.
간단하게 사용해봤다고 생각할 수 있지만, 기본적인 UI 적용과 로컬DB를 이용한 데이터 입/출력, 네트워크를 통한 데이터 가져오기와 화면 표시, 데이터 가공 및 네이티브 단으로 데이터 전달까지 모두 가능하다.
그 과정에서 필요한 추가적인 리소스는 Flutter와 네이티브 간의 인터페이스를 정의하는 일인데, 만약 독립적인 Flutter 화면이 필요하다면 더욱 적용할 가치가 있다고 생각한다.
기존의 네이티브 설계 및 개발 과정에 비해 단순해진 작업으로 효율이 높아지는 것을 느꼈다.

해커톤 참여
2022년 8월 26일 금요일, 서울시에서 진행하는 해커톤에 참여했다. 고등학생 이후 처음 참여하는 해커톤이었는데, 연차를 사용하고 참가했다. 이전에 사이드 프로젝트를 함께 했던 디자이너분이 연락을 해주셔서 처음 보는 분들과 팀을 맺고 진행하게 되었다.
주제는 제목처럼 서울시에 있는 IoT 데이터를 활용하여 제한된 시간 안에 앱을 제작하고 발표한 후 시상하는 방식으로 진행되었다.
다행히 잘 만들어졌고, 발표도 무사히 마쳐서 우수상을 수상하고 200만원의 상금을 받았다(아직 안 들어왔다).

Flutter를 이용한 협업
최근에 Flutter를 많이 사용하고 활용하려고 노력하고 있다. 개발을 진행하는 개발자 분에게 양해를 구하고 내 파트는 Flutter로 UI를 작업하여 라이브러리 형태로 임포트하여 바로 사용할 수 있도록 만들겠다고 했다. 이전 외주 작업을 진행할 때에는 .AAR 파일을 만들어서 진행했지만, 이번에는 속도가 중요할 것 같아 빌드 시 나오는 라이브러리 repo를 Github에 업로드하고, 업데이트될 때마다 Pull 받아 바로 적용할 수 있도록 하였다.
방식은 간단하다.
- Flutter 모듈 프로젝트에서 화면을 만든다.
flutter build aar로 빌드하면 안드로이드에서 받아 쓸 수 있는 Maven 저장소 형태로 결과물이 나온다.- 이 결과물을 Github에 올린다.
- 안드로이드 프로젝트에서는 그 저장소를
repositories에 추가하고, 의존성으로 받아서 Flutter 화면을 띄운다.
Flutter 쪽이 바뀌면 다시 빌드해서 올리고, 안드로이드 쪽은 Pull만 받으면 된다. 자세한 설정은 Flutter 웹사이트의 가이드에 잘 나와 있다.
Flutter UI 작업
아래 이미지는 내가 작업을 진행했던 화면이다. Native 안드로이드로도 충분히 작업을 진행할 수 있었지만, Flutter를 이용하는 것이 UI를 개발하는 것이 더 빠를 것이라고 판단했다.

디자이너를 위하여
이미지에서 보이는 것처럼 CollapsingToolbarLayout을 이용하면 쉽게 AppBar가 스크롤되면서 가려지고, Title이 중앙에 위치하게 작업을 진행할 수 있는데, Flutter에서는 조금 달랐기 때문에 코드를 수정해야 했다.
비슷한 기능으로 SliverAppBar를 이용하고, 애니메이션을 이용하여 비슷하게 보이도록 개발을 진행했다. SliverAppBar를 이용하면 충분히 비슷하게 만들 수 있지만, 디자이너가 열심히 만든 디자인을 그대로 구현하기 위해 더 찾아보았다.
역시 StackOverflow에 답이 나와있다. StackOverflow에 있는 코드를 수정하여 사용하기로 했다.
https://stackoverflow.com/questions/63231817/custom-flexiblespacebar-widget
SliverAppBar와 Flexible 위젯을 이용하여 Flexible 위젯의 height를 이용해 opacity를 계산하여 이미지를 보이게 함과 동시에 아래 주석 처리되어 있는 opacityReverse를 적용하여 스크롤이 되는 순간 숨겨져 있는 AppBar가 보이게 만들었다.
아래는 핵심 부분만 남긴 코드다. 전체 코드는 gist에 올려뒀다.
https://gist.github.com/lowapple/6f80e4549192617782c085737f15d3f0
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:math' as math;
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로 할까 했는데, 생각보다 보이는 부분만 교묘하게 수정하여 실제 구현된 것처럼 보이게 하는 작업이 잘 맞아 떨어져서 해결이 가능했다.
해보고 느낀 점
해커톤이라 시간이 정말 없었다. 처음에는 그냥 네이티브로 하는 게 안전하지 않을까 했는데, UI는 Flutter로 짜는 게 훨씬 빨라서 결과적으로 기능을 더 많이 넣을 수 있었다.
디자인도 중간에 계속 바뀌었다. Hot Reload 덕분에 바뀐 걸 바로바로 확인하면서 맞출 수 있었다. 네이티브 쪽과 데이터를 주고받는 부분도 걱정했는데, Method Channel로 생각보다 쉽게 붙었다.
앞으로 기존 네이티브 프로젝트에 Flutter를 붙이는 경우가 많아질 것 같은데, 이 글이 도움이 됐으면 좋겠다.