앱이 Android 15(API수준 35) 이상을 타겟팅해야 함

얼마 전에 구글플레이에서 다음과 같은 메일을 받았다.

콘솔에서 더 자세히 확인해 봤더니 2025년 8월 31일까지
프로덕션 앱을 안드로이드 15 (API 수준 35) 이상을 타겟팅하지 않으면
더 이상 앱을 업데이트할 수 없다고 한다.
flutterCompileSdkVersion = '35'
flutterTargetSdkVersion = '35'
그래서 기존 `34`였던 sdk 버전을 `35`로 올리고 앱을 다시 게시했다.
Android 15 Edge-to-edge

버전 업데이트 후 기능이 잘 작동하는지 이것저것 확인해 보던 중
Bottom sheet를 올렸을 때 화면 하단의 내비게이션바와 앱 화면이 겹치는 것을 확인했다.
사용자들이 텍스트를 확인하거나 버튼을 누르는 데 불편함이 있을 거 같아서
내비게이션 바가 Bottom sheet 영역을 침범하지 않도록 수정하기로 했다.

일단 이유를 찾아보니 Android 15 이상을 실행하는 기기에서 SDK 35 이상을 타겟팅하면
앱이 자동으로 더 넓은 화면으로 표시된다고 한다.

예를 들면 오른쪽 예시와 같이 콘텐츠를 더 넓은 화면에 보여줄 수 있다.
하지만 앱의 상단 및 하단 영역이 상태 표시줄과 탐색 메뉴 뒤에 그려지기 때문에
앱의 일부가 가려지는 것을 원하지 않으면 별도로 인셋이나 패딩 설정을 해야 한다.
참고로 인셋은 화면의 특정 영역이 시스템 UI(상태 바, 내비게이션 바 등)와 겹치는 경우에
앱의 콘텐츠가 시스템 UI와 겹치는 위치를 정의하는 값을 의미한다.
앱 콘텐츠와 내비게이션 바가 겹치는 문제 해결하기
Bottom sheet가 내비게이션 바와 겹치는 문제
앱이 Material 3 Component(예시: `TopAppBar`, `BottomAppBar`, `NavigationBar`)를 사용하는 경우에는
인셋을 자동으로 처리하므로 추가 작업이 필요하지 않다.

하지만 `BottomSheet` 등 특정 위젯의 경우에는
시스템 표시줄이 앱의 화면과 겹치는 것을 원하지 않는다면 별도로 인셋이나 패딩을 적용해야 한다.
이렇게 하면 화면에 표시된 시스템 UI (예: 내비게이션 바)가 차지하는 영역만큼
Bottom Sheet가 하단에 패딩값을 가지기 때문에 내비게이션 바에 가려지지 않는다.
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom, // ✅ 하단 가려지는 문제 해결
),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 20,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.camera_alt),
title: const Text('사진 촬영하기'),
onTap: () {
getAndSetImage(ImageSource.camera);
Navigator.of(context).pop();
},
),
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text('갤러리에서 가져오기'),
onTap: () {
getAndSetImage(ImageSource.gallery);
Navigator.of(context).pop();
},
),
],
),
),
);
},
);
`Container`를 `Padding` 위젯으로 감싸고 `MediaQuery.of(context).padding.bottom`을 추가했다.
이렇게 하면 화면에 표시된 시스템 UI (예: 내비게이션 바)가 차지하는 영역만큼
Bottom Sheet가 하단에 패딩값을 가지기 때문에 내비게이션 바에 가려지지 않는다.
스크린이 내비게이션 바와 겹치는 문제

그런데 `BottomNavigationBar`를 사용하지 않는 스크린에서도
하단 내비게이션 바에 앱 화면이 가려지는 것을 발견했다.
Scaffold(
body: SafeArea(
child: MyPageContent(),
),
);
이런 경우에는 간단하게 `Scaffold`의 하위 위젯을 `SafeArea`로 감싸면 된다.
`SafeArea` 위젯은 노치, 상태 바, 하단 내비게이션 바 등 시스템 UI 요소에 의해 가려지지 않도록
자동으로 padding을 적용해서 안전한 영역을 확보해 주는 위젯이다.
플러터에서 edge-to-edge 비활성화하기
Flutter 앱이 Android SDK 버전 15를 타겟팅하는 경우, 앱이 자동으로 edge-to-edge 모드로 표시된다.
Flutter 3.27 버전부터는 edge-to-edge를 옵트아웃할 수도 있다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application ...>
<activity ...>
<!-- ✅Style to modify: -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
</activity>
</application>
</manifest>
`android/app/src/main/AndroidManifest.xml`에 기본 스타일을 수정하는 `meta-data`를 추가한다.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
...
<!-- ✅Add the following line: -->
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
...
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
...
<!-- ✅Add the following line: -->
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
</resources>
스타일이 정의된 파일 `android/app/src/main/res/values/styles.xml`에서 다음 속성을 추가하면
Android SDK 15를 타겟으로 하는 앱의 edge-to-edge 기능이 해제된다.
참고 문서
- [Android Developers] Lay out your app within window insets
- [Android Developers] Behavior changes: Apps targeting Android 15 or higher-Window inset changes
- [Android Developers] Set default of SystemUiMode to edge-to-edge
- [Android] Edge-to-Edge 이해하고 적용해보기