Mobilni razvoj
FlutterRazvoj mobilnih aplikacijaDeep linkingUniversal LinksAndroid App Linksgo_routerAnalitika

Vodič za Flutter deep linking za 2026.: Universal Links, Android App Links i pouzdano rutiranje unutar aplikacije

AO
Adrijan Omićević
·13 min čitanja

# Š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:

  1. 1
    Cold start: aplikacija ne radi i otvara se iz linka.
  2. 2
    Warm start: aplikacija je u pozadini i otvara se iz linka.
  3. 3
    Foreground: aplikacija je aktivna i prima link.
  4. 4
    Nije 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.
MogućnostUniversal Links (iOS)Android App Links (Android)Prilagođena shema (oboje)
Koristi HTTPSDaDaNe
Verifikacija domeneAASA datotekaassetlinks.jsonNije primjenjivo
Pouzdano otvara aplikaciju iz preglednikaVisoko (kad je verificirano)Visoko (kad je verificirano)Srednje do nisko
Može pasti natrag na web ako aplikacija nije instaliranaDaDaDa
Najčešći način kvaraAASA nije dostupna ili je pogrešan team IDPogrešan SHA-256 fingerprintBlokira 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#

ZahtjevVerzijaNapomene
Flutter3.19+Bilo koja novija stable verzija je OK
iOS13+Universal Links se najbolje ponašaju na modernom iOS-u
Android6.0+App Links su široko podržani; verifikacija se poboljšava kroz vrijeme
go_router13+Primjeri koriste tipične go_router obrasce
Domena koju kontroliratePotrebna 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/123 za detalje proizvoda
  • https://example.com/invite/ABCDEF za referral ili pozivnice u tim
  • https://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 obrazacOdredište u aplikacijiNapomene
/p/:idProductDetailsScreen(id)Dohvat proizvoda po id-u
/invite/:codeInviteAcceptScreen(code)Često zahtijeva auth
/reset-passwordResetPasswordScreen(token)Token u queryju, brzo validirati
/u/:usernameProfileScreen(username)Razmislite o kolizijama slugova

💡 Savjet: Držite URL-ove stabilnima. Preimenovanje /p/:id u /product/:id kasnije 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_links za Universal Links i App Links
  • go_router za deklarativno rutiranje i redirekcije
  • opcionalni analytics SDK (Firebase Analytics, Segment, Amplitude) za attribution eventove

Dodajte pakete#

Bash
flutter pub add go_router app_links

Napravite servis za obradu linkova#

Ovaj servis normalizira dolazne URL-ove i prosljeđuje ih go_routeru. Neka bude malen i testabilan.

Dart
// 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#

Dart
// 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;
    },
  );
}

Postoje dva slučaja: aplikacija se otvara preko linka i aplikacija je već pokrenuta.

Dart
// 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 isLoggedIn rač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.

Universal Links zahtijevaju dvije stvari:

  1. 1
    Uključiti Associated Domains u Xcodeu.
  2. 2
    Hostati 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.com
    • applinks: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-association
  • https://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:

JSON
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAMID.com.example.app",
        "paths": ["/p/*", "/invite/*", "/reset-password*"]
      }
    ]
  }
}

Gdje:

  • TEAMID je vaš Apple Team ID
  • com.example.app je 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.

Android App Links zahtijevaju:

  1. 1
    Intent filtre u AndroidManifest.xml.
  2. 2
    Digital Asset Links datoteku na vašoj domeni.
  3. 3
    Ispravne signing fingerprinte za build varijantu koju testirate.

5.1 Dodajte intent filtre#

U android/app/src/main/AndroidManifest.xml pod vašom main activity:

XML
<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

Postavite ga na:

  • https://example.com/.well-known/assetlinks.json

Primjer:

JSON
[
  {
    "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:

Bash
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.#

OpcijaPouzdanostSloženost postavljanjaNapomene
Vlastiti “install then claim” flowSrednjaSrednjaZahtijeva backend + clipboard ili handshake kroz login
Pružatelj atribucije treće straneVisokaSrednja do visokaAppsFlyer, Adjust, Branch se često koriste
Firebase Dynamic LinksDeprecated za nove projekteNiska do srednjaIzbjegavajte 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#

  1. 1
    Link otvara landing stranicu poput /invite/ABCDEF.
  2. 2
    Landing stranica sprema kod server-side vezan uz anonimni session ID.
  3. 3
    Landing stranica nudi “Open app” i “Install app”.
  4. 4
    Nakon 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#

PoljePrimjerZašto je važno
Puni URLhttps://example.com/p/123?utm_source=...Debugiranje i atribucija
Path/p/123Rutiranje i grupiranje sadržaja
Query parametriutm_source, utm_campaign, refUčinak marketinga
TimestampISO stringPovezivanje sesija
Stanje instalacijeinstalled or notDeep link vs deferred

Primjer: izvuci i zabilježi UTM parametre#

Dart
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_opened
  • deep_link_routed
  • invite_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

ProvjeraOčekivanoRješenje
AASA dostupna200 OK, bez redirekcijeUklonite redirekcije, poslužite s root-a ili .well-known
appID odgovaraTEAMID i bundle ID ispravniAžurirajte AASA details
Associated Domainsdodan applinks:example.comOmogućite capability u Xcodeu
CachingiOS kešira verifikacijuObrišite app, reboot uređaja, reinstall

Android provjere

ProvjeraOčekivanoRješenje
assetlinks putanja/.well-known/assetlinks.jsonPostavite datoteku ispravno
Fingerprintodgovara signaturi instalirane aplikacijeKoristite ispravan debug ili release SHA-256
Manifestintent-filter uključuje host i schemeDodajte filter s autoVerify
Status verifikacijeverificirano u sustavuKoristite pm get-app-links za uvid

Česti uzroci:

  1. 1
    Rutirate koristeći uri.toString(), a router očekuje samo path.
  2. 2
    Koristite go kad vam treba push ili obratno.
  3. 3
    Auth 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.

Dart
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.go viš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 go za “ovaj link definira trenutnu lokaciju”, a push za “otvori povrh trenutnog flowa”

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-links i 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

Share
A
Adrijan OmićevićSamioda Team
All articles →

Trebate pomoć s projektom?

Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.