
Teknik not
Flutter’da Android 15/16 Edge-to-Edge Sorunu: SafeArea Yetmediğinde Ne Yapmalı?

Flutter uygulamanızı Android 15’e taşıdınız ve bir anda başlık kamera deliğinin altına, alt menü de gezinme çubuğunun arkasına mı girdi? Bu genellikle bozuk bir widget’tan değil, Android’in ekranı yerleştirme kuralını değiştirmesinden kaynaklanır. Android 15, API 35’i hedefleyen uygulamalarda edge-to-edge düzeni varsayılan hâle getirdi; Android 16’da ise bu davranıştan çıkış yolu kaldırıldı.
Kısa süreli çözüm olarak her ekranı SafeArea ile sarmak cazip görünür. Fakat klavye açıldığında, özel alt menü kullanıldığında veya cihaz yataya döndüğünde bu yöntem yeni boşluklar ve çift padding üretebilir. Kalıcı çözüm; padding, viewPadding ve viewInsets değerlerinin farklı görevlerini bilip güvenli alanı yalnızca ihtiyaç duyan bileşene uygulamaktır.
Hızlı teşhis: Sorun gerçekten edge-to-edge mi?
- Sorun yalnızca Android 15 veya 16 cihazlarda görünüyorsa,
targetSdk35 ya da 36’ya yükseltildikten sonra başladıysa,- Üstteki butonlar durum çubuğunun, alttaki butonlar gezinme alanının arkasında kalıyorsa,
- Klavye açıldığında formun son alanı veya gönder düğmesi kayboluyorsa,
- Üç düğmeli gezinmede sorun var, hareketle gezinmede yoksa
büyük olasılıkla Flutter Android edge-to-edge sorunu yaşıyorsunuz. Android’in resmi belgelerine göre API 35’i hedefleyen uygulamalar Android 15’te sistem çubuklarının arkasına çizilir. API 36’yı hedefleyen uygulamalar Android 16’da bu davranışı kapatamaz.

Android 15 ve 16’da ne değişti?
Edge-to-edge, uygulamanın arka planını ekranın fiziksel sınırlarına kadar uzatır. Durum çubuğu, kamera kesiti ve gezinme alanı artık ayrı birer “boş şerit” değildir; uygulama bu bölgelerin arkasına çizim yapabilir. Bu daha modern bir görünüm sağlar, ancak dokunulabilir kontrollerin güvenli bölgeye alınması geliştiricinin sorumluluğundadır.
Flutter tarafında da yön aynı. Güncel Flutter edge-to-edge geçiş belgesi, Android 16 ve sonrasında farklı bir SystemUiMode seçerek bu düzenden çıkılamayacağını açıkça belirtiyor. Bu nedenle tema dosyasına geçici opt-out satırı eklemek yerine arayüzü doğru insets mantığına taşımak gerekir.
SafeArea neden her zaman yetmez?
SafeArea yanlış değildir; çoğu standart ekran için en iyi başlangıçtır. Sorun, onu tüm ekranın etrafına koşulsuz yerleştirmektir. Flutter’ın MediaQueryData sınıfında üç farklı kenar bilgisi bulunur:
| Değer | Ne anlatır? | Tipik kullanım |
|---|---|---|
padding | O anda kısmen görünmeyebilecek güvenli alanı | Standart SafeArea davranışı |
viewPadding | Klavye açılsa da cihazın kalıcı fiziksel güvenli alanını | Alt menü, home indicator, ekran kesiti |
viewInsets | Klavye gibi içeriği tamamen örten sistem arayüzünü | Form ve bottom sheet konumlandırma |
Klavye açıldığında alt padding sıfıra yaklaşabilir, fakat viewInsets.bottom klavyenin yüksekliğini verir. Bu ayrımı yapmadan hem SafeArea hem de klavye padding’i eklerseniz form yukarı gereğinden fazla sıçrar.
1. Edge-to-edge davranışını bilinçli olarak etkinleştirin
Güncel Flutter sürümlerinde edge-to-edge zaten varsayılandır. Yine de uygulama başlangıcında niyetinizi açık hâle getirmek, eski Android sürümlerinde de tutarlı davranış sağlar:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setEnabledSystemUIMode(
SystemUiMode.edgeToEdge,
);
runApp(const MyApp());
}
Sistem çubuğu ikonlarının açık veya koyu görünmesini ekranın zeminine göre ayarlayın. Android 15/16 edge-to-edge modunda statusBarColor gibi renk alanlarına güvenmeyin; sistem çubukları şeffaf davranır. Asıl kontrol edilmesi gereken ikon parlaklığıdır.
const SystemUiOverlayStyle darkBackgroundStyle =
SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
statusBarBrightness: Brightness.dark,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness: Brightness.light,
systemNavigationBarDividerColor: Colors.transparent,
);
Bu stili ekran kökünde AnnotatedRegion<SystemUiOverlayStyle> ile uygulayabilirsiniz. Farklı arka planlara sahip sayfalarda tek bir global stil kullanmak yerine sayfa bazında ikon kontrastını seçmek daha güvenlidir.
2. Tüm ekranı değil, etkileşimli içeriği koruyun
Arka planın durum çubuğunun arkasına uzanmasını, ancak başlık ve butonların güvenli alanda kalmasını istiyorsanız SafeArea yalnızca içeriği sarmalıdır:
Scaffold(
extendBody: true,
body: DecoratedBox(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF111827), Color(0xFF312E81)],
),
),
child: SafeArea(
bottom: false,
child: CustomScrollView(
slivers: [/* içerik */],
),
),
),
);
Burada arka plan ekranın tamamını boyar, kaydırılabilir içeriğin üst kısmı kesitten korunur. Alt alanı ayrıca yöneteceğimiz için bottom: false kullanılır. Sliver tabanlı bir ekranda aynı yaklaşım SliverSafeArea ile kurulabilir.
3. Özel alt menüde viewPadding kullanın
Alt menünün rengi gezinme hareketi alanına kadar uzanmalı, ancak ikonları home indicator’ın üzerinde kalmalıdır. Bunun için dış katman arka planı, iç katman ise fiziksel güvenli alanı yönetir:
class EdgeAwareBottomBar extends StatelessWidget {
const EdgeAwareBottomBar({super.key});
@override
Widget build(BuildContext context) {
final bottomSafe = MediaQuery.viewPaddingOf(context).bottom;
return ColoredBox(
color: const Color(0xFF171026),
child: Padding(
padding: EdgeInsets.only(bottom: bottomSafe),
child: const SizedBox(
height: 64,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [/* menü düğmeleri */],
),
),
),
);
}
}
viewPadding, klavye açıldığında da cihazın kalıcı alt güvenli alanını korur. Sabit 24 veya 48 piksel vermek ise üreticiye, gezinme moduna ve ekran yönüne göre bozulur.
4. Klavye açıldığında viewInsets’i ayrı yönetin
Normal bir Scaffold, varsayılan resizeToAvoidBottomInset: true ayarıyla çoğu formu otomatik küçültür. Bu durumda ikinci kez klavye padding’i eklemeyin. Ancak showModalBottomSheet, özel katman veya tam ekran sohbet girişi kullanıyorsanız klavye yüksekliğini açıkça hesaba katın:
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
final keyboard = MediaQuery.viewInsetsOf(context).bottom;
return AnimatedPadding(
duration: const Duration(milliseconds: 180),
curve: Curves.easeOut,
padding: EdgeInsets.only(bottom: keyboard),
child: const SafeArea(
top: false,
child: MessageComposerSheet(),
),
);
},
);
Buradaki iki değer farklı sorunları çözer: viewInsets.bottom açılan klavyeyi, SafeArea ise klavye kapalıyken gezinme alanını korur.
5. Yatay ekran ve kamera kesitini unutmayın
Edge-to-edge hataları yalnızca üst ve altta oluşmaz. Telefon yataya döndüğünde kamera kesiti sol veya sağ kenara geçebilir. Dokunulabilir bir geri düğmesi, oyun kontrolü ya da yatay araç çubuğu kullanıyorsanız yan güvenli alanları da okuyun:
final safe = MediaQuery.viewPaddingOf(context);
Padding(
padding: EdgeInsets.only(
left: safe.left,
right: safe.right,
),
child: const LandscapeToolbar(),
);
Android’in ekran kesiti rehberi de durum çubuğu yüksekliğini sabitlemek yerine gerçek window insets değerlerinin kullanılmasını öneriyor.
Sık yapılan beş hata
- Tüm uygulamayı SafeArea ile sarmak: Arka planların kenarlara uzanmasını engeller ve iç içe sayfalarda çift boşluk oluşturabilir.
- Sistem çubuğu yüksekliğini sabitlemek: Kamera kesiti, yatay ekran ve üretici farklarında bozulur.
- SafeArea ile viewInsets’i körlemesine toplamak: Klavye açıldığında form gereğinden fazla yukarı çıkar.
- Yalnızca hareketle gezinmeyi test etmek: Üç düğmeli gezinmenin alt scrim ve yükseklik davranışı farklıdır.
- Android 15 opt-out satırını kalıcı çözüm sanmak: Android 16’da opt-out devre dışıdır; yalnızca gerçek insets uyarlaması geleceğe dayanır.
Android 15/16 edge-to-edge test listesi
- Android 15 API 35 ve Android 16 API 36 emülatörlerinde çalıştırın.
- Hareketle gezinme ve üç düğmeli gezinme modlarını ayrı ayrı deneyin.
- Açık ve koyu temada sistem ikonlarının kontrastını kontrol edin.
- Klavye açıkken son form alanına ve ana düğmeye erişilebildiğini doğrulayın.
- Portre, yatay ve kamera kesitli cihaz profillerini test edin.
- Bottom sheet, snackbar, FAB ve özel alt menüyü ayrıca inceleyin.
- Ekran görüntüsü testlerinde üst ve alt güvenli alanları karşılaştırın.
Android 15 uyumluluk davranışını hedef SDK’yı değiştirmeden denemek için geliştirici seçeneklerindeki App Compatibility Changes ekranından ENFORCE_EDGE_TO_EDGE değişikliğini açabilirsiniz. Resmî değişiklik kimliği 309578419’dur. Bu test, sorunun gerçekten yeni pencere yerleşiminden kaynaklanıp kaynaklanmadığını hızlıca gösterir.
Sonuç: SafeArea bir araçtır, mimari değil
Flutter Android edge-to-edge sorununun kalıcı çözümü, her yere daha fazla padding eklemek değildir. Arka planın ekranın tamamını kullanmasına izin verin; başlık, buton ve form gibi etkileşimli bileşenleri ihtiyaç duydukları inset türüyle koruyun. Fiziksel güvenli alan için viewPadding, klavye için viewInsets, standart içerik için SafeArea kullanın.
Bu yaklaşım Android 15’teki görünüm bozukluğunu düzeltirken Android 16 ve sonraki sürümlere de hazır bir arayüz üretir. Benzer pratik rehberler için Uygulama Geliştirme kategorisini takip edebilirsiniz.