Kotlin .flatMapLatest
Bir Android geliştiricisi olarak en sık karşılaştığımız senaryolardan biri şudur: Bir arama çubuğu (Search Bar) koymak ve kullanıcı her harfe bastığında sonuçları anlık olarak listelemek.
Kulağa basit geliyor, değil mi? Ancak Kotlin Flow ve Coroutines dünyasında bu işlemi yaparken yanlış operatörü seçmek, uygulamanızın hafıza sızdırmasına, gereksiz ağ trafiği yaratmasına ve hatta kullanıcının yanlış veriyi görmesine neden olabilir.
Bugün, bu senaryonun gizli kahramanı flatMapLatest operatörünü ve neden düz bir map işleminden veya diğer düzleştirme (flattening) yöntemlerinden çok daha fazlası olduğunu inceleyeceğiz.
Sorun 1: İç İçe Geçmiş Akışlar (The Nested Flow Problem)
Diyelim ki bir film arama uygulaması yapıyorsunuz. Elinizde kullanıcının girdiği metni tutan bir StateFlow ve veritabanından sonuç getiren bir Repository fonksiyonu var.
Repository fonksiyonunuz muhtemelen şöyledir:
// Repository katmanı
fun searchMovies(query: String): Flow<List<Movie>>Eğer ViewModel tarafında standart map operatörünü kullanmaya kalkarsanız şöyle bir durumla karşılaşırsınız:
// YANLIŞ YAKLAŞIM
val results = queryFlow.map { text ->
repository.searchMovies(text) // Bu fonksiyon da bir Flow döndürüyor!
}Buradaki matematiksel sorun şudur: queryFlow bir Flow’dur. searchMovies de bir Flow döndürür. Sonuç?
Flow<Flow<List<Movie>>>
Elinizde “Flow içinde Flow” oluşur. UI katmanında (Compose veya XML) bunu tüketmek (collect etmek) tam bir kabustur. Bizim UI’da istediğimiz iç içe kutular değil, doğrudan Flow<List<Movie>> verisidir.
İşte bu yüzden, akışları “düzleştirmek” (flatten) gerekir. flatMap ismindeki “flat”, bu iç içe yapıyı düz bir çizgiye indirmeyi temsil eder.
Sorun 2: Yarış Hali (Race Condition)
Peki, flatMapConcat veya flatMapMerge kullanarak akışları düzleştirebiliriz. Neden özellikle flatMapLatest kullanmalıyız?
Bunu bir senaryo ile canlandıralım. Kullanıcı “Avatar” kelimesini aratmak istiyor.
- Kullanıcı “A” harfine bastı. Uygulama veritabanında “A” ile başlayan binlerce filmi aramaya başladı. (Bu işlem diyelim ki 500ms sürsün).
- Kullanıcı çok hızlı, henüz 100ms geçmişken “v” harfine bastı (“Av” oldu).
Eğer flatMapMerge kullanırsanız:
Uygulamanız “A” sorgusunu iptal etmez. Arka planda hem “A” sorgusu hem de “Av” sorgusu aynı anda çalışır.
- Hata Riski: Eğer “A” sorgusu, sistemsel bir gecikmeyle “Av” sorgusundan daha geç biterse; kullanıcı ekranda önce doğru sonucu (“Av”), saniyeler sonra ise yanlış sonucu (“A”) görür. Liste anlamsızca güncellenir.
- Kaynak İsrafı: Kullanıcı artık “A” harfiyle ilgilenmiyor, “Av” yazdı bile. Neden hala “A” için CPU, RAM veya İnternet harcıyoruz?
Çözüm: flatMapLatest
flatMapLatest operatörünün çalışma mantığı çok basittir: “Kral öldü, yaşasın yeni kral!”
Yeni bir değer geldiği anda (kullanıcı yeni bir harfe bastığında), önceki işlemi anında iptal eder (cancel) ve sadece yeni gelen değer için işlemi başlatır.
// DOĞRU YAKLAŞIM
val results = queryFlow.flatMapLatest { text ->
repository.searchMovies(text)
}Bu operatör kullanıldığında yukarıdaki senaryo şöyle işler:
- Kullanıcı “A” yazar. Veritabanı sorgusu başlar.
- Kullanıcı “Av” yazar.
flatMapLatestdevreye girer: “A” için çalışan sorguyu anında öldürür (CancellationException). O sorgudan artık veri gelmez. Kaynaklar serbest kalır.- Hemen “Av” için yeni sorgu başlatılır.
Analoji: Sabırsız Müşteri ve Garson
Bunu bir restoran örneğiyle düşünelim:
- Siz (Kullanıcı): Masada oturuyorsunuz.
- Garson (Flow Operatörü): Siparişinizi alıyor.
- Mutfak (Repository/Database): Yemeği hazırlıyor.
Senaryo: Önce “Çorba” istediniz. Garson mutfağa iletti. 10 saniye sonra fikrinizi değiştirip “Hayır, vazgeçtim! Salata istiyorum” dediniz.
flatMapMergekullanan garson: Mutfağa gider, Salatayı da söyler. Mutfak hem çorbayı hem salatayı hazırlar. Masanıza ikisi de gelir (belki de istemediğiniz çorba en son gelir). Masa karışır, yemek ziyan olur.flatMapLatestkullanan garson: “Salata” dediğiniz anda mutfağa koşar, “Çorbayı iptal edin, yapmayın! Yerine Salata yapın!” der. Masanıza sadece ve sadece en son istediğiniz gelir.
Özet: Neden Kullanmalıyız?
Modern Android geliştirmede, özellikle Search (Arama), Filter (Filtreleme) veya Pagination (Sayfalama) gibi kullanıcı girdisine dayalı, dinamik veri akışlarında flatMapLatest kullanmak bir tercih değil, neredeyse bir zorunluluktur.
Bu operatör sayesinde:
- Veri Tutarlılığı: Kullanıcı her zaman yazdığı metnin karşılığını görür, eski sorgular araya girmez.
- Performans: Gereksiz işlemler iptal edilerek pil, işlemci ve veri (internet) tasarrufu sağlanır.
- Temiz Kod: İç içe geçmiş Flow yapılarından (Nested Flows) kurtulursunuz.
Bir sonraki arama fonksiyonunuzu yazarken map yazmadan önce bir kez daha düşünün: Eski veriye gerçekten ihtiyacım var mı? Cevabınız hayır ise, flatMapLatest sizin en iyi dostunuzdur.