· Workflow · 9 min lezen

App Store screenshots genereren met Flutter: integration_test, golden tests en een snellere aanpak (2026)

App Store screenshots genereren met Flutter: integration_test, golden tests en een snellere aanpak (2026)
TL;DR. Flutter heeft geen eigen commando om store-screenshots te genereren. De realistische opties zijn: echte frames vastleggen met integration_test + flutter drive, widget-snapshots renderen met golden tests (uitstekend voor regressie, een compromis voor store-assets), of beide combineren met Fastlane/Codemagic CI. Geen van deze opties componeert de marketingcarrousel — bijschriften, frames, achtergronden, copy in 50 locales. Dat is een aparte stap, en daar past Mokbi: het componeert en lokaliseert de screenshots die je hebt vastgelegd. Vastleggen zelf doet het niet.

Als je met Flutter bouwt en je hebt iets uitgebracht in de App Store of Play Store, ben je al tegen de leemte aangelopen: er is geen flutter screenshots. Apple wil exacte PNG's op afmetingen als 1320 × 2868 (6.9-inch iPhone) en 2064 × 2752 (13-inch iPad); Flutter geeft je een rendering engine en een testhulpomgeving en laat de store-pipeline aan jou over. Dit artikel is de eerlijke kaart van wat er daadwerkelijk werkt in 2026, met code die je kunt plakken, en een duidelijke grens over welk tool welke taak doet. Als je eerst een framework-specifiek overzicht wilt, zie Mokbi voor Flutter-developers.

Eén kader vooraf, omdat het veel verwarring scheelt: een frame vastleggen (de app naar een toestand sturen en een PNG opslaan van wat er op het scherm staat) en een store-screenshot samenstellen (dat frame in een device-mockup, met een koptekst, een achtergrond en vertaalde tekst, geëxporteerd op elk vereist formaat) zijn twee verschillende problemen. Het meeste Flutter-tooling hieronder lost het eerste op. Geen ervan lost het tweede op.

Optie 1: integration_test + flutter drive (echte vastgelegde frames)

Dit is het dichtst bij een officiële screenshot-route die Flutter heeft. Het integration_test-pakket start je volledige app op een echt apparaat of emulator, stuurt het met dezelfde finder/tester-API als widget-tests, en biedt binding.takeScreenshot(). Het addertje is dat takeScreenshot de bytes teruggeeft aan het testproces — het schrijft geen bestand — dus je hebt de uitgebreide driver nodig om die bytes op de host te ontvangen en op te slaan.

Eerst de test. Let op de Android-specifieke stap: convertFlutterSurfaceToImage() moet worden aangeroepen (en een frame worden gepompt) vóór vastleggen, omdat Android Flutter rendert in een SurfaceView die de framebuffer niet direct kan uitlezen.

// integration_test/screenshot_test.dart
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;

void main() {
  final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('home + detail screenshots', (tester) async {
    app.main();
    await tester.pumpAndSettle();

    // Android needs the surface converted to an image before capture.
    if (Platform.isAndroid) {
      await binding.convertFlutterSurfaceToImage();
      await tester.pumpAndSettle();
    }

    await binding.takeScreenshot('01_home');

    await tester.tap(find.byKey(const Key('open-detail')));
    await tester.pumpAndSettle();
    await binding.takeScreenshot('02_detail');
  });
}

Dan de driver, die op de hostmachine draait en de PNG-bytes opslaat die de test terugstuurde. Dit bestand staat in test_driver/ en gebruikt integration_test_driver_extended.dart (de gewone integration_test_driver.dart heeft geen onScreenshot-hook):

// test_driver/integration_test.dart
import 'dart:io';
import 'package:integration_test/integration_test_driver_extended.dart';

Future<void> main() async {
  await integrationDriver(
    onScreenshot: (String name, List<int> bytes,
        [Map<String, Object?>? args]) async {
      final file = await File('screenshots/$name.png').create(recursive: true);
      file.writeAsBytesSync(bytes);
      return true; // false fails the test (e.g. on a diff mismatch)
    },
  );
}

Belangrijk: je moet dit uitvoeren met flutter drive, niet met flutter test — alleen het driver-pad roept onScreenshot aan:

flutter drive \
  --driver=test_driver/integration_test.dart \
  --target=integration_test/screenshot_test.dart \
  -d emulator-5554

Wat je krijgt en wat niet:

  • Echte app-pixels. Dit zijn screenshots van je daadwerkelijk draaiende widgets, geen herrendering — lettertypen, thema, dynamische data, allemaal native.
  • Deterministische toestanden. Je stuurt de app naar precies de schermen die je wilt en legt ze op aanvraag vast.
  • Apparaatklasse = het apparaat waarop je draait. De uitvoerresolutie is die van de emulator of het apparaat. Om Apple's exacte 1320 × 2868 te halen, draai je op een 6.9-inch iOS-simulator; voor de 13-inch iPad-set draai je die simulator. Er is geen magische "schaal naar App Store"-vlag.
  • Locale is je eigen verantwoordelijkheid. Je kunt een locale meegeven aan je app bij het opstarten en opnieuw draaien per taal, maar dan leg je de app-UI in die locale vast — niet de marketingtekst voor je koptekst.
  • Onderhoudskosten zijn reëel. De finders (find.byKey, tikacties) breken wanneer de UI wijzigt, net als bij elke UI-test.

Optie 2: golden tests — en waarom ze geen store-assets zijn

Flutter's golden tests (matchesGoldenFile, of het hogere testGoldens van pakketten zoals golden_toolkit) renderen een widget-tree naar een afbeelding en vergelijken die byte voor byte met een vastgelegde referentie. Ze zijn uitstekend, en ze zijn gebouwd voor een ander doel dan store-screenshots: pixelregressie. Het punt is om de dag te ontdekken waarop een opvulwijziging of lettertypevervanging stilletjes je UI verschuift. De golden is het contract; een verschil is een mislukte test.

Twee redenen om geen ruwe golden tests als store-pipeline te gebruiken:

  • Golden tests renderen widgets in een headless testomgeving, niet op het echte apparaatoppervlak. Platformweergaven, sommige shaders en alles wat afhankelijk is van de daadwerkelijke GPU-compositor kunnen anders renderen (of helemaal niet) dan wat een gebruiker ziet. Voor een regressiecontract is dat prima — je vergelijkt met jezelf. Voor een store-asset wil je het echte frame, wat Optie 1 geeft.
  • Pixel-perfecte vergelijking werkt tegen je. Standaard golden tests verwachten een exacte overeenkomst, waardoor antialiasing- en font-hinting-verschillen tussen machines onstabiele fouten veroorzaken. Dat is de juiste strengheid voor regressie en de verkeerde voor "produceer een mooie marketingafbeelding".

Dat gezegd hebbende, is er een legitiem middenpad. Pakketten zoals golden_screenshot hergebruiken de golden-machinerie bewust voor store-output: ze gebruiken testGoldens, wikkelen je scherm in echte device frames (telefoon/tablet/desktop), schakelen een vage vergelijker in die een kleine afwijking (~0,1%, configureerbaar) tolereert in plaats van pixel-perfectie te eisen, en schrijven bestanden naar een Fastlane-stijl mappenstructuur (metadata/<locale>/images/...). Als je schermen puur Flutter zijn (geen platformweergaven) en je wilt frames-rondom-een-render zonder een emulator te starten, is dit een redelijke en snelle route. Wees je er alleen bewust van dat je widgets rendert, niet de live app vastlegt, en dat de "marketing" die je krijgt een device frame is — geen koppen, achtergronden of een multi-paneel carrousel.

Optie 3: Fastlane / Codemagic voor Flutter

Fastlane is iOS/Android-automatisering, niet Flutter-specifiek, maar het past op twee manieren. Fastlanes eigen opnametools (snapshot voor iOS via XCUITest, screengrab voor Android via Espresso/UI Automator) verwachten native UI-tests, die Flutter-apps doorgaans niet hebben — de meeste Flutter-teams gebruiken snapshot dus niet om vast te leggen. Waar Fastlane zijn plek verdient is bij levering: deliver (iOS) en supply (Android) uploaden je al vastgelegde PNG's en metadata naar App Store Connect / Play Console, en ze lezen een Fastlane-stijl map — precies de mappenstructuur die golden_screenshot schrijft en die je ook je Optie 1-driver kunt laten schrijven.

Codemagic is de Flutter-native CI waar de meeste teams hier voor kiezen. Het realistische CI-recept is: je integration_test screenshot drive uitvoeren op de macOS/Android runners voor de apparaatklassen die je nodig hebt, de PNG's verzamelen als build-artefacten, en vervolgens deliver/supply aanroepen om ze te uploaden. Dit is echt nuttig wanneer screenshots bij elke release verouderen en je vaak uitbrengt. Voor de bredere afweging "automatiseren vs. no-code" — inclusief wanneer deze opzet overdreven is — zie Fastlane snapshot vs no-code App Store screenshots.

Optie 4: handmatige opname (nog steeds volledig geldig)

Voor veel indie Flutter-apps is het eerlijke antwoord: leg de screenshots gewoon handmatig vast per release. Draai de app in de iOS Simulator met de juiste apparaatklasse (6.9-inch iPhone, 13-inch iPad), navigeer naar elk scherm en gebruik het screenshot-commando van de simulator — het legt vast op de exacte apparaatresolutie, precies wat App Store Connect wil. Op Android doet de camera-knop van de emulator hetzelfde. Totale tijd voor een vijf-schermen-app: misschien tien minuten.

Handmatige opname heeft geen onderhoudskosten, geen onstabiele finders en geen CI om in de gaten te houden. De enige zwakte is dat niets automatisch wordt opnieuw gegenereerd — als je een UI-wijziging uitbrengt, leg je opnieuw vast. Voor een team dat een paar keer per jaar uitbrengt, is die afweging overduidelijk de moeite waard. De automatisering in Opties 1–3 loont precies wanneer handmatig opnieuw vastleggen niet meer een klusje van tien minuten is, omdat je wekelijks uitbrengt in veel locales.

Het deel dat geen van de bovenstaande aanpakken doet: compositie en lokalisatie naar 50 talen

Dit is de bondige, eerlijke alinea. Elke optie hierboven produceert kale frames — je app-UI op apparaatresolutie. App Store- en Play Store-carrousels die converteren zijn geen kale frames: het is het frame in een device-mockup, op een achtergrond, onder een korte koptekst, vaak als multi-paneel reeks, en herhaald voor alle locales die je ondersteunt. Die compositiestap is waarvoor Mokbi bedoeld is. Je brengt de frames die je hebt vastgelegd (via integration_test, golden_screenshot, de simulator, waar dan ook), zet ze in een browser-editor, voegt device frames, bijschriften en achtergronden toe, vertaalt de bijschriften naar maximaal 50 App Store-talen met ongeveer één klik, en exporteert in bulk op elk vereist formaat. Ontwerpen met een voorvertoning met watermerk is gratis; onbeperkt exporteren en publiceren zit bij een abonnement — Solo voor €29.99/mo (1 app) of Studio voor €49.99/mo (tot 5 apps), geen eenmalige aankoop. Om de grens duidelijk te stellen: Mokbi legt geen bronscreenshots vast — het draait je app niet en stuurt je widgets niet aan. Vastleggen blijft bij het Flutter-tooling hierboven; Mokbi componeert en lokaliseert wat dat tooling oplevert.

Een realistische gecombineerde Flutter-workflow

  1. Leg de bronframes vast. Schrijf een integration_test screenshot drive (Optie 1) voor de schermen die je in de carrousel wilt, of — als je zelden uitbrengt — leg ze gewoon handmatig vast vanuit de simulator. Draai op een 6.9-inch iPhone-klasse apparaat en een 13-inch iPad-klasse apparaat zodat de resoluties kloppen.
  2. (Optioneel) Koppel aan CI. Als veroudering een echt probleem is, draai de drive op Codemagic en verzamel de PNG's als artefacten (Optie 3). Zo niet, sla dit volledig over.
  3. Stel de marketingcarrousel samen. Zet de frames in Mokbi, voeg frames, bijschriften en achtergronden toe en bouw de multi-paneel reeks. Dit is de stap die Flutter-tooling niet kan doen.
  4. Lokaliseer en exporteer. Vertaal bijschriften met één klik naar je doellocales in de App Store, exporteer in bulk op elk vereist formaat, en zet de output eventueel in een Fastlane-map zodat deliver/supply het uploadt.
  5. Bij de volgende UI-wijziging: leg opnieuw vast en open opnieuw. Voer de drive opnieuw uit (of leg opnieuw handmatig vast), open het opgeslagen Mokbi-project opnieuw, vervang de frames en exporteer opnieuw. De compositie en vertalingen zijn bewaard.

Wanneer je de automatisering volledig kunt overslaan

Als je één tot drie releases per jaar uitbrengt, kost het bouwen van een integration_test screenshot-harness en een CI-pipeline je meer uren dan het ooit bespaart. Leg handmatig vast vanuit de simulator, componeer en lokaliseer eenmalig, en ga verder. De automatisering in Opties 1–3 is voor teams waarbij screenshot-veroudering een terugkerende kostenpost is — frequente releases, veel locales of meerdere apps in een portfolio. Pas de tooling aan op het releasetempo, niet op wat rigoureus lijkt.

Voordat je iets exporteert, loont het de moeite de doelen te controleren: haal de exacte pixelafmetingen op uit de App Store-screenshot-formaatgids en de formaatregels (PNG/JPEG, RGB, geen alfakanaal, 1–10 per apparaatklasse) uit de App Store-screenshot-vereisten. Apple wijst afmetingen met één pixel verschil af zonder tolerantie, dus dit is dertig seconden waard.

Verder lezen

Editor openen →