# Što ćete izgraditi (i zašto je produkcija drugačija)#
Ovaj vodič pokriva Flutter push notifikacije FCM APNs end-to-end, s produkcijskim načinom razmišljanja: ne samo “prikaže se banner”, nego isporuku, routing, životni ciklus tokena i observability.
U produkciji push sustavi padaju iz dosadnih razloga: istekli tokeni, krivo konfigurirani APNs ključevi, pogrešno okruženje, nedostajući background handleri ili krhko routanje deep linkova. Razlika između “push featurea” i push platforme je pouzdanost.
Implementirat ćete:
- FCM + APNs postavljanje za Android i iOS
- strategiju životnog ciklusa tokena koja preživi reinstalacije aplikacije, odjave i promjene uređaja
- obrasce topicova i segmentacije koji skaliraju dalje od “pošalji svima”
- deep linking iz notifikacija kroz foreground, background i terminated stanja
- checkliste za troubleshooting i savjete za isporučivost koje možete predati timu
Ovo možete upariti s našim vodičem za Firebase temelje: Flutter Firebase tutorial.
# Preduvjeti#
| Zahtjev | Verzija | Napomene |
|---|---|---|
| Flutter | 3.19+ | Koristite najnoviji stable ako je moguće |
| Dart | 3+ | Dolazi uz Flutter |
| Firebase projekt | — | FCM uključen |
| iOS | Xcode 15+ | Apple Developer račun potreban za APNs |
| Android | minSdk 21+ | Preporučeno za moderno ponašanje |
| Paketi | Najnovije | firebase_core, firebase_messaging, opcionalno flutter_local_notifications |
ℹ️ Napomena: Na iOS-u notifikacije u konačnici isporučuje APNs. FCM djeluje kao vaš provider i sloj mapiranja, ali vaša iOS aplikacija mora biti ispravno entitleana i potpisana, inače će isporuka tiho failati.
# Arhitektura: FCM, APNs i vaš backend#
Pouzdana produkcijska postava obično izgleda ovako:
| Komponenta | Odgovornost | Produkcijski rizik |
|---|---|---|
| Flutter app | Traži dopuštenje, upravlja tokenima, obrađuje tapove, routa deep linkove | Upravljanje stanjima, refresh tokena, UX |
| FCM | Registracija uređaja, topic messaging, delivery bridge prema APNs-u | Postavljanje APNs ključa, format poruke |
| APNs | iOS isporuka | Entitlementi, okruženje, headeri, push type |
| Backend | Ciljanje korisnika, segmenti, idempotency, audit logovi | Sigurnost, retryji, analitika |
Preporuka: Šaljite s backenda, ne iz aplikacije#
Slanje push notifikacija direktno iz aplikacije je teško osigurati i praktički nemoguće kvalitetno auditirati. U produkciji tretirajte push kao backend mogućnost uz:
- autentificirane send API-je
- rate limite
- predloške poruka
- metrike isporuke po kampanji
Za security hardening, uskladite push endpointove sa širom checklistom: Web application security checklist.
# Korak 1: Konfigurirajte Firebase i FCM#
Android: Dodajte Firebase i registrirajte aplikaciju#
- 1Kreirajte Android app u Firebaseu s točnim
applicationId. - 2Preuzmite
google-services.jsonuandroid/app. - 3Primijenite Google services plugin i provjerite da ovisnosti odgovaraju vašoj Gradle verziji.
Na Androidu 13 i novijem morate tražiti dopuštenje za notifikacije u runtimeu (Flutter plugin pomaže, ali i dalje ga morate eksplicitno pozvati).
iOS: Dodajte Firebase i APNs vjerodajnice#
- 1Kreirajte iOS app u Firebaseu s točnim bundle ID-jem.
- 2Preuzmite
GoogleService-Info.plistuios/Runner. - 3U Apple Developer:
- uključite capability Push Notifications za App ID
- generirajte APNs Auth Key (preporučeno)
- 4U Firebase konzoli:
- uploadajte APNs Auth Key
.p8 - postavite Key ID i Team ID
Koristite APNs Auth Key za dugoročnu pouzdanost. Certifikati istječu i uzrokuju iznenadne ispade.
⚠️ Upozorenje: Jedan od najčešćih produkcijskih failova je korištenje pogrešnog bundle ID-ja ili provisioning profila. Ako signing identity ne odgovara entitlementima, APNs registracija može uspjeti lokalno, ali isporuka će failati u produkciji.
# Korak 2: Dodajte pakete i inicijalizirajte messaging#
Dodajte pakete:
flutter pub add firebase_core firebase_messagingInicijalizirajte Firebase i konfigurirajte background obradu.
// main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
// Keep this minimal: log, enqueue work, update local storage.
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
runApp(const MyApp());
}Zatražite dopuštenje i konfigurirajte foreground prikaz (iOS)#
Future<void> setupPushPermissions() async {
final messaging = FirebaseMessaging.instance;
final settings = await messaging.requestPermission(
alert: true,
badge: true,
sound: true,
provisional: false,
);
// iOS: show notifications while app is in foreground
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
// You can log settings.authorizationStatus for analytics.
}Produkcijska napomena: stope prihvaćanja dopuštenja jako variraju po kategoriji. Mnoge consumer aplikacije imaju opt-in oko 50 do 80 posto uz dobar value prompt, dok “cold prompt” može biti značajno niži. Koristite pre-permission ekran ako o notifikacijama ovisi ključni dio workflowa.
# Korak 3: Životni ciklus tokena koji ne razbije kampanje#
Upravljanje tokenima je mjesto gdje se većina sustava s vremenom degradira.
Što može promijeniti token#
FCM registration tokeni nisu stabilni identifikatori. Mogu se rotirati zbog:
- reinstalacije aplikacije ili brisanja podataka
- OS restorea ili promjene uređaja
- updatea aplikacije i interne rotacije
- uključivanja/isključivanja postavki notifikacija od strane korisnika
- promjena iOS APNs tokena koje dovode do novog mapiranja FCM tokena
Tokene morate tretirati kao promjenjive i držati ih sinkronizirane s backendom.
Minimalna produkcijska strategija za tokene#
| Događaj | Akcija u aplikaciji | Akcija na backendu |
|---|---|---|
| Start aplikacije nakon login-a | dohvat tokena | upsert token pod user ID |
| Refresh tokena | pošalji novi token | označi stari token neaktivnim |
| Logout | obriši token lokalno | ukloni vezu s korisnikom |
| Uninstall | nema callbacka | detektiraj kroz send failove i očisti |
Implementirajte:
getToken()pri startu- listener
onTokenRefresh - server-side upsert ključan po
userId + deviceId
Future<void> syncTokenWithBackend(String userId) async {
final messaging = FirebaseMessaging.instance;
final token = await messaging.getToken();
if (token != null) {
await sendTokenToBackend(userId: userId, fcmToken: token);
}
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) async {
await sendTokenToBackend(userId: userId, fcmToken: newToken);
});
}Backend endpoint neka bude idempotentan. Ako mobilna aplikacija pošalje isti token više puta, to ne smije stvarati duplikate.
💡 Savjet: Spremite tokene s metapodacima: platforma, verzija aplikacije, locale, vremenska zona i last-seen timestamp. To omogućuje cleanup jobove i pametniju segmentaciju bez dodatnih poziva s klijenta.
Backend model podataka za tokene#
| Polje | Tip | Zašto je bitno |
|---|---|---|
| id | UUID | Interna referenca |
| user_id | string | Ciljanje i privatnost |
| device_id | string | Podrška za više uređaja |
| fcm_token | string | Adresa za isporuku |
| platform | enum | iOS i Android se razlikuju |
| app_version | string | Debugging rollouta |
| enabled | boolean | Opt-out obrada |
| last_seen_at | timestamp | Cleanup i “svježina” |
Čišćenje i pruning tokena#
FCM send odgovori će vam reći kada je token nevažeći. Napravite nightly job koji:
- onemogućava tokene koji hard-failaju
- uklanja tokene koji nisu viđeni 60 do 180 dana (po vašoj politici)
- čuva audit logove radi compliancea
Ako ne pruneate, platit ćete kroz lošiju isporučivost i “šumnije” metrike.
# Korak 4: Topics i segmenti koji skaliraju#
Strategija samo s topicsima rijetko preživi prvi marketing campaign. Strategija samo sa segmentima može preopteretiti backend fanoutom.
Koristite hibridni pristup:
Kada topics imaju smisla#
Topics su najbolji za:
- široke interesne skupine poput
sports,promotions,product_updates - globalne operativne push poruke poput
status_incidents - ekstremno jednostavne pretplate koje kontrolirate u aplikaciji
Pretplate na topics u aplikaciji:
Future<void> subscribeToTopics() async {
await FirebaseMessaging.instance.subscribeToTopic('product_updates');
await FirebaseMessaging.instance.subscribeToTopic('promotions');
}Kada je backend segmentacija bolja#
Koristite backend segmentaciju kada trebate:
- ciljanja po više atributa poput država + plan + prozor aktivnosti
- frequency capove poput max 2 push poruke dnevno
- A B testove i holdout grupe
- compliance pravila, npr. marketinški consent po regiji
| Strategija | Složenost | Preciznost ciljanja | Operativni rizik |
|---|---|---|---|
| Samo topics | Niska | Srednja | Srednji, topics se lako “zapetljaju” |
| Samo backend segmenti | Visoka | Visoka | Visok, fanout troškovi i bugovi |
| Hibrid | Srednja | Visoka | Nizak do srednji uz disciplinu |
Praktični primjeri segmenata#
- “Aktivni u zadnjih 7 dana i visok rizik od churn-a”
- “Plaćeni korisnici na verziji manjoj od 3.4.0”
- “Croatia locale i opt-in na marketing”
U produkciji je najveća dobit obično frequency capping. Više smanjuje opt-out i povećava povjerenje nego bilo kakva promjena copyja.
# Korak 5: Deep linkovi iz notifikacija (bez slomljenog routanja)#
Notifikacija vrijedi samo ako dovede korisnika na pravi ekran.
Dizajn payloada: Data prvo#
Preferirajte data payload polja koja kontrolirate, pa ih mapirajte u rute:
| Key | Primjer | Svrha |
|---|---|---|
type | order_update | Routing i analitika |
deep_link | myapp://orders/123 | Navigacijska meta |
id | 123 | Lookup entiteta |
campaign_id | spring_2026_01 | Atribucija |
dedupe_id | user123-ord123-v2 | Idempotency |
Izbjegavajte enkodiranje kompleksnih JSON stringova. Payload neka bude mali i predvidiv.
Obrada u foreground, background i terminated stanju#
Morate pokriti tri toka:
- 1Foreground: poruka stiže dok je aplikacija otvorena
- 2Background: korisnik tapne notifikaciju
- 3Terminated: aplikacija se pokreće nakon tap-a
Osnovni listeneri:
void setupMessageHandlers() {
FirebaseMessaging.onMessage.listen((message) {
// Foreground: you may show an in-app banner or local notification.
handleMessage(message, openedFromTap: false);
});
FirebaseMessaging.onMessageOpenedApp.listen((message) {
// Background: user tapped notification.
handleMessage(message, openedFromTap: true);
});
}
Future<void> handleInitialMessage() async {
final initial = await FirebaseMessaging.instance.getInitialMessage();
if (initial != null) {
handleMessage(initial, openedFromTap: true);
}
}Sigurno routanje nakon starta aplikacije#
Navigacija deep linkom često faila jer aplikacija pokušava navigirati prije nego što su:
- učitana korisnička sesija
- router spreman
- potrebni podaci “hidratizirani”
Koristite queue: spremite deep link, pa routajte tek kad je aplikacija spremna.
String? pendingDeepLink;
void handleMessage(RemoteMessage message, {required bool openedFromTap}) {
final deepLink = message.data['deep_link'];
if (deepLink is String && deepLink.isNotEmpty) {
pendingDeepLink = deepLink;
}
}
void onAppReadyNavigate() {
final link = pendingDeepLink;
pendingDeepLink = null;
if (link == null) return;
// Parse and route using your navigation solution.
// Example: go_router or Navigator.
}🎯 Ključna poruka: Tretirajte navigaciju iz notifikacija kao i bilo koji drugi deep link: validirajte inpute, odgodite routanje dok aplikacija nije spremna i učinite ga idempotentnim.
Sigurnost deep linkova#
Nikad nemojte slijepo vjerovati notification data payloadu. U nekim kontekstima napadači mogu replayati ili spoofati intentove. Validirajte:
- dopuštene putanje
- obavezne parametre
- autorizaciju korisnika za pristup entitetu
Ovo je isti princip kao na webu: validacija na serveru i na klijentu. Primijenite mindset iz sigurnosne checkliste: Web application security checklist.
# Korak 6: Background obrada i obrasci pouzdanosti#
Ograničenja background handlera#
Background handler nije za težak posao. Zadržite ga na:
- logiranju
- pisanju u local storage
- scheduleanju lokalnog taska
- sinkanju malog payloada
Ako trebate garantirano izvršavanje u pozadini, dizajnirajte eventual consistency: ažurirajte stanje na serveru i pustite aplikaciju da fetch-a updateove pri sljedećem otvaranju.
Koristite local notifications za konzistentan UX u foregroundu#
Na Androidu i iOS-u foreground push poruke možda neće prikazati sistemski UI osim ako ga konfigurirate. Mnogi timovi koriste flutter_local_notifications kako bi prikazali konzistentan banner kad se okine onMessage.
Ako to radite, držite se dva pravila:
- nikad ne “double-notify”: pokažite local notification samo kada se remote notifikacija ionako ne prikazuje
- koristite iste payload ključeve kako bi tap handling ostao konzistentan
Idempotency i deduplikacija#
Mobilne mreže su nepouzdane. Korisnici mogu tapnuti dvaput. Isporuka se može retryati. Vaša aplikacija treba tretirati akcije pokrenute notifikacijom kao idempotentne.
Koristite dedupe_id i spremite nedavno obrađene ID-jeve na 24 sata:
- ako je već obrađeno, ignorirajte
- ako nije, obradite i spremite
To smanjuje duplikate navigacije i ponovljene dijaloge.
# Isporučivost i pouzdanost: što stvarno pravi razliku#
Pouzdanost je većinom operativna disciplina, ne kod.
Praktična checklist za isporučivost#
| Područje | Što napraviti | Zašto pomaže |
|---|---|---|
| Svježina tokena | pruneajte zastarjele tokene, pratite last seen | smanjuje invalid sendove i šum |
| Permission UX | odgodite permission dok vrijednost nije jasna | povećava opt-in |
| Quiet hours | poštujte lokalno vrijeme korisnika | smanjuje opt-out |
| Frequency caps | provedite per user i per campaign | smanjuje zamor |
| Veličina payloada | držite data mali | smanjuje failove i parsing bugove |
| Monitoring | alertajte na send failove i pad isporuke | brzo hvata outage |
Mjerite ono što je bitno#
Minimum koji trebate pratiti:
- attempted sends
- accepted by FCM
- stopa invalid tokena
- otvaranja iz push-a
- opt-out stopa
- vrijeme do otvaranja
“Dobra” stopa invalid tokena ovisi o churnu i profilu reinstalacija, ali ako kontinuirano raste, životni ciklus tokena vam je slomljen.
Za performanse kada se aplikacija otvara iz push-a, pazite da target ekran ostane brz. Ako deep link sleti na “tešku” stranicu, korisnici odustaju. Osvježite osnove performansi ovdje: Flutter performance optimization for 60 FPS.
# Troubleshooting checkliste (copy-paste za runbook)#
iOS checklist: Push nije isporučen#
- 1Potvrdite da je Push Notifications capability uključen za App ID.
- 2Potvrdite da se za build koristi ispravan provisioning profile.
- 3Potvrdite da je APNs Auth Key uploadan u Firebase i da Team ID odgovara.
- 4Potvrdite da uređaj ima dopuštenje u iOS Settings.
- 5Potvrdite da je
setForegroundNotificationPresentationOptionspostavljen za prikaz u foregroundu. - 6Potvrdite da bundle ID točno odgovara Firebase app registraciji.
- 7Potvrdite da ne testirate na simulatoru za stvarno APNs ponašanje.
- 8Potvrdite da poruka uključuje ispravna polja za vaš use case:
- koristite data payload za routing
- uključite notification payload ako želite sistemski UI bez local notifications
Android checklist: Notifikacija se ne prikazuje#
- 1Android 13 i noviji: potvrdite da je POST_NOTIFICATIONS dopuštenje odobreno.
- 2Potvrdite da aplikacija ima default notification channel i da je importance high.
- 3Potvrdite da ne suppressate notifikacije u foregroundu bez local notifications.
- 4Potvrdite da battery optimization ne ograničava aplikaciju previše.
- 5Potvrdite da payload uključuje
notificationpolja ako se oslanjate na sistemski UI.
Token i backend checklist: “Radi nekim korisnicima”#
- 1Provjerite da backend sprema više tokena po korisniku za multi-device.
- 2Provjerite da logout uklanja asocijaciju tokena.
- 3Provjerite da token refresh ažurira backend.
- 4Provjerite da ne prepisujete tokene između korisnika.
- 5Provjerite da se invalid tokeni pruneaju nakon FCM odgovora.
- 6Provjerite da su segmentation filteri točni i auditirani.
Deep link checklist: Otvori aplikaciju, ali ne pravi ekran#
- 1Provjerite da se
getInitialMessage()obrađuje za terminated stanje. - 2Provjerite da se
onMessageOpenedAppobrađuje za background stanje. - 3Provjerite da routing čeka dok sesija i router nisu spremni.
- 4Provjerite da su deep linkovi validirani i mapirani na postojeće rute.
- 5Provjerite da je tap handling idempotentan i dedupliciran.
# Produkcijski formati poruka (primjeri)#
Primjer: Samo data za custom obradu#
{
"message": {
"token": "{{fcm_token}}",
"data": {
"type": "order_update",
"deep_link": "myapp://orders/123",
"campaign_id": "ops_2026_04",
"dedupe_id": "user42-order123-v1"
}
}
}Primjer: Notification + Data za sistemski UI plus routing#
{
"message": {
"token": "{{fcm_token}}",
"notification": {
"title": "Order shipped",
"body": "Tracking is available now."
},
"data": {
"type": "order_update",
"deep_link": "myapp://orders/123"
}
}
}Držite server-side template varijable poput {{fcm_token}} eksplicitnima i validirajte sve podatke prije slanja.
# Ključne lekcije#
- Implementirajte stvarni životni ciklus tokena: upsert pri login-u, slušajte refresh, disocirajte pri logoutu i server-side pruneajte invalid tokene.
- Koristite hibridnu strategiju ciljanja: topics za široke interese, backend segmenti za precizno targetiranje, frequency capove i compliance.
- Pokrijte sva stanja aplikacije:
onMessageza foreground,onMessageOpenedAppza background tapove igetInitialMessage()za pokretanje iz terminated stanja. - Dizajnirajte payload za routing: mali, stabilni ključevi poput
type,deep_link,campaign_ididedupe_idza idempotency i analitiku. - Tretirajte pouzdanost kao operacije: pratite stopu invalid tokena, padove isporuke i latenciju otvaranja iz push-a te održavajte runbook s iOS i Android checklistama.
# Zaključak#
Flutter push notifikacije je lako demonstrirati, a iznenađujuće lako slomiti u produkciji. Ako izgradite disciplinu oko životnog ciklusa tokena, predvidive payloade, deep-link routanje kroz sva stanja i osnovni monitoring isporučivosti, dobit ćete sustav koji raste s bazom korisnika umjesto da se s vremenom degradira.
Ako želite da Samioda pregleda vašu trenutnu postavu ili implementira produkcijski push pipeline sa segmentacijom i deep linkovima, kontaktirajte nas i pomoći ćemo vam isporučiti notifikacije kojima korisnici stvarno vjeruju i koje stvarno otvaraju.
FAQ
Više iz kategorije Mobilni razvoj
Sve →Flutter offline-first aplikacije: lokalna pohrana, strategije sinkronizacije i rješavanje konflikata
Izgradite pouzdane offline-first Flutter aplikacije s robusnom lokalnom pohranom, sinkronizacijom u pozadini i rješavanjem konflikata. Uključuje referentnu arhitekturu i praktičnu checklistu za testiranje na nestabilnim mrežama.
Optimizacija performansi u Flutteru: kako održavamo aplikacije na 60 fps (profiliranje + popravci)
Ponovljiv workflow za optimizaciju performansi u Flutteru uz DevTools: dijagnosticiranje trzanja (jank), zatim ciljane dorade — smanjenje rebuildova, poboljšanja renderiranja, image pipeline i isolates — uz budžete i kontrolne liste.
Flutter arhitektura aplikacije koja se skalira: Clean Architecture vs Feature-First (s realnim strukturama mapa)
Praktičan vodič za arhitekturu Flutter aplikacije u 2026.: usporedite Clean Architecture i Feature-First, pogledajte stvarne strukture mapa, granice ovisnosti i strategije testiranja te odaberite pravi pristup za svoj tim i ritam izdanja.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
Flutter + Firebase: Potpuni vodič za 2026. (Auth, Firestore, Functions, Deploy)
Korak-po-korak Flutter Firebase vodič za 2026.: postavite Firebase, dodajte autentifikaciju, izgradite Firestore CRUD, napišite Cloud Functions i deployajte aplikaciju spremnu za produkciju.
Upravljanje stanjem u Flutteru 2026: Riverpod vs Bloc vs Provider
Praktična usporedba Riverpoda, Bloca i Providera za flutter state management 2026—performanse, DX, testiranje, arhitektura i kada odabrati koji pristup.
Flutter offline-first aplikacije: lokalna pohrana, strategije sinkronizacije i rješavanje konflikata
Izgradite pouzdane offline-first Flutter aplikacije s robusnom lokalnom pohranom, sinkronizacijom u pozadini i rješavanjem konflikata. Uključuje referentnu arhitekturu i praktičnu checklistu za testiranje na nestabilnim mrežama.