autoshot 1.1.0
autoshot: ^1.1.0 copied to clipboard
Automate App Store and Play Store screenshot generation across multiple devices, locales, and screens. Runs in Flutter Web and outputs a ZIP archive.
Autoshot #
A device_preview wrapper plugin that automates generating App Store / Play Store screenshots across multiple devices, locales, and screens — entirely from the Flutter Web runner.
autoshot wraps device_preview as a dependency, so you only need to add autoshot to your project — no need to depend on device_preview separately.
Problem #
Developers currently have to manually switch devices and locales in device_preview and take screenshots one-by-one. For an app targeting 3 devices × 5 locales × 4 screens, that's 60 manual screenshots.
This plugin turns that into a single button click.
Features #
- Single dependency: wraps
device_preview— just addautoshotand you're set - Matrix automation: iterates through every combination of
screens × locales × devices - Programmatic control: updates the
DevicePreviewstate (device, locale) without user interaction - Render-safe: waits for frames to settle after each state change before capturing
- Web-optimised: captures via
RepaintBoundary→toImage()→ PNG bytes - ZIP download: bundles all screenshots into a single
.zipand triggers a browser download - Progress UI: shows a live progress bar in the device_preview toolbar
- Naming convention:
device_name_locale_screen_name.png
Installation #
dependencies:
autoshot: ^1.1.0
Note: You do not need to add
device_previewseparately. It is re-exported byautoshot.
Quick Start #
import 'package:autoshot/autoshot.dart';
Just wrap your app in Autoshot — that's it:
Autoshot(
config: AutoshotConfig(
screens: [
ScreenEntry.widget(name: 'home', builder: (_) => HomeScreenMockup()),
ScreenEntry.widget(name: 'profile', builder: (_) => ProfileScreen()),
],
locales: [Locale('en', 'US'), Locale('fr', 'FR'), Locale('ar')],
devices: [
Devices.ios.iPhone13ProMax,
Devices.ios.iPadPro11Inches,
Devices.android.samsungGalaxyS20,
],
// Optional:
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
localizationsDelegates: [...],
settleDelay: Duration(milliseconds: 500),
),
builder: (context) => MyApp(),
)
Autoshot internally creates a DevicePreview with the automation controller
and toolbar already wired up. No manual setup needed.
Advanced: manual wiring
If you need full control (custom tool ordering, access to the controller, etc.), use the lower-level widgets directly:
final controller = AutoshotController();
DevicePreview(
builder: (context) => AutoshotApp(
controller: controller,
child: MyApp(),
),
tools: [
...DevicePreview.defaultTools,
AutoshotToolbar(
controller: controller,
config: AutoshotConfig(
screens: [
ScreenEntry.widget(name: 'home', builder: (_) => HomeScreen()),
ScreenEntry.widget(name: 'profile', builder: (_) => ProfileScreen()),
],
locales: [Locale('en', 'US'), Locale('fr', 'FR'), Locale('ar')],
devices: [
Devices.ios.iPhone13ProMax,
Devices.ios.iPadPro11Inches,
Devices.android.samsungGalaxyS20,
],
),
),
],
)
Preferred for real apps: Route-based capture #
When screens depend on providers, DI containers, or initialization that lives in your app shell,
use ScreenEntry.route(...) so captures run through your real MaterialApp and navigator.
final appNavigatorKey = GlobalKey<NavigatorState>();
Autoshot(
config: AutoshotConfig(
screens: [
ScreenEntry.route(
name: 'home',
navigate: (_) async {
appNavigatorKey.currentState?.pushNamedAndRemoveUntil(
'/home',
(route) => false,
);
},
),
ScreenEntry.route(
name: 'profile',
navigate: (_) async {
appNavigatorKey.currentState?.pushNamedAndRemoveUntil(
'/profile',
(route) => false,
);
},
),
],
locales: [Locale('en', 'US')],
devices: [Devices.ios.iPhone14Pro],
),
builder: (_) => MyApp(navigatorKey: appNavigatorKey),
)
Optional per-screen async preparation is available via prepare:.
Complete route example #
import 'package:autoshot/autoshot.dart';
import 'package:flutter/material.dart';
final appNavigatorKey = GlobalKey<NavigatorState>();
void main() {
runApp(
Autoshot(
config: AutoshotConfig(
screens: [
ScreenEntry.route(
name: 'home',
navigate: (_) async {
appNavigatorKey.currentState?.pushNamedAndRemoveUntil(
'/home',
(route) => false,
);
},
),
ScreenEntry.route(
name: 'profile',
navigate: (_) async {
appNavigatorKey.currentState?.pushNamedAndRemoveUntil(
'/profile',
(route) => false,
);
},
prepare: (_) async {
await Future.delayed(const Duration(milliseconds: 250));
},
),
],
locales: const [Locale('en', 'US')],
devices: [Devices.ios.iPhone14Pro],
),
builder: (_) => MyApp(navigatorKey: appNavigatorKey),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key, required this.navigatorKey});
final GlobalKey<NavigatorState> navigatorKey;
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
// ignore: deprecated_member_use
useInheritedMediaQuery: true,
locale: DevicePreview.locale(context),
builder: DevicePreview.appBuilder,
routes: {
'/home': (_) => const HomeScreen(),
'/profile': (_) => const ProfileScreen(),
},
initialRoute: '/home',
);
}
}
Click "Generate Screenshots" in the toolbar #
The plugin will:
- Loop through every
device × locale × screencombination - Programmatically switch the device and locale via
DevicePreviewStore - Select the target screen (route navigation or widget mode)
- Wait for the frame to settle
- Capture the pixels as PNG
- Package everything into a
.zipand trigger a browser download
Configuration #
| Parameter | Type | Default | Description |
|---|---|---|---|
screens |
List<ScreenEntry> |
required | Pages to screenshot |
locales |
List<Locale> |
required | Languages to iterate |
devices |
List<DeviceInfo> |
required | Target devices |
theme |
ThemeData? |
ThemeData.light() |
Light theme for captures |
darkTheme |
ThemeData? |
ThemeData.dark() |
Dark theme for captures |
localizationsDelegates |
List<LocalizationsDelegate>? |
null |
Your app's l10n delegates |
supportedLocales |
List<Locale>? |
locales |
Supported locales list |
includeDeviceFrame |
bool |
false |
Include device chrome in capture |
settleDelay |
Duration |
500ms |
Wait time after state changes |
Delivery Modes #
AutoshotToolbar(
delivery: AutoshotDelivery.zip, // default — single .zip download
// delivery: AutoshotDelivery.individual, // one download per image
)
Architecture #
AutoshotController ← shared state (widget mode screen override)
│
├── AutoshotApp ← widget wrapper inside DevicePreview.builder
│ shows controller.currentScreen ?? normalApp
│
└── AutoshotToolbar ← toolbar section widget
│
└── AutoshotRunner ← automation engine
│
├── changes device via store.selectDevice()
├── changes locale via store.data.copyWith(locale:)
├── either navigates route OR swaps widget screen
├── optional screen.prepare()
├── waits for frame settle
├── captures via DevicePreview.screenshot()
└── packages results → ZIP → browser download
Output #
The ZIP contains PNGs with the naming convention:
autoshot/
iphone_13_pro_max_en_us_home.png
iphone_13_pro_max_en_us_profile.png
iphone_13_pro_max_fr_fr_home.png
...
samsung_galaxy_s20_ar_home.png
...
Platform Support #
| Platform | Status |
|---|---|
| Web | ✅ Full support (ZIP download) |
| Mobile/Desktop | ⚠️ Capture works, but download requires a file-based processor |
License #
MIT