Flutter-cachingstrategier för lokal cache, föråldrade data och uppdateringsregler: vad man sparar, när man invalidierar och hur man håller skärmar konsekventa.

Caching i en mobilapp betyder att du håller en kopia av data nära (i minne eller på enheten) så nästa skärm kan rendera direkt istället för att vänta på nätverket. Den datan kan vara en lista med objekt, en användarprofil eller sökresultat.
Det svåra är att cache:ad data ofta är lite felaktig. Användare märker snabbt: ett pris som inte uppdateras, en notifieringsräknare som känns fast, eller en detaljskärm som visar gammal info precis efter att de ändrat något. Det som gör detta svårt att debugga är tajmingen. Samma endpoint kan se rätt ut efter pull-to-refresh, men fel efter tillbakavigering, app-resume eller kontobyte.
Det finns en verklig avvägning. Om du alltid hämtar färsk data känns skärmarna långsamma och ryckiga, och du slösar batteri och data. Om du cache:ar aggressivt känns appen snabb, men folk slutar lita på vad de ser.
Ett enkelt mål hjälper: gör färskheten förutsägbar. Bestäm vad varje skärm får visa (färskt, lätt föråldrat eller offline), hur länge data kan leva innan du uppdaterar det, och vilka händelser som måste invalidiera den.
Föreställ dig ett vanligt flöde: en användare öppnar en order, går sedan tillbaka till orderlistan. Om listan kommer från cache kan den fortfarande visa gammal status. Om du uppdaterar varje gång kan listan flimra och kännas långsam. Klara regler som “visa cache:at omedelbart, uppdatera i bakgrunden och uppdatera båda skärmarna när svaret kommer” gör upplevelsen konsekvent över navigering.
En cache är inte bara "sparad data". Det är en sparad kopia plus en regel för när den kopian fortfarande är giltig. Om du sparar payloaden men hoppar över regeln får du två versioner av verkligheten: en skärm visar ny info, en annan visar gårdagens.
En praktisk modell är att placera varje cache:ad post i ett av tre tillstånd:
Denna inramning håller ditt UI förutsägbart eftersom det kan reagera likadant varje gång det ser ett givet tillstånd.
Färskhetsregler bör baseras på signaler du kan förklara för en kollega. Vanliga val är tidsbaserad utgång (t.ex. 5 minuter), versionsändring (schema- eller appversion), en användaråtgärd (pull to refresh, submit, delete) eller en serverhint (ETag, last-updated timestamp eller ett explicit ”cache invalid” svar).
Exempel: en profilsida laddar cache:ad användardata omedelbart. Om den är föråldrad-men-användbar visar den det cachade namnet och avataren, och uppdaterar tyst. Om användaren precis redigerade sin profil är det en "måste uppdateras"-händelse. Appen bör uppdatera cachen omedelbart så varje skärm förblir konsekvent.
Bestäm vem som äger dessa regler. I de flesta appar är bästa standard: datalagret äger färskhet och invalidation, UI:n reagerar bara (visa cache:at, visa laddning, visa fel) och backend ger hints när den kan. Detta förhindrar att varje skärm uppfinner sina egna regler.
Bra caching börjar med en fråga: om den här datan är lite gammal, kommer det skada användaren? Om svaret är "troligen okej" är det ofta en bra kandidat för lokal caching.
Data som läses mycket och förändras långsamt är vanligtvis värd att cache:a: flöden och listor som folk skrollar ofta, kataloginnehåll (produkter, artiklar, mallar) och referensdata som kategorier eller länder. Inställningar och preferenser hör också hit, tillsammans med grundläggande profilinfo som namn och avatar-URL.
Riskfyllda områden är allt som rör pengar eller är tidkritiskt. Saldo, betalningsstatus, lagertillgänglighet, bokningsslotar, leverans-ETA och "senast online" kan orsaka verkliga problem om de är föråldrade. Du kan fortfarande cache:a dem för snabbhet, men behandla cachen som en temporär platshållare och tvinga uppdatering i beslutspunkter (t.ex. precis före orderbekräftelse).
Härledd UI-state är en egen kategori. Att spara vald flik, filter, sökfråga, sortering eller scrollposition kan göra navigering smidigare. Det kan också förvirra när gamla val återkommer oväntat. En enkel regel fungerar bra: håll UI-state i minnet medan användaren stannar i det flödet, men återställ när de medvetet "börjar om" (som att återvända till startsidan).
Undvik att cache:a data som skapar säkerhets- eller integritetsrisk: hemligheter (lösenord, API-nycklar), engångstoken (OTP-koder, lösenordsåterställning) och känsliga personuppgifter om du inte verkligen behöver offline-åtkomst. Cach aldrig fullständiga kortuppgifter eller något som ökar bedrägeririsk.
I en shoppingapp är det en stor fördel att cache:a produktlistan. Kassasidan däremot bör alltid uppdatera totalsummor och tillgänglighet precis före köp.
De flesta Flutter-appar behöver någon form av lokal cache så skärmar laddar snabbt och inte blinkar tomma medan nätverket vaknar. Nyckelbeslutet är var den cache:ade datan bor, eftersom varje lager har olika hastighet, storleksbegränsningar och uppstädningsbeteende.
En minnescache är snabbast. Den är utmärkt för data du just hämtat och kommer återanvända medan appen är öppen, som aktuell användarprofil, senaste sökresultat eller en produkt användaren nyss tittat på. Nackdelen är enkel: den försvinner när appen dödas, så hjälper inte vid cold starts eller offline-användning.
Diskbaserad key-value-lagring passar små saker du vill bevara över omstarter. Tänk preferenser och enkla blobbar: feature-flaggor, "senast vald flik" och små JSON-svar som sällan ändras. Håll det medvetet litet. När du börjar lagra stora listor i key-value blir uppdateringar röriga och bloat lätt.
En lokal databas är bäst när din data är större, strukturerad eller behöver offline-beteende. Den hjälper också när du behöver frågor ("alla olästa meddelanden", "varor i kundvagn", "order från förra månaden") istället för att ladda en jättestor blob och filtrera i minnet.
För att hålla caching förutsägbar, välj en primär lagringsplats per datatyp och undvik att hålla samma dataset på tre ställen.
Snabb tumregel:
Planera också för storlek. Bestäm vad "för stort" betyder, hur länge du behåller poster och hur du städar upp. Till exempel: kapa cache:ade sökresultat till de senaste 20 frågorna och ta bort poster äldre än 30 dagar så cachen inte växer tyst för evigt.
Uppdateringsregler bör vara så enkla att du kan förklara dem i en mening per skärm. Där tjänar rimlig caching in: användare får snabba skärmar och appen förblir pålitlig.
Den enklaste regeln är TTL (time to live). Spara data med en tidsstämpel och behandla den som färsk i till exempel 5 minuter. Därefter blir den föråldrad. TTL fungerar bra för "trevligt att ha"-data som flöden, kategorier eller rekommendationer.
En nyttig förfining är att dela TTL i soft TTL och hard TTL.
Med soft TTL visar du cache:ad data omedelbart, uppdaterar i bakgrunden och uppdaterar UI om det ändrats. Med hard TTL slutar du visa gammal data när den går ut. Du blockerar med en loader eller visar ett "offline/försök igen"-läge. Hard TTL passar där det är värre att ha fel än att vara långsam, som saldon, orderstatus eller behörigheter.
Om din backend stödjer det, föredra "uppdatera bara om det ändrats" med ETag, updatedAt eller versionsfält. Appen kan fråga "har det ändrats?" och hoppa över att ladda ner full payload när inget är nytt.
Ett användarvänligt standardbeteende för många skärmar är stale-while-revalidate: visa nu, uppdatera tyst och rendera om bara om resultatet skiljer sig. Det ger snabbhet utan slumpmässig flicker.
Per-skärm färskhet ser ofta ut så här:
Välj regler baserat på kostnaden av att ha fel, inte bara kostnaden att hämta.
Cache-invalidering börjar med en fråga: vilken händelse gör cache:ad data mindre pålitlig än kostnaden att hämta om den? Om du väljer ett litet set triggers och håller dig till dem blir beteendet förutsägbart och UI:t känns stabilt.
Triggers som spelar störst roll i riktiga appar:
Exempel: en användare uppdaterar sin profilbild och går tillbaka. Om du bara förlitar dig på tidsbaserad refresh kan föregående skärm visa den gamla bilden tills nästa hämtning. Behandla istället edit som trigger: uppdatera det cache:ade profilobjektet direkt och markera det som färskt med en ny tidsstämpel.
Håll invalidationsregler små och explicita. Om du inte kan peka på exakt vilken händelse som invalidierar en cache-post kommer du uppdatera för ofta (långsam, ryckig UI) eller inte tillräckligt (föråldrade skärmar).
Börja med att lista dina viktiga skärmar och vilken data varje behöver. Tänk inte i endpoints. Tänk i användar-synliga objekt: profil, kundvagn, orderlista, katalogobjekt, oläst räknare.
Välj sedan en källa till sanning per datatyp. I Flutter är detta oftast ett repository som döljer var data kommer ifrån (minne, disk, nätverk). Skärmar ska inte bestämma när de ska slå mot nätverket. De bör be repository:t om data och reagera på det returnerade tillståndet.
Ett praktiskt flöde:
Metadata är vad som gör regler verkställbara. Om ownerUserId ändras (logout/login) kan du slänga eller ignorera gamla cache-rader omedelbart istället för att kortvarigt visa föregående användares data.
För UI-beteende, bestäm i förväg vad "föråldrat" betyder. En vanlig regel: visa föråldrad data omedelbart så skärmen inte är blank, starta en refresh i bakgrunden och uppdatera när ny data anländer. Om refresh misslyckas, behåll den föråldrade datan synlig och visa ett litet, tydligt fel.
Lås sedan reglerna med några enkla tester:
Detta är skillnaden mellan "vi har caching" och "vår app beter sig likadant varje gång."
Inget bryter förtroende snabbare än att se ett värde på en listskärm, trycka på detaljer, redigera det och sedan gå tillbaka och se det gamla värdet igen. Konsekvens över navigering kommer från att varje skärm läser från samma källa.
En solid regel är: hämta en gång, spara en gång, rendera många gånger. Skärmar ska inte kalla samma endpoint oberoende och hålla privata kopior. Placera cache:ad data i en delad store (din state management-lager), och låt både lista och detaljskärmar bevaka samma data.
Behåll en enda plats som äger nuvarande värde och färskhet. Skärmar kan be om en refresh, men de ska inte själva hantera timers, retries och parsing.
Praktiska vanor som förhindrar "två verkligheter":
Även med bra regler kommer användare ibland se föråldrad data (offline, långsamt nätverk, bakgrundad app). Gör det tydligt med små, lugna signaler: en "Uppdaterad just nu"-tidsstämpel, en subtil "Uppdaterar…"-indikator eller en "Offline"-badge.
För redigeringar känns optimistiska uppdateringar ofta bäst. Exempel: en användare ändrar ett produktpris på detaljskärmen. Uppdatera den delade storen omedelbart så listan visar det nya priset när de går tillbaka. Om sparandet misslyckas, ångra ändringen och visa ett kort felmeddelande.
De flesta cachingfel är tråkiga: cachen fungerar, men ingen kan förklara när den ska användas, när den löper ut och vem som äger den.
Den första fällan är caching utan metadata. Om du bara sparar payloaden kan du inte säga om den är gammal, vilken appversion som skapade den eller vilken användare den tillhör. Spara åtminstone savedAt, ett enkelt versionsnummer och en userId (eller tenant-nyckel). Den vanan förhindrar många "varför visar den här skärmen fel?"-buggar.
Ett annat vanligt problem är flera cachar för samma data utan ägare. En listskärm håller en minneslista, ett repository skriver till disk och en detaljskärm hämtar igen och sparar någon annanstans. Välj en källa till sanning (ofta repository-lagret) och låt varje skärm läsa genom den.
Kontobyten är en frekvent fälla. Om någon loggar ut eller byter konto, rensa user-scoped tabeller och nycklar. Annars kan du visa föregående användares profilbild eller order ett ögonblick, vilket känns som ett integritetsbrott.
Praktiska åtgärder som täcker problemen ovan:
Exempel: din produktlista laddar direkt från cache, sedan uppdaterar den tyst. Om refresh misslyckas, fortsätt visa cache:ad data men var tydlig med att den kan vara gammal och erbjud Retry. Blockera inte UI:t på refresh när cache:ad data är acceptabel.
Innan release, gör caching från "det verkar okej" till regler du kan testa. Användare ska se data som känns rimlig även efter navigering fram och tillbaka, offline eller vid inloggning med annan användare.
För varje skärm, bestäm hur länge data kan anses färskt. Det kan vara minuter för snabbrörlig data (meddelanden, saldon) eller timmar för långsamt förändrande data (inställningar, produktkategorier). Bekräfta sedan vad som händer när det inte är färskt: bakgrundsrefresh, refresh vid öppning eller manuell pull-to-refresh.
För varje datatyp, bestäm vilka händelser som måste torka eller kringgå cachen. Vanliga triggers inkluderar logout, redigering av objekt, kontobyte och appuppdateringar som ändrar datastrukturen.
Se till att cache-poster lagrar en liten uppsättning metadata bredvid payloaden:
Håll ägarskapet klart: använd ett repository per datatyp (t.ex. ProductsRepository), inte per widget. Widgets ska be om data, inte besluta cache-regler.
Bestäm och testa även offline-beteende. Bekräfta vad skärmar visar från cache, vilka åtgärder som är inaktiva och vilken text du visar ("Visar sparad data", plus en synlig refresh-kontroll). Manuell refresh bör finnas på varje cache:ad skärm och vara lätt att hitta.
Föreställ dig en enkel butik med tre skärmar: produktkatalog (lista), produktdetaljer och en Favoriter-flik. Användare skrollar katalogen, öppnar en produkt och trycker på ett hjärta för att favorisera. Målet är att kännas snabbt även på långsamma nätverk utan förvirrande mismatch.
Cache:a lokalt det som hjälper dig rendera omedelbart: katalogsidor (ID, titel, pris, thumbnail-URL, favorit-flagga), produktdetaljer (beskrivning, specifikationer, tillgänglighet, lastUpdated), bildmetadata (URLs, storlekar, cache-nycklar) och användarens favoriter (en mängd produkt-ID:n, gärna med tidsstämplar).
När användaren öppnar katalogen, visa cache:at resultat direkt och revalidera i bakgrunden. Om färsk data anländer, uppdatera bara det som ändrats och behåll scrollpositionen stabil.
För favorit-toggle, behandla det som en "måste vara konsekvent"-åtgärd. Uppdatera lokal favorites-mängd omedelbart (optimistisk uppdatering), uppdatera sedan cachade produktrader och produktdetaljer för det ID:t. Om nätverkskallet misslyckas, rulla tillbaka och visa ett litet meddelande.
För att hålla navigeringen konsekvent, driva både listbadges och detaljhjärta från samma källa till sanning (din lokala cache eller store), inte från separata skärm-state. Listans hjärta uppdateras så fort du återvänder från detaljer, detaljskärmen reflekterar ändringar gjorda från listan, och Favoriter-räknaren matchar överallt utan att vänta på refetch.
Lägg till enkla refresh-regler: katalogcachen går ut snabbt (minuter), produktdetaljer något längre, och favoriter upphör aldrig men rekonsilieras alltid efter login/logout.
Caching slutar vara mystiskt när ditt team kan peka på en sida med regler och komma överens om vad som ska hända. Målet är inte perfektion — det är förutsägbart beteende som håller sig likt över releaser.
Skriv en liten tabell per skärm och håll den kort nog att granska vid förändringar: skärmens namn och huvudsakliga data, cache-placering och nyckel, färskhetsregel (TTL, event-baserad eller manuell), invalidation-triggers och vad användaren ser under uppdatering.
Lägg till lättviktig logging medan du finjusterar. Logga cache-hits, misses och varför en refresh hände (TTL expiration, användarpull, app-resume, mutation slutförd). När någon rapporterar "den här listan känns fel" gör de loggarna felet spårbart.
Börja med enkla TTLs och förfina utifrån vad användare märker. Ett nyhetsflöde kan acceptera 5–10 minuters föråldring, medan en orderstatus-skärm kanske behöver refresh vid resume och efter checkout-åtgärder.
Om du bygger en Flutter-app snabbt kan det hjälpa att skissa ditt datalager och cache-regler innan du implementerar något. För team som använder Koder.ai (koder.ai) är planning mode en praktisk plats att skriva per-skärms regler först, sedan bygga för att matcha dem.
När du finjusterar uppdateringsbeteende, skydda stabila skärmar medan du experimenterar. Snapshots och rollback kan spara tid när en ny regel oavsiktligt introducerar flicker, tomma tillstånd eller inkonsekventa räkningar över navigering.
Börja med en tydlig regel per skärm: vad som får visas omedelbart (cache:at), när det måste uppdateras, och vad användaren ser under uppdatering. Om du inte kan förklara regeln med en mening kommer appen så småningom kännas inkonsekvent.
Behandla cache:ad data som att den har ett färskhetstillstånd. Om det är färskt — visa det. Om det är gammalt men användbart — visa det nu och uppdatera tyst i bakgrunden. Om det måste uppdateras — hämta innan visning (eller visa laddnings-/offline-läge). Detta håller UI-beteendet konsekvent istället för "ibland uppdateras det, ibland inte."
Cache:a data som läses ofta och kan vara lite gammal utan att skada användaren: feeds, kataloger, referensdata och grundläggande profilinfo. Var försiktig med pengar- eller tidkritiska data som saldon, lagertillgänglighet, ETAs och orderstatus — dessa kan cache:as för snabbhet men tvingas uppdateras precis före beslut eller bekräftelse.
Använd minne för snabb återanvändning under aktuell session, som aktuell profil eller nyligen visade objekt. Använd disk (key-value) för små, enkla objekt som ska överleva omstarter, som preferenser. Använd databas när data är stora, strukturerade, behöver sökfrågor eller ska fungera offline, som meddelanden, ordrar eller ett inventarium.
En enkel TTL är ett bra standardval: betrakta data som färskt under en bestämd tid, sedan uppdatera. För många skärmar ger dock "visa cache:at nu, uppdatera i bakgrunden, och uppdatera bara om det ändrats" en bättre upplevelse eftersom det undviker tomma skärmar och minskar flicker.
Invalidiera på händelser som klart minskar förtroendet för cachen: användarediteringar (create/update/delete), login/logout eller kontobyte, app-resume om data är äldre än din TTL, och uttrycklig användaruppdatering. Håll dessa triggers små och explicita så du inte uppdaterar konstant eller aldrig när det behövs.
Gör att båda skärmarna läser från samma källa till sanningen, inte sina egna privata kopior. När användaren redigerar något på detaljskärmen, uppdatera det delade cache-objektet omedelbart så listan visar det nya värdet vid återgång, synka sedan med servern och ångra bara om sparandet misslyckas.
Spara alltid metadata tillsammans med nyttolasten, särskilt en tidsstämpel och en användaridentifierare. Vid logout eller kontobyte — rensa eller isolera user-scoped cache-poster omedelbart och avbryt pågående förfrågningar kopplade till den gamla användaren så du inte kortvarigt visar föregående användares data.
Visa gärna den gamla datan och visa ett litet, tydligt felmeddelande med möjlighet att försöka igen, istället för att göra skärmen tom. Om skärmen inte säkert kan visa gammal data, använd istället en "måste uppdateras"-regel och visa laddning eller offline-meddelande i stället för att låtsas att den gamla datan är pålitlig.
Lägg cache-regler i ditt datalager (t.ex. repositories) så varje skärm följer samma beteende. Om du bygger snabbt i Koder.ai, skriv per-skärms färskhets- och invalidationsregler i planning mode först och implementera sedan så UI:n bara reagerar på tillstånd istället för att själv uppfinna uppdateringslogik.