Bildirime dokununca doğru ekranı açmak
React Native’de uygulama açık, arka planda veya kapalıyken bildirimden doğru ekrana geçiş: ortak yönlendirme, veri düzenleme ve oturum yönetimi.

"Sepetin seni bekliyor" diyen bir bildirim, tıklanınca ana sayfayı açıyorsa bozuktur. Kullanıcı sepeti görmek için tıkladı. Uygulama omuz silkti.
Revotas'ta push'un (uygulama açık olmasa da cihaza gönderilebilen bildirim) mobil tarafında işim buydu: biri bildirime dokunduğunda, kampanyanın kastettiği ekrana inmesi. Home, Category, Product ya da Cart. Bunu React Native referans uygulamaları olarak kurdum (önce 0.77, sonra 0.79.2), @react-native-firebase/messaging ve Notifee üzerinde. Amaç, uygulama ekiplerinin yorumlayacağı bir spec yerine kopyalayabileceği çalışan kod vermekti. Şubat 2025 ile Mart 2026 arasında 25 commit (kod deposuna kaydedilen değişiklik paketi).
Bir tıklama, üç ayrı problem
Tıklama, bildirim geldiğinde uygulamanın ne yaptığına göre kodunuza farklı bir kapıdan girer.
- Ön plan. FCM (Firebase Cloud Messaging, cihazlara bildirim iletme hizmeti) mesajı
onMessageiçinde verir ve Android kendiliğinden hiçbir şey göstermez. Banner istiyorsanız kendiniz çizersiniz (ben Notifee kullandım), tıklama da sonra Notifee'nin press event'i (bir işlemin gerçekleştiğini bildiren olay) olarak döner. - Arka plan. Bildirimi sistem çizer. Tıklama
onNotificationOpenedAppiçine düşer. - Kapalı uygulama. Uygulama sıfırdan açılır ve onu bir tıklamanın başlattığını öğrenmek için
getInitialNotification()'ı erkenden, bir kez sormanız gerekir.
Bir de dördüncü kapı var. Uygulama arka plandayken Notifee bildirimine basılırsa event headless bir JS bağlamında çalışır. Orada React ağacı yok, yani yönlendirecek bir şey de yok.
Sunucu tarafı ise başkasınındı: RabbitMQ'ya mesaj atan mevcut bir Java/Spring servisi ve FCM'i çağıran bir worker. Okuyabiliyordum. Ben yazmadım ve yeniden tasarlamak kapsam dışıydı.
Her kapının arkasında tek router
Akla ilk gelen, her listener'ın (bir olayı bekleyip gerçekleşince kod çalıştıran dinleyici) içine biraz yönlendirme koymak. Aynı if-else'in beş farklı kopyasını istemedim. O yüzden her giriş noktası iki şey yapıyor: payload'ı (istek veya bildirimle taşınan veri) normalize ediyor, sonra tek bir fonksiyonu çağırıyor.
routeFromNotificationTap(normalized, 'open'); // ya da 'initial', 'foreground'Kaynak etiketi sadece debug (yazılımdaki hatanın nedenini bulma) paneli için var. Fonksiyon route, click action, trace id, package id, başlık ve gövdeden bir anahtar üretiyor; son işlediği tıklamayla aynıysa duruyor. Soğuk başlatmada iki kütüphane de "seni açan bildirim buydu" diyebiliyor ve kullanıcının aynı ekrana iki kez fırlatılmasını istemedim.
Headless kapı için ayrı bir numara gerekti. index.js içindeki arka plan press handler'ı payload'ı AsyncStorage'a bekleyen bir anahtarla yazıyor, başka hiçbir şey yapmıyor. Uygulama bu anahtarı mount'ta ve AppState her active olduğunda okuyor. Önce anahtarı siliyor, sonra yönlendiriyor. Yönlendirme bir gün hata fırlatırsa, bir sonraki açılış aynı bozuk payload'ı sonsuza kadar tekrar oynatmak yerine temiz başlıyor.
Yönlendirmeden önce normalize et
Payload'lar birbiriyle anlaşmıyordu. Başlık bazen notification bloğunda, bazen data içindeydi. dataMap kimi zaman obje, kimi zaman JSON (veriyi alan ve değer çiftleriyle taşıyan metin biçimi) string (metin değeri) olarak geliyordu (FCM data değerleri string olduğu için bu sürekli oluyor). Bazı göndericiler click_action, bazıları clickAction kullanıyordu. Bazıları işe yarayan kısmı extra ya da payloadJson altına gömmüştü.
payload.ts (218 satır) hepsini tek bir şekle indiriyor: başlık, gövde, görsel, ses, click action, route, screen, artı birleştirilmiş data ve dataMap. Asla hata fırlatmıyor. Bozuk JSON boş objeye dönüşüyor, uygulama yoluna devam ediyor.
Route çözümü sabit bir sırayla işliyor:
- Açık bir
routeya dascreenalanı varsa o kazanır. - Yoksa click action eşlenir (
OPEN_PRODUCTProduct olur,OPEN_CARTCart olur, vb.). - O da yoksa id'lerden tahmin edilir:
productIdProduct,categoryIdCategory,cartIdCart demek. - Hiçbiri yoksa
nulldöner.
null gerçek bir cevap. Uygulama olduğu yerde kalıyor, debug paneli "route çözülmedi" yazıyor. Rastgele bir ekran açıp her şey yolundaymış gibi yapmaktansa bunu göstermeyi tercih ederim.
Token'lar ve Android 13 izni
Android 13 (API (yazılımların birbirine istek göndermesini sağlayan arayüz) 33), POST_NOTIFICATIONS'ı çalışma zamanı iznine çevirdi. İzni istemeyi atlarsanız hiçbir şey çökmez; bildirimler sadece hiç görünmez. Bu en kötü bug (yazılım hatası) türü, çünkü bütün loglar sağlıklı görünür.
Bu yüzden açılış akışı önce bu izni istiyor, reddedilirse ekranda görünen bir durum mesajıyla duruyor. Ardından ön plan banner'ları için yüksek öncelikli bir Android kanalı oluşturuyor, FCM'e kaydoluyor, token'ı (bildirimin gönderileceği uygulama kurulumunu tanıtan kayıt değeri) alıp panelde kopyalama butonuyla gösteriyor. Token backend'e (uygulamanın sunucuda çalışan bölümü) profil çağrısıyla gidiyor; onTokenRefresh listener'ı da yenilerini yakalıyor. Çünkü token'lar gerçekten değişiyor (yeniden kurulum ya da uygulama verisinin silinmesi yeterli).
401, bir kez daha giriş demek
Push API'si JWT (oturum bilgisini imzalı biçimde taşıyan erişim belgesi) kullanıyordu. Her çağrı tek bir küçük post() sarmalayıcısından geçiyor. Cevap 401 (isteğin geçerli bir oturumla doğrulanmadığını bildiren durum kodu) ise önbellekteki token'ı atıyor, tekrar giriş yapıyor ve isteği yeniden deniyor. Tam olarak bir kez.
İkinci bir 401 gerçek bir sorundur ve olduğu gibi çağırana döner. Orada bir döngü, asla çalışmayacak bir ayarla login endpoint'ini (bir serviste istek gönderilen belirli adres) dövmekten başka işe yaramaz.
Yazmadığım bir backend'i debug etmek
En çok buradan öğrendim, kısmen de yanlış yaptığım için.
Gateway 200 dönüyordu ve telefona hiçbir şey gelmiyordu. 200 bana sadece isteğin kabul edildiğini söylüyordu. Zinciri sınıf seviyesinde takip ettim: controller, service, producer, RabbitMQ, consumer ve FCM gönderici. Consumer'ın dinlediği exchange-queue binding'inin gerçekten var olduğunu kontrol ettim (vardı). Test gönderimleri sırasında kuyruğu polling ile izledim. Yine de belirli bir gönderimi, test penceresi içindeki belirli bir publish'e bağlayamadım.
İstediğim şey controller, service ve producer'a trace id'li log eklemekti. Hata da burada: servisi kaynaktan build (kaynak kodu çalıştırılabilir veya yayımlanabilir çıktıya dönüştürme) etmek yerine sınıfları mevcut jar'ın içine elle yamadım. Servis açılışta, ihtiyaç duyduğu bir DTO sınıfı için NoClassDefFoundError ile düştü.
Eski jar'ı geri koydum, servisi tekrar başlattım ve başka hiçbir şeye dokunmadan önce 200 döndüğünü kontrol ettim. Sonra kuralı handoff (işi ve gerekli bilgiyi başka bir ajana veya insana devretme) dokümanına düz cümlelerle yazdım: ya kaynaktan tam build, ya hiç. Çalışan bir servise elle yamanmış jar yok.
Bu sırada uygulamayı backend olmadan da test edilebilir hale getirdim. Debug paneli ham payload'ı, normalize edilmiş payload'ı ve route sonucunu yan yana gösteriyor. Mock (gerçek servis veya verinin yerini alan test örneği) payload'lar ve yerel bir test bildirimi tıklama yolunun tamamını çalıştırıyor. İki PowerShell script'i (belirli bir işi yapan kod dosyası) token'a ya da topic'e gerçek push atıyor. Yerel bildirimin neyi kanıtladığını (uygulama tarafını) ve neyi kanıtlamadığını (sunucu zincirini) da yazdım, kimse yeşil bir yerel testi uçtan uca test sanmasın diye. Backend ekibi bir kontrol listesi aldı ve tam uçtan uca test daha sonra yapıldığında geçti.
Farklı yapacaklarım
İlk günden kaynak repo (projenin dosyalarını ve değişiklik geçmişini tutan kod deposu) ile başlardım. Elle yamanmış jar kısa yol gibi görünüyordu, ama ben geri alana kadar servisi düşürdü.
Her push'ta bir mesaj id'si isterdim. Trace id yoksa tekrar engelleme anahtarı başlık ve gövdeye düşüyor; aynı metni kullanan iki kampanya tek bir tıklamaya dönüşebilir.
Referans uygulama ekranları düz state ile değiştiriyor. Mağazadaki bir uygulamada aynı PushRoute değerlerini navigasyon kütüphanesinin linking ayarına bağlardım; böylece push ile deep link (uygulamanın belirli ekranını doğrudan açan bağlantı) aynı koddan geçerdi.
payload.ts saf bir modül, yani projede test etmesi en kolay şey. Debug panelinde yakaladığım tuhaf payload'ların yeri fixture (test için önceden hazırlanmış örnek veri) testleri. Böylece yeni bir alan adı uyduran bir sonraki gönderici, bir tıklamayı bozmadan önce bir testi bozar.
Bir tıklama, kampanyanın verdiği bir söz. Uygulamanın işi, üç durumun hepsinde o sözü tutmak.
Revotas'taki diğer işlerim /tr/calismalar/revotas/ sayfasında.
Bu yazı İngilizce aslından Türkçeye çevrilmiştir.


Bu yazıya bırakılan notlar
Katıl
Yükleniyor…
Bir yorum bırak.
Bu yazıya not eklemek için giriş yap.
Sadece adın ve avatarın kullanılır.