# Što ćeš naučiti#
Ovaj vodič donosi ponovljiv workflow za optimizaciju performansi u Flutteru koji koristimo u produkciji kako bismo tipične interakcije držali na 60 fps na uređajima srednje klase.
Naučit ćeš kako profilirati jank uz Flutter DevTools, prepoznati je li usko grlo rad na UI threadu ili rad na raster threadu, te primijeniti popravke u četiri područja s najvećim učinkom: smanjenje rebuildova, renderiranje, slike, i isolates.
Također ćemo definirati praktične performance budžete za česte ekrane i uključiti kontrolne liste prije i poslije koje možeš ponoviti za svaku značajku.
# Zašto je 60 fps važno i što je zapravo “jank”#
Na uređaju od 60 Hz imaš oko 16,67 milisekundi za izgradnju i renderiranje svakog framea. Ako UI posao ili rasterizacija prijeđu taj budžet, frameovi promaše svoj rok i korisnici vide trzanje.
Na 120 Hz uređajima budžet je oko 8,33 milisekundi, pa aplikacija koja je “skroz OK” na 60 Hz može djelovati tromo na modernim mobitelima.
ℹ️ Napomena: Mnogi “problemi s performansama” su zapravo problemi konzistentnosti. Ekran koji u prosjeku traje 10 ms, ali svakih nekoliko sekundi skoči na 40 ms, i dalje djeluje loše jer ljudi nepravilno kretanje primjećuju više nego same prosjeke.
# Performance budžeti koje koristimo za česte ekrane#
Budžeti pomažu da ne optimiziraš nasumično. Odabereš klasu ciljnih uređaja i definiraš što znači “dovoljno dobro” po interakciji.
| Ekran ili interakcija | Ciljani FPS | Budžet UI threada | Budžet rastera | Bilješke |
|---|---|---|---|---|
| Pokretanje aplikacije do prvog interaktivnog framea | 60 | manje od 16,67 ms po frameu | manje od 16,67 ms | Mjeri odvojeno od vremena cold starta |
| Scroll home liste sa slikama | 60 | manje od 10 ms | manje od 12 ms | Ostavi prostora za GC i input |
| Product feed s animacijama | 60 | manje od 8 ms | manje od 10 ms | Teške animacije povećavaju rizik |
| Tipkanje u pretrazi s live prijedlozima | 60 | manje od 6 ms | manje od 10 ms | UI mora ostati responzivan |
| Pomicanje (pan) na mapi | 60 | manje od 8 ms | manje od 8 ms | Raster često dominira |
| Otvaranje i zatvaranje modala | 60 | manje od 8 ms | manje od 8 ms | Ovdje se jank jako primijeti |
Ako ciljaš 120 Hz uređaje, prepolovi ove budžete. U praksi ciljamo na headroom kako bi aplikacija ostala stabilna kad OS raspoređuje pozadinski posao.
🎯 Ključna poruka: Ako ne možeš jasno izreći budžet za interakciju koju optimiziraš, vjerojatno ćeš trošiti vrijeme na promjene malog učinka.
# Preduvjeti#
| Zahtjev | Verzija | Bilješke |
|---|---|---|
| Flutter | 3.19+ | Preporučen stable |
| Dart | Bundled | Uskladi s Flutter SDK-om |
| Flutter DevTools | Najnoviji | Koristi DevTools koji dolazi uz Flutter |
| Uređaj | Fizički Android i iOS | Uređaji srednje klase otkrivaju realna uska grla |
| Build modovi | Profile i Release | Debug nije dobar za timing |
Pomoći će i čista baseline arhitektura. Ako ti je UI čvrsto vezan uz dohvat podataka i poslovnu logiku, kontrola rebuildova postaje teža. Ako trebaš pomoć oko strukture, vidi Arhitektura Flutter aplikacije: Clean Architecture vs Feature-first i naše smjernice za skalabilne state patternse u Flutter upravljanje stanjem u 2026..
# Ponovljiv workflow: dijagnosticiraj, dokaži, popravi, provjeri#
Ovo je workflow koji koristimo kako bismo izbjegli “cargo cult” optimizacije.
Korak 1: Reproduciraj i snimi minimalni scenarij#
Odaberi jednu interakciju koja trzucka: brzi scroll, otvaranje modala, prebacivanje tabova, tipkanje u pretrazi, širenje stavke liste.
Definiraj reproducibilan skript:
- 1Otvori ekran
- 2Izvedi interakciju 10 do 15 sekundi
- 3Stani
- 4Ponovi dvaput da potvrdiš konzistentnost
Koristi stvarne uređaje. Emulatore često “sakriju” GPU i termalno ponašanje, a iOS simulator nije reprezentativan.
Korak 2: Uključi pravu observability#
Uključi ove alate prvo, prije promjena koda.
- 1Flutter performance overlay
- 2DevTools Performance timeline
- 3DevTools CPU profiler kad treba
- 4Rebuild tracking kad sumnjaš na widget churn
// main.dart
import 'package:flutter/material.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
// Enable during profiling only.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
showPerformanceOverlay: false, // set true during local profiling
home: const HomeScreen(),
);
}
}💡 Savjet: Drži poseban “profiling flavor” s uključenim logiranjem i overlayima. Nemoj ga miješati u produkcijske buildove i nemoj donositi zaključke o performansama na temelju debug-only flagova.
Korak 3: Snimi trace u DevTools Performance#
Pokreni u profile modu:
flutter run --profileOtvori DevTools, idi na Performance, klikni Record, izvedi interakciju, zatim zaustavi.
Sada klasificiraj jank:
- UI jank: UI thread promašuje rokove za frameove jer gradi layoute, vrti težak Dart kod ili uzrokuje čest garbage collection.
- Raster jank: GPU thread ne stiže zbog skupih shadera, clippanja, blura, velikih slika ili previše layera.
Korak 4: Pronađi hotspot i njegovu kategoriju#
Koristi ove signale:
- Flutter Frames graf: pokazuje koji frameovi su promašeni.
- Timeline eventi: traži dugačke “Build”, “Layout”, “Paint”, “Rasterize”.
- CPU profiler: provjeri Dart hot pathove kad je UI thread spor.
- Rebuild stats: pronađi widgete koji se rebuildaju prečesto.
Cilj je odgovoriti na jedno pitanje: koji je najskuplji posao tijekom promašenih frameova i zašto se događa?
Korak 5: Primijeni jedan fix odjednom, zatim ponovno izmjeri#
Napravi jednu promjenu, ponovno pokreni isti skript, snimi novi trace i usporedi.
Ako se traceovi ne poboljšaju, vrati promjenu. Performance rad je iterativan i vođen dokazima.
# Dijagnosticiranje janka u DevTools: na što gledati#
Tumačenje performance overlaya#
Overlay prikazuje dva grafa:
- Gornji graf: posao na UI threadu
- Donji graf: posao na raster threadu
Ako gornji graf skače, sumnjaj na rebuildove, layout ili sinkroni compute na glavnom isolateu. Ako donji graf skače, sumnjaj na složenost renderiranja i slike.
Čitanje timeline tracea kao kontrolne liste#
U snimljenom timelineu:
- 1Pronađi janky frame (preko budžeta).
- 2Zoomiraj.
- 3Provjeri koja faza dominira:
- Build i Layout znači widget churn ili složene layoute.
- Paint znači teško custom paintanje ili efekte.
- Raster znači shader efekte, clipove, skaliranje slika ili previše layera.
- 4Identificiraj ponavljajuće obrasce kroz više janky frameova.
Kada koristiti CPU profiler#
Ako je UI thread spor, ali timeline ne pokazuje jasno zašto, pokreni CPU profiler i ponovi interakciju. Traži:
- Teško JSON dekodiranje
- Skupi string operacije
- Sortiranje velikih lista
- Diffanje i mapiranje velikih kolekcija pri svakom frameu
- Sinkroni file IO ili velika čitanja preferenci
# Kategorija popravaka 1: Smanjenje rebuildova (najveći ROI)#
Pretjerani rebuildovi su jedan od najčešćih uzroka janka u Flutter aplikacijama jer povećavaju i UI posao i posljedični layout i paint.
Obrasci simptoma#
- Scroll liste pokreće rebuildove nepovezanih widgeta poput app bara i footera.
- Tipkanje u search boxu uzrokuje rebuild cijele stranice.
- Promjena jedne stavke u listi rebuilda sve list tileove.
Fix 1: Smanji opseg rebuilda razbijanjem widgeta#
Ako je sve u jednom velikom build metodu, Flutter ne može izolirati što se treba rebuildati.
// Bad: big build method reacts to small state changes.
@override
Widget build(BuildContext context) {
final state = context.watch<HomeState>();
return Column(
children: [
Header(user: state.user),
SearchBar(query: state.query),
Expanded(child: ResultsList(items: state.items)),
Footer(version: state.version),
],
);
}Razbij i selektiraj samo ono što svaki sub-widget treba.
// Better: smaller rebuild surfaces via selectors.
class HeaderSection extends StatelessWidget {
const HeaderSection({super.key});
@override
Widget build(BuildContext context) {
final userName = context.select<HomeState, String>((s) => s.user.name);
return Header(userName: userName);
}
}Ovaj pristup radi s više state rješenja, ali princip je isti: pretplati se na najmanji potrebni “slice”.
Fix 2: Preferiraj const i stabilna widget podstabla#
Const widgeti smanjuju trošak rebuilda i object churn. Stabilna podstabla mogu izbjeći i relayout.
return const Padding(
padding: EdgeInsets.all(16),
child: Text('Settings'),
);Također izbjegavaj stvaranje novih objekata u buildu kad se ne moraju mijenjati, posebno TextStyle, BorderRadius, EdgeInsets i Duration.
Fix 3: Ne rebuildaj liste kada se mijenja jedna stavka#
Kod lista ažuriraj stavku, ne listu. Uobičajene tehnike:
- Koristi keyed stavke liste kako bi Flutter sačuvao state elemenata.
- Koristi granularni state po retku kad je moguće.
- Izbjegavaj
setStatena razini stranice za promjene po retku.
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return KeyedSubtree(
key: ValueKey(item.id),
child: ProductTile(item: item),
);
},
);Fix 4: Cacheiraj izvedene podatke, ne računaj ponovno pri svakom frameu#
Ako filtrirane liste, grupirane sekcije ili formatirane stringove računaš u build, plaćaš taj trošak pri svakom rebuildu.
Premjesti to u:
- Memoizirane selectore
- View-model computed fieldove
- Jednokratni pre-processing korak kad se podaci promijene
Ovdje arhitektura radi razliku. Predvidljiv state layer olakšava da izračunaš jednom i ponovno koristiš. Ako ti je state layer kaotičan, vrati se na Flutter upravljanje stanjem u 2026..
⚠️ Upozorenje: Nemoj “optimizirati” dodavanjem globalnih cacheova posvuda. Većina regresija performansi dolazi od neograničenog cacheiranja, zastarjelih podataka i kompleksnosti koja onemogućuje buduće popravke.
# Kategorija popravaka 2: Renderiranje i layout (kada Raster ili Paint skaču)#
Ako raster thread skače, optimizacija rebuildova sama po sebi neće pomoći. Trebaš smanjiti trošak paintanja.
Česti uzroci visokog render troška#
| Uzrok | Zašto je skupo | Tipičan fix |
|---|---|---|
| Backdrop blur | Traži offscreen renderiranje | Ukloni blur ili ograniči područje blura |
| Pretjerano clipping | Sprječava određene GPU optimizacije | Clipaj samo gdje je nužno |
| Velike sjene | Dodatni blur passovi | Lakše sjene, manji spread |
| Ugniježđeni opacity | Forsira offscreen layere | Izbjegavaj opacity na velikim podstablima |
| Overdraw | Paintanje piksela više puta | Pojednostavi pozadine i layere |
Fix 1: Koristi RepaintBoundary tamo gdje stvarno pomaže#
RepaintBoundary izolira repaint regije. Koristi ga kad mali animirani dio uzrokuje repaint velike statične površine.
Dobri kandidati:
- Animirani brojači unutar statične kartice
- Mali indikatori napretka u velikoj stavci liste
- Video thumbnaili s overlay animacijama
Loši kandidati:
- Omotavanje svega, što povećava broj layera i potrošnju memorije
RepaintBoundary(
child: AnimatedBuilder(
animation: animation,
builder: (context, _) => Transform.scale(
scale: animation.value,
child: const Icon(Icons.favorite),
),
),
);Fix 2: Izbjegni “layout thrash” zbog intrinsic mjerenja#
Widgeti koji se oslanjaju na intrinsic sizing mogu uzrokovati dodatne layout passove. Ako u traceovima vidiš ponavljani layout, pregledaj upotrebu intrinsic patterna i zamijeni ih eksplicitnim constraintovima.
Preferiraj SizedBox, ConstrainedBox i jasno definirane layoute.
Fix 3: Drži scroll hijerarhije jednostavnima#
Ugniježđeni scrollables i složeni sliveri mogu biti ispravni, ali ih je lako učiniti skupima.
Praktične smjernice:
- Preferiraj jedan primarni scrollable po ekranu.
- Koristi
ListView.builderili slivere s lazy buildingom. - Izbjegavaj
shrinkWrap: trueu velikim listama osim ako moraš, jer može forsirati kompletan layout.
# Kategorija popravaka 3: Slike (tihi ubojica performansi)#
Slike mogu uzrokovati i UI i raster jank zbog dekodiranja, resizeanja i overdrawa.
Budžeti za slike koji sprječavaju većinu problema#
| Scenarij | Preporučena max display veličina | Preporučena veličina datoteke | Bilješke |
|---|---|---|---|
| Thumbnail u listi | 64 do 120 logical pixela | 10 do 30 KB | Koristi WebP ili AVIF kad je moguće |
| Hero slika na kartici | 300 do 500 logical pixela širine | 50 do 150 KB | Cacheiraj i prefetchaj |
| Full-screen slika | U skladu sa širinom uređaja | 150 do 400 KB | Progressive loading pomaže |
Ako dekodiraš 4000 x 3000 JPEG za thumbnail 100 x 100, platit ćeš to u memoriji i vremenu.
Fix 1: Dekodiraj na ciljanu veličinu#
Koristi cacheWidth i cacheHeight kako bi Flutter dekodirao približno na display veličinu.
Image.network(
url,
width: 96,
height: 96,
fit: BoxFit.cover,
cacheWidth: 192, // roughly 2x for high density screens
cacheHeight: 192,
);Fix 2: Precacheaj strateški za scroll#
Ako se lista scrolla u slike koje se istovremeno dekodiraju, dobiješ trzanje. Precacheaj sljedećih nekoliko slika kada imaš idle vrijeme.
@override
void didChangeDependencies() {
super.didChangeDependencies();
for (final url in urls.take(5)) {
precacheImage(NetworkImage(url), context);
}
}Fix 3: Smanji overdraw u karticama s puno slika#
Izbjegavaj slaganje više poluprozirnih gradijenata, blurova i sjena preko velikih slika. Ako dizajn to zahtijeva, ograniči područje efekta.
# Kategorija popravaka 4: Isolates i pozadinski posao (kada CPU skače)#
Ako timeline pokazuje dugačke Dart zadatke, vjerojatno blokiraš glavni isolate.
Koristi isolates za:
- Parsiranje velikih JSON payloadova
- Transformaciju podataka na velikim listama
- Enkripciju i hashing
- Generiranje PDF-ova ili thumbnaila
Praktično pravilo#
Ako sinkroni zadatak traje više od oko 4 do 8 milisekundi na ciljnom uređaju srednje klase, tretiraj ga kao kandidata za prebacivanje u isolate.
Primjer: Parsiranje JSON-a u isolateu pomoću compute#
import 'dart:convert';
import 'package:flutter/foundation.dart';
List<Map<String, dynamic>> parseItems(String body) {
final decoded = jsonDecode(body) as List<dynamic>;
return decoded.cast<Map<String, dynamic>>();
}
Future<List<Map<String, dynamic>>> parseItemsAsync(String body) {
return compute(parseItems, body);
}Ovo ostavlja UI isolate slobodnim za renderiranje. Upari s cacheiranjem da ne parsiraš isti payload više puta.
Ne prebacuj sve s glavnog isolatea#
Isolates dodaju overhead i kompleksnost. Također traže message passing i kopiranje, što može biti skupo za velike objekte.
Koristi isolates za težak posao, ne kao default.
# Prije i poslije: kontrolna lista koju možeš ponoviti za svaku značajku#
Ovo je razlika između “optimizirali smo jednom” i “održavamo aplikaciju brzom”.
Kontrolna lista prije profiliranja#
| Provjera | Cilj | Kako provjeriti |
|---|---|---|
| Profile mode na uređaju | Da | flutter run --profile |
| Definiran repro skript | Da | Isti koraci pri svakom runu |
| Definirani budžeti | Da | Koristi tablicu u ovom postu |
| Minimalno logiranja | Da | Izbjegni spam u hot pathovima |
| Realistične slike | Da | Testiraj s produkcijski sličnim medijima |
Kontrolna lista nakon popravaka#
| Provjera | Cilj | Kako provjeriti |
|---|---|---|
| Manje promašenih frameova | Da | DevTools Performance frames graf |
| Headroom za UI i raster | Najmanje 20 posto | Overlay grafovi ostaju ispod budžeta |
| Manji broj rebuildova | Da | Rebuild stats i widget inspector |
| Nema novih memory spikeova | Da | DevTools Memory i GC frekvencija |
| Potvrđeno u releaseu | Da | flutter run --release na uređaju |
💡 Savjet: Drži screenshotove “prije” i “poslije” traceova u opisu PR-a. Time performance rad postaje reviewable i sprječava da se regresije kasnije ponovno uvedu.
# Kako performance rad utječe na trošak i isporuku#
Optimizacija performansi nije besplatna, ali je predvidljiva kada budžete uvedeš rano. Popravljanje performansi nakon što su featurei već isporučeni obično košta više jer UI, state i data slojevi postaju teži za refaktoriranje.
Ako planiraš novu aplikaciju, planiranje vremena za profiliranje i performance gateove rano može smanjiti rework i rizik store reviewa. Za način razmišljanja o troškovima vidi Trošak razvoja Flutter aplikacije.
# Ključne poruke#
- Profiliraj u profile modu na stvarnim uređajima, snimi DevTools traceove i klasificiraj jank kao UI-thread ili raster-thread prije nego diraš kod.
- Najbrže smanjuješ jank tako da smanjiš opseg rebuilda: razbijanje widgeta, selektori, stabilna podstabla i const konstruktori.
- Kad raster skače, optimiziraj složenost renderiranja: ograniči blur, clipping, opacity layere i koristi
RepaintBoundarysamo za ciljanu izolaciju. - Slike tretiraj kao pipeline: dekodiraj na ciljanu veličinu uz
cacheWidthicacheHeight, prefetchaj strateški i izbjegavaj skupe overlaye. - Prebaci teške CPU zadatke u isolates kada sinkroni posao prelazi oko 4 do 8 milisekundi na ciljnom uređaju, pa potvrdi s “prije i poslije” traceovima.
# Zaključak#
Optimizacija performansi u Flutteru je najlakša kada je workflow, a ne jednokratni sprint: postavi budžete, reproduciraj jank, snimi traceove, primijeni jedan fix i provjeri u release buildovima.
Ako želiš da auditiramo najsporije ekrane tvoje aplikacije, definiramo performance budžete za tvoj proizvod i implementiramo popravke bez destabilizacije arhitekture, javi se Samiodi. Gradimo Flutter aplikacije koje ostaju fluidne kako rastu, a promjene potkrepljujemo mjerljivim “prije i poslije” traceovima.
FAQ
Više iz kategorije Mobilni razvoj
Sve →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.
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.
Koliko košta MVP mobilne aplikacije? Realistična razrada (2026)
Objašnjenje troška MVP-a mobilne aplikacije uz realne razrade po funkcionalnostima, raspona prema tipu aplikacije i usporedbu Fluttera i nativnog razvoja kako biste realno isplanirali budžet.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
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.
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.
Koliko košta razvoj Flutter aplikacije u 2026.? (Realni budžeti prema složenosti aplikacije)
Saznajte realan trošak razvoja Flutter aplikacije u 2026. kroz budžete po složenosti, detaljnu tablicu troškova i usporedbu native vs Flutter za iOS i Android.