# Što ćete naučiti#
Ovaj vodič pokazuje kako implementirati deep linking produkcijske razine u Flutteru koristeći Universal Links na iOS-u i Android App Links na Androidu, a zatim te linkove pouzdano rutirati s go_router. Također pokriva deferred deep linkove, osnovne obrasce atribucije u analitici i kontrolnu listu za najčešće pogrešne konfiguracije.
Ako planirate MVP, deep linking je jedna od najisplativijih funkcionalnosti za rast jer smanjuje trenje između marketinškog dodira i akcije u aplikaciji. U timovima s kojima smo radili, popravak pokvarenih deep linkova često povećava stopu konverzije kampanja za 10 do 30 posto jer korisnici završe na ispravnom ekranu umjesto na generičkom home prikazu.
Za povezane arhitekturne odluke koje utječu na rutiranje i granice featurea, pogledajte Arhitektura Flutter aplikacije: clean architecture feature-first. Ako se deep linkovi koriste iz notifikacija, uparite ovo s člankom Flutter push notifikacije u produkciji. Za planiranje isporuke i budžetiranje pogledajte trošak MVP-a mobilne aplikacije.
# Osnove deep linkinga koje stvarno znače#
Deep linkovi su korisni samo ako su predvidljivi kroz ova stanja:
- 1Cold start: aplikacija ne radi i otvara se iz linka.
- 2Warm start: aplikacija je u pozadini i otvara se iz linka.
- 3Foreground: aplikacija je aktivna i prima link.
- 4Nije instalirana: korisnik klikne link i želite ispravan fallback, idealno s deferred rutiranjem nakon instalacije.
Također trebate imati čvrsta dva sloja:
- Verifikacija na razini OS-a: Universal Links i App Links zahtijevaju hostanje verifikacijskih datoteka na vašoj domeni.
- Rutiranje unutar aplikacije: kad URL stigne, router ga mora mapirati na ekrane uz ispravno rukovanje autentikacijom i stanjem.
Universal Links vs Android App Links vs prilagođene sheme#
| Mogućnost | Universal Links (iOS) | Android App Links (Android) | Prilagođena shema (oboje) |
|---|---|---|---|
| Koristi HTTPS | Da | Da | Ne |
| Verifikacija domene | AASA datoteka | assetlinks.json | Nije primjenjivo |
| Pouzdano otvara aplikaciju iz preglednika | Visoko (kad je verificirano) | Visoko (kad je verificirano) | Srednje do nisko |
| Može pasti natrag na web ako aplikacija nije instalirana | Da | Da | Da |
| Najčešći način kvara | AASA nije dostupna ili je pogrešan team ID | Pogrešan SHA-256 fingerprint | Blokira preglednik ili se pojavi upit korisniku |
🎯 Ključna poruka: Ako vam je važna pouzdanost iz emailova, oglasa i društvenih aplikacija, dajte prioritet verificiranim HTTPS linkovima (Universal Links i Android App Links), a prilagođene sheme tretirajte kao sekundarni fallback.
# Preduvjeti#
| Zahtjev | Verzija | Napomene |
|---|---|---|
| Flutter | 3.19+ | Bilo koja novija stable verzija je OK |
| iOS | 13+ | Universal Links se najbolje ponašaju na modernom iOS-u |
| Android | 6.0+ | App Links su široko podržani; verifikacija se poboljšava kroz vrijeme |
| go_router | 13+ | Primjeri koriste tipične go_router obrasce |
| Domena koju kontrolirate | — | Potrebna za verifikacijske datoteke |
# Korak 1: Definirajte strategiju linkova i URL “ugovor”#
Prije bilo kakvog platformskog podešavanja definirajte URL ugovor koji backend, marketing i mobilna aplikacija svi mogu pratiti.
Praktična struktura izgleda ovako:
https://example.com/p/123za detalje proizvodahttps://example.com/invite/ABCDEFza referral ili pozivnice u timhttps://example.com/reset-password?token=...za auth flowove
Izbjegavajte stavljanje osjetljivih podataka u path. Preferirajte kratkotrajne tokene i razmjenu tokena server-side.
Preporučena tablica mapiranja deep linkova#
| URL obrazac | Odredište u aplikaciji | Napomene |
|---|---|---|
/p/:id | ProductDetailsScreen(id) | Dohvat proizvoda po id-u |
/invite/:code | InviteAcceptScreen(code) | Često zahtijeva auth |
/reset-password | ResetPasswordScreen(token) | Token u queryju, brzo validirati |
/u/:username | ProfileScreen(username) | Razmislite o kolizijama slugova |
💡 Savjet: Držite URL-ove stabilnima. Preimenovanje
/p/:idu/product/:idkasnije lomi stare linkove u emailovima i PDF-ovima osim ako održavate redirekcije na web strani.
# Korak 2: Flutter paketi i povezivanje u aplikaciji#
Za primanje linkova tipično kombinirate:
- paket za prihvat linkova kao
app_linksza Universal Links i App Links - go_router za deklarativno rutiranje i redirekcije
- opcionalni analytics SDK (Firebase Analytics, Segment, Amplitude) za attribution eventove
Dodajte pakete#
flutter pub add go_router app_linksNapravite servis za obradu linkova#
Ovaj servis normalizira dolazne URL-ove i prosljeđuje ih go_routeru. Neka bude malen i testabilan.
// deep_link_service.dart
import 'dart:async';
import 'package:app_links/app_links.dart';
class DeepLinkService {
final _appLinks = AppLinks();
Stream<Uri> get uriStream => _appLinks.uriLinkStream;
Future<Uri?> getInitialUri() => _appLinks.getInitialLink();
}# Korak 3: Pouzdano rutiranje unutar aplikacije s go_router#
go_router najbolje radi kad centralizirate logiku redirekcije za auth i onboarding, a zatim deep linkove provodite kroz ista pravila.
Primjer routera s rutama prilagođenima deep linkovima#
// router.dart
import 'package:go_router/go_router.dart';
GoRouter buildRouter({
required bool isLoggedIn,
}) {
return GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/p/:id',
builder: (context, state) => ProductScreen(
id: state.pathParameters['id']!,
),
),
GoRoute(
path: '/invite/:code',
builder: (context, state) => InviteScreen(
code: state.pathParameters['code']!,
),
),
GoRoute(
path: '/reset-password',
builder: (context, state) => ResetPasswordScreen(
token: state.uri.queryParameters['token'] ?? '',
),
),
GoRoute(
path: '/login',
builder: (context, state) => const LoginScreen(),
),
],
redirect: (context, state) {
final loggingIn = state.matchedLocation == '/login';
final requiresAuth = state.matchedLocation.startsWith('/invite');
if (requiresAuth && !isLoggedIn) {
final from = Uri.encodeComponent(state.uri.toString());
return '/login?from=$from';
}
if (isLoggedIn && loggingIn) return '/';
return null;
},
);
}Obradite početni link i linkove tijekom rada aplikacije#
Postoje dva slučaja: aplikacija se otvara preko linka i aplikacija je već pokrenuta.
// deep_link_bootstrap.dart
import 'dart:async';
import 'package:go_router/go_router.dart';
class DeepLinkBootstrapper {
final GoRouter router;
final Stream<Uri> stream;
final Future<Uri?> initial;
StreamSubscription<Uri>? _sub;
DeepLinkBootstrapper({
required this.router,
required this.stream,
required this.initial,
});
Future<void> start() async {
final first = await initial;
if (first != null) {
router.go(first.path + (first.hasQuery ? '?${first.query}' : ''));
}
_sub = stream.listen((uri) {
router.go(uri.path + (uri.hasQuery ? '?${uri.query}' : ''));
});
}
Future<void> dispose() async {
await _sub?.cancel();
}
}⚠️ Upozorenje: Nemojte rutirati na deep link prije nego što je stanje aplikacije spremno. Ako
isLoggedInračunate asinkrono, router gradite sa sinkronim “state holderom” i okidajte refresh kada auth završi, inače ćete dobiti redirect petlje ili pogrešne ekrane.
# Korak 4: iOS Universal Links podešavanje#
Universal Links zahtijevaju dvije stvari:
- 1Uključiti Associated Domains u Xcodeu.
- 2Hostati Apple App Site Association datoteku na vašoj domeni.
4.1 Konfigurirajte Associated Domains#
U Xcodeu:
- Target settings
- Signing and Capabilities
- Add capability: Associated Domains
- Dodajte unose poput:
applinks:example.comapplinks:links.example.com
Ako imate više okruženja, koristite subdomene. Izbjegavajte dijeljenje jedne domene između dev i prod varijanti s različitim bundle ID-ovima osim ako vrlo pažljivo kontrolirate rutiranje.
4.2 Hostajte AASA datoteku#
Datoteka mora biti dostupna na jednoj od ovih putanja:
https://example.com/apple-app-site-associationhttps://example.com/.well-known/apple-app-site-association
Mora se poslužiti kao JSON bez redirekcija. Mnogi timovi ovo pokvare forsiranjem 301 s non-www na www. iOS može failati dohvat i vi ćete se tiho vratiti na Safari.
Primjer AASA:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.example.app",
"paths": ["/p/*", "/invite/*", "/reset-password*"]
}
]
}
}Gdje:
TEAMIDje vaš Apple Team IDcom.example.appje vaš iOS bundle ID
4.3 Validirajte na uređaju#
Nakon instalacije aplikacije testirajte dodirom linka u Notes ili Messages. Ako otvara Safari, verifikacija vjerojatno nije uspjela.
Praktične provjere:
- Potvrdite da je AASA dostupna u običnom pregledniku na točnoj putanji.
- Potvrdite da je odgovor 200 i da nema redirekcije.
- Potvrdite da sadržaj odgovara vašem team ID-u i bundle ID-u.
ℹ️ Napomena: Universal Links se кешiraju na iOS-u. Nakon popravka AASA, možda ćete morati obrisati aplikaciju, restartati uređaj i ponovno instalirati da biste prisilili novu verifikaciju.
# Korak 5: Android App Links podešavanje#
Android App Links zahtijevaju:
- 1Intent filtre u AndroidManifest.xml.
- 2Digital Asset Links datoteku na vašoj domeni.
- 3Ispravne signing fingerprinte za build varijantu koju testirate.
5.1 Dodajte intent filtre#
U android/app/src/main/AndroidManifest.xml pod vašom main activity:
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="example.com"
android:pathPrefix="/p" />
</intent-filter>Ponovite za ostale pathove ili koristite širi filter:
- više intent filtera po hostu i pathPrefixu
- ili jedan filter po hostu i prihvatite sve pathove ako vaša aplikacija sigurno obrađuje nepoznate pathove
5.2 Hostajte assetlinks.json#
Postavite ga na:
https://example.com/.well-known/assetlinks.json
Primjer:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.app",
"sha256_cert_fingerprints": [
"AA:BB:CC:DD:...:ZZ"
]
}
}
]Fingerprint mora odgovarati signing certifikatu aplikacije instalirane na uređaju. To se razlikuje za:
- debug buildove
- lokalne release buildove
- Play Store buildove
- Play App Signing ključeve
5.3 Provjera s adb-om#
Koristite Androidove ugrađene alate za verifikaciju:
adb shell pm get-app-links com.example.app
adb shell am start -a android.intent.action.VIEW -d "https://example.com/p/123"Ako verifikacija ne uspije, Android će obično otvoriti preglednik umjesto aplikacije ili na nekim uređajima prikazati chooser.
# Korak 6: Deferred deep linkovi (kad aplikacija nije instalirana)#
Deferred deep linking znači:
- korisnik dodirne
https://example.com/invite/ABCDEF - instalira aplikaciju
- aplikacija se otvori i navigira na InviteAcceptScreen s kodom
ABCDEF
Ovo je teže od direktnih deep linkova jer OS ne prosljeđuje automatski originalni URL kroz store install flow.
Opcije u 2026.#
| Opcija | Pouzdanost | Složenost postavljanja | Napomene |
|---|---|---|---|
| Vlastiti “install then claim” flow | Srednja | Srednja | Zahtijeva backend + clipboard ili handshake kroz login |
| Pružatelj atribucije treće strane | Visoka | Srednja do visoka | AppsFlyer, Adjust, Branch se često koriste |
| Firebase Dynamic Links | Deprecated za nove projekte | Niska do srednja | Izbjegavajte započinjati nove dugoročne implementacije |
Praktičan pristup za mnoge proizvode: ne jurite savršen deferred deep linking prvi dan. Pobrinite se da fallback web stranica uhvati namjeru i ponudi put “Nastavi u aplikaciji nakon instalacije”.
Izvediv obrazac bez vendora#
- 1Link otvara landing stranicu poput
/invite/ABCDEF. - 2Landing stranica sprema kod server-side vezan uz anonimni session ID.
- 3Landing stranica nudi “Open app” i “Install app”.
- 4Nakon instalacije i prvog pokretanja, aplikacija pita vaš backend “imamo li pending invite za ovaj session ID” koristeći jednokratni token koji landing stranica može proslijediti putem queryja prema storeu ili kroz email login korak.
Ovo nije jednako seamless kao vendor rješenja, ali je mjerljivo i izbjegava lock-in.
💡 Savjet: Za pozivnice i referale često možete prihvatiti “još jedan korak” bez gubitka većine konverzija ako je landing stranica brza. Ciljajte Largest Contentful Paint manji od 2.5 sekunde i držite CTA iznad pregiba (above the fold).
# Korak 7: Osnove analitike i atribucije#
Deep linkovi vrijede samo ako možete mjeriti njihov učinak. Minimalno logirajte:
- deep link otvoren
- deep link rutiran na ekran
- deep link konverzijski event
Što uhvatiti iz URL-a#
| Polje | Primjer | Zašto je važno |
|---|---|---|
| Puni URL | https://example.com/p/123?utm_source=... | Debugiranje i atribucija |
| Path | /p/123 | Rutiranje i grupiranje sadržaja |
| Query parametri | utm_source, utm_campaign, ref | Učinak marketinga |
| Timestamp | ISO string | Povezivanje sesija |
| Stanje instalacije | installed or not | Deep link vs deferred |
Primjer: izvuci i zabilježi UTM parametre#
Map<String, String> extractUtm(Uri uri) {
final qp = uri.queryParameters;
return {
'utm_source': qp['utm_source'] ?? '',
'utm_medium': qp['utm_medium'] ?? '',
'utm_campaign': qp['utm_campaign'] ?? '',
'utm_content': qp['utm_content'] ?? '',
};
}Zatim logirajte eventove poput:
deep_link_openeddeep_link_routedinvite_accepted
Držite naming konzistentnim i stabilnim, inače dashboardi postaju neupotrebljivi.
# Rješavanje problema: česti problemi i rubni slučajevi#
Ovo su kvarovi koje najčešće viđamo u produkcijskim launchovima.
Linkovi otvaraju preglednik umjesto aplikacije#
iOS provjere
| Provjera | Očekivano | Rješenje |
|---|---|---|
| AASA dostupna | 200 OK, bez redirekcije | Uklonite redirekcije, poslužite s root-a ili .well-known |
| appID odgovara | TEAMID i bundle ID ispravni | Ažurirajte AASA details |
| Associated Domains | dodan applinks:example.com | Omogućite capability u Xcodeu |
| Caching | iOS kešira verifikaciju | Obrišite app, reboot uređaja, reinstall |
Android provjere
| Provjera | Očekivano | Rješenje |
|---|---|---|
| assetlinks putanja | /.well-known/assetlinks.json | Postavite datoteku ispravno |
| Fingerprint | odgovara signaturi instalirane aplikacije | Koristite ispravan debug ili release SHA-256 |
| Manifest | intent-filter uključuje host i scheme | Dodajte filter s autoVerify |
| Status verifikacije | verificirano u sustavu | Koristite pm get-app-links za uvid |
Deep link otvori aplikaciju, ali rutira na pogrešan ekran#
Česti uzroci:
- 1Rutirate koristeći
uri.toString(), a router očekuje samo path. - 2Koristite
gokad vam trebapushili obratno. - 3Auth redirekcija pregazi deep link i ne vratite se nakon login-a.
Robustan obrazac je sačuvati namjeravano odredište u from queryju, pa redirektati nakon uspješne prijave.
String? fromAfterLogin(Uri loginUri) {
final from = loginUri.queryParameters['from'];
return from == null || from.isEmpty ? null : Uri.decodeComponent(from);
}Duplicirani ekrani ili čudna back navigacija#
To se često događa kada:
- zovete
router.goviše puta za isti URI - vaš link listener okine dvaput na Androidu zbog recreationa activityja
- miješate imperativne Navigator pozive s go_routerom
Mitigacije:
- debounce identičnih URI-eva unutar 300 do 500 ms
- koristite go_router svugdje, izbjegavajte raw Navigator u feature ekranima
- preferirajte
goza “ovaj link definira trenutnu lokaciju”, apushza “otvori povrh trenutnog flowa”
Universal Links rade u Messages, ali ne u Gmailu ili Slacku#
Neke aplikacije otvaraju linkove u vlastitom in-app browseru, što može promijeniti ponašanje linkova. Universal Links i App Links su najpouzdaniji iz sistemskih aplikacija i punih preglednika, ali ponašanje varira po aplikaciji i verziji OS-a.
Praktično rješenje:
- osigurajte da je web fallback stranica dobra
- uključite eksplicitni “Open in app” gumb koji koristi custom scheme kao fallback za in-app browsere
Imajte na umu da custom scheme može biti blokiran ili zahtijevati potvrdu korisnika, pa ga tretirajte kao best-effort.
Deep linkovi iz push notifikacija konfliktiraju s normalnim deep linkovima#
Ako navigirate i iz notifikacija, ujedinite oboje u isti “routing contract”. Payload notifikacije treba nositi URL, a ne ad-hoc naziv ekrana. To drži logiku konzistentnom i testabilnom.
Za produkcijsko postavljanje notifikacija pogledajte Flutter push notifikacije u produkciji.
# Ključne poruke#
- Koristite verificirane HTTPS linkove za pouzdanost: Universal Links na iOS-u i Android App Links na Androidu, uz ispravno hostanje AASA i assetlinks.json.
- Deep linkove tretirajte kao URL ugovor u vlasništvu produkta i engineeringa, a URL obrasce mapirajte na go_router rute s konzistentnim redirekcijama.
- Obradite i početne linkove i streamove linkova te odgodite rutiranje dok auth i stanje aplikacije nisu spremni kako biste izbjegli redirect petlje.
- Za deferred deep linkove ili koristite attribution vendora ili implementirajte mjerljiv “install then claim” flow uz snažan web fallback.
- Instrumentirajte deep link eventove i UTM parametre kako bi marketing i product mogli mjeriti konverzije end-to-end.
- Debugirajte sistematično alatima na uređaju: iOS caching ponašanje, Android
pm get-app-linksi signature fingerprinte po build varijanti.
# Zaključak#
Flutter deep linking nije samo otvaranje aplikacije; radi se o tome da korisnik završi na ispravnom ekranu kroz cold start, background i foreground, uz mjerljivu atribuciju. Ako ispravno implementirate verifikaciju domene i centralizirate rutiranje s go_router, uklanjate najčešće uzroke pokvarenih linkova i izgubljenih konverzija.
Ako želite da Samioda postavi deep linking end-to-end, uključujući deferred deep linkove, analytics atribuciju i routing arhitekturu koja skalira s featureima, kontaktirajte nas i pregledat ćemo vašu trenutnu konfiguraciju te isporučiti produkcijski spremnu implementaciju u predvidljivom sprintu.
FAQ
Više iz kategorije Mobilni razvoj
Sve →Flutter CI/CD u 2026.: GitHub Actions vs Codemagic vs Fastlane (uz nacrt produkcijskog pipelinea)
Praktičan vodič za Flutter CI/CD u 2026.: usporedba GitHub Actionsa, Codemagica i Fastlanea te implementacija produkcijski spremnog pipelinea s flavorima, potpisivanjem, build brojevima, testovima, generiranjem koda, cacheiranjem i deployem na trgovine.
Flutter push notifikacije u produkciji: FCM + APNs, deep linkovi i pouzdanost (vodič za 2026.)
End-to-end produkcijski vodič za Flutter push notifikacije uz FCM i APNs: postavljanje, životni ciklus tokena, segmentacija, deep linkovi, obrada u pozadini i pri ugašenoj aplikaciji, te checkliste za pouzdanost i otklanjanje problema.
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.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
Flutter push notifikacije u produkciji: FCM + APNs, deep linkovi i pouzdanost (vodič za 2026.)
End-to-end produkcijski vodič za Flutter push notifikacije uz FCM i APNs: postavljanje, životni ciklus tokena, segmentacija, deep linkovi, obrada u pozadini i pri ugašenoj aplikaciji, te checkliste za pouzdanost i otklanjanje problema.
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.