Imported from GiovanniDrago/opencode-skills (
mobile/flutter-android-starter/SKILL.md). Install upstream withnpx skills add GiovanniDrago/opencode-skills --skill flutter-android-starter. Copyright stays with the author.
When to use
Use this skill only for brand-new Flutter Android projects that need a solid, production-ready foundation before feature development begins.
This skill sets up:
- Material 3 dynamic theming with a curated light/dark theme catalog (inspired by
ColorScheme.fromSeed) - Riverpod v2 state management with
StateNotifierProviderfor theme and locale persistence - Localization pre-configured for English + Italian, with infrastructure to add more languages
- Comprehensive
.gitignorefor Flutter, Android, and VSCode - README with a human-friendly title and startup commands
- GitHub deploy integration via the
flutter-github-deployskill if available
This skill is not for existing projects. For existing codebases, apply the individual domain skills (flutter-localizing-apps, flutter-github-deploy) instead.
What this foundation contains
Theme system
A Material 3 theme catalog managed by Riverpod and persisted with shared_preferences.
lib/theme/app_theme.dart— definesAppThemeOption, the theme catalog, andbuildAppTheme()lib/providers/theme_provider.dart—StateNotifierProviderthat loads/saves the active theme- Themes are generated dynamically via
ColorScheme.fromSeed(seedColor, brightness)withuseMaterial3: true - Light and dark variants are discrete options in the catalog
Localization
Flutter's built-in flutter_localizations + intl with code generation.
l10n.yaml— gen-l10n configurationlib/l10n/app_en.arb— English templatelib/l10n/app_it.arb— Italian translationlib/providers/locale_provider.dart—StateNotifierProviderfor locale persistence- Adding a new language later: create
app_XX.arb, addLocale('XX')tosupportedLocales, runflutter gen-l10n
State management
Riverpod v2 with flutter_riverpod.
- Root app wrapped in
ProviderScope ConsumerWidget/ConsumerStatefulWidgetfor UIStateNotifierProviderfor theme and localeshared_preferencesused for lightweight persistence (swappable to Hive/ObjectBox later without changing provider structure)
GitHub Actions deploy
This skill references flutter-github-deploy rather than duplicating it.
- If
flutter-github-deployis available in the OpenCode skills registry, load and apply it after the foundation is set. - If it is not available, document in the README that the user can apply it later.
Source patterns to mirror
pubspec.yaml dependencies
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
cupertino_icons: ^1.0.8
flutter_riverpod: ^2.6.1
shared_preferences: ^2.5.3
intl: any
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
flutter:
uses-material-design: true
generate: true
l10n.yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
lib/l10n/app_en.arb
{
"@@locale": "en",
"appTitle": "My App",
"@appTitle": {
"description": "Application title"
},
"themeLabel": "Theme",
"@themeLabel": {
"description": "Theme setting label"
},
"languageLabel": "Language",
"@languageLabel": {
"description": "Language setting label"
}
}
lib/l10n/app_it.arb
{
"@@locale": "it",
"appTitle": "La Mia App",
"themeLabel": "Tema",
"languageLabel": "Lingua"
}
lib/theme/app_theme.dart
import 'package:flutter/material.dart';
class AppThemeOption {
final String id;
final String name;
final Color seedColor;
final Brightness brightness;
const AppThemeOption({
required this.id,
required this.name,
required this.seedColor,
required this.brightness,
});
}
const List<AppThemeOption> appThemes = [
AppThemeOption(
id: 'default_light',
name: 'Default Light',
seedColor: Color(0xFF6750A4),
brightness: Brightness.light,
),
AppThemeOption(
id: 'ocean_light',
name: 'Ocean Light',
seedColor: Color(0xFF00677D),
brightness: Brightness.light,
),
AppThemeOption(
id: 'default_dark',
name: 'Default Dark',
seedColor: Color(0xFFD0BCFF),
brightness: Brightness.dark,
),
AppThemeOption(
id: 'forest_dark',
name: 'Forest Dark',
seedColor: Color(0xFF2E5D3B),
brightness: Brightness.dark,
),
];
AppThemeOption appThemeById(String id) {
return appThemes.firstWhere(
(theme) => theme.id == id,
orElse: () => appThemes.first,
);
}
ThemeData buildAppTheme(AppThemeOption option) {
final scheme = ColorScheme.fromSeed(
seedColor: option.seedColor,
brightness: option.brightness,
);
final isDark = option.brightness == Brightness.dark;
return ThemeData(
useMaterial3: true,
colorScheme: scheme,
scaffoldBackgroundColor: scheme.surface,
appBarTheme: AppBarTheme(
elevation: 0,
centerTitle: true,
backgroundColor: scheme.surface,
foregroundColor: scheme.onSurface,
surfaceTintColor: scheme.surfaceTint,
),
cardTheme: CardThemeData(
elevation: isDark ? 1 : 2,
color: scheme.surfaceContainerHighest,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: scheme.surfaceContainerHighest,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(color: scheme.outlineVariant),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(color: scheme.primary, width: 2),
),
hintStyle: TextStyle(color: scheme.onSurfaceVariant),
),
dividerTheme: DividerThemeData(color: scheme.outlineVariant),
chipTheme: ChipThemeData(
backgroundColor: scheme.surfaceContainerHighest,
selectedColor: scheme.primaryContainer,
labelStyle: TextStyle(color: scheme.onSurface),
secondaryLabelStyle: TextStyle(color: scheme.onSecondaryContainer),
shape: const StadiumBorder(),
),
snackBarTheme: SnackBarThemeData(
backgroundColor: scheme.inverseSurface,
contentTextStyle: TextStyle(color: scheme.onInverseSurface),
actionTextColor: scheme.inversePrimary,
),
listTileTheme: ListTileThemeData(
iconColor: scheme.primary,
textColor: scheme.onSurface,
),
);
}
lib/providers/theme_provider.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../theme/app_theme.dart';
final themeProvider = StateNotifierProvider<ThemeNotifier, AppThemeOption>((ref) {
return ThemeNotifier();
});
class ThemeNotifier extends StateNotifier<AppThemeOption> {
static const _key = 'theme_id';
ThemeNotifier() : super(appThemes.first) {
_load();
}
Future<void> _load() async {
final prefs = await SharedPreferences.getInstance();
final id = prefs.getString(_key);
if (id != null) {
state = appThemeById(id);
}
}
Future<void> setTheme(String id) async {
final option = appThemeById(id);
state = option;
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_key, id);
}
}
lib/providers/locale_provider.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
final localeProvider = StateNotifierProvider<LocaleNotifier, Locale>((ref) {
return LocaleNotifier();
});
class LocaleNotifier extends StateNotifier<Locale> {
static const _key = 'locale';
LocaleNotifier() : super(const Locale('en')) {
_load();
}
Future<void> _load() async {
final prefs = await SharedPreferences.getInstance();
final tag = prefs.getString(_key);
if (tag != null) {
state = Locale(tag);
}
}
Future<void> setLocale(Locale locale) async {
state = locale;
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_key, locale.languageCode);
}
}
lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'providers/locale_provider.dart';
import 'providers/theme_provider.dart';
import 'theme/app_theme.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final themeOption = ref.watch(themeProvider);
final locale = ref.watch(localeProvider);
return MaterialApp(
title: AppLocalizations.of(context)?.appTitle ?? 'My App',
debugShowCheckedModeBanner: false,
theme: buildAppTheme(themeOption),
locale: locale,
supportedLocales: const [
Locale('en'),
Locale('it'),
],
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
home: const HomePage(),
);
}
}
class HomePage extends ConsumerWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final themeNotifier = ref.read(themeProvider.notifier);
final localeNotifier = ref.read(localeProvider.notifier);
return Scaffold(
appBar: AppBar(title: Text(l10n.appTitle)),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.themeLabel, style: Theme.of(context).textTheme.titleMedium),
Wrap(
spacing: 8,
children: appThemes.map((t) {
return ActionChip(
label: Text(t.name),
onPressed: () => themeNotifier.setTheme(t.id),
);
}).toList(),
),
const SizedBox(height: 24),
Text(l10n.languageLabel, style: Theme.of(context).textTheme.titleMedium),
Wrap(
spacing: 8,
children: [
ActionChip(
label: const Text('English'),
onPressed: () => localeNotifier.setLocale(const Locale('en')),
),
ActionChip(
label: const Text('Italiano'),
onPressed: () => localeNotifier.setLocale(const Locale('it')),
),
],
),
],
),
),
);
}
}
Workflow
Phase 1: Project creation and inspection
- Confirm the target directory is empty or contains a fresh Flutter project.
- If
pubspec.yamlis missing, runflutter create --platforms=android . - Read existing
pubspec.yaml,.gitignore, andREADME.mdif they exist.
Phase 2: Dependencies
- Update
pubspec.yamlwith the dependencies listed above. - Ensure
flutter.generate: trueis present. - Run
flutter pub get.
Phase 3: Localization infrastructure
- Create
l10n.yamlat project root. - Create
lib/l10n/app_en.arbandlib/l10n/app_it.arbwith starter keys. - Run
flutter gen-l10nto verify ARB syntax and generateAppLocalizations.
Phase 4: Theme system
- Create
lib/theme/app_theme.dartwith the catalog andbuildAppTheme().
Phase 5: Riverpod providers
- Create
lib/providers/theme_provider.dartwithStateNotifierProviderandshared_preferencespersistence. - Create
lib/providers/locale_provider.dartwith the same pattern.
Phase 6: Main app entry point
- Rewrite
lib/main.dartto:- Wrap with
ProviderScope - Use
ConsumerWidget - Build
MaterialAppwiththeme,locale,supportedLocales, andlocalizationsDelegates - Include a minimal
HomePagewith working theme and locale toggles
- Wrap with
Phase 7: .gitignore
- Create or update
.gitignorewith the comprehensive Flutter + VSCode template below.
Phase 8: GitHub deploy integration
- Check whether the
flutter-github-deployskill is available. - If available: load and apply it to set up tag-driven GitHub Actions release.
- If not available: add a note in the README telling the user they can apply it later.
Phase 9: README
- Derive a friendly title from
pubspec.yamlname, the folder name, or the user's prompt.- Replace underscores with spaces.
- Title-case the result.
- Write a short, friendly description.
- Add the startup commands section:
flutter pub get flutter gen-l10n flutter run - Add a note about supported locales and how to add more.
- Add a note about the theme system.
Phase 10: Verification
- Run
flutter pub get. - Run
flutter gen-l10n. - Run
flutter analyze. - Confirm the app builds and runs with working theme and locale toggles.
Comprehensive .gitignore template
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ / Android Studio
*.iml
*.ipr
*.iws
.idea/
# VS Code
.vscode/
*.code-workspace
# Flutter / Dart / Pub
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
.packages
.packages.generated
.pub-preload-cache/
# Symbolication
app.*.symbols
# Obfuscation
app.*.map.json
# Android build artifacts
/android/app/debug
/android/app/profile
/android/app/release
.gradle/
**/android/**/gradle-wrapper.jar
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
/android/key.properties
*.jks
**/android/app/*.keystore
# iOS / Xcode
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS
**/Flutter/ephemeral/
**/Pods/
**/macos/Flutter/GeneratedPluginRegistrant.swift
**/macos/Flutter/ephemeral
**/xcuserdata/
# Windows
**/windows/flutter/generated_plugin_registrant.cc
**/windows/flutter/generated_plugin_registrant.h
**/windows/flutter/generated_plugins.cmake
# Linux
**/linux/flutter/generated_plugin_registrant.cc
**/linux/flutter/generated_plugin_registrant.h
**/linux/flutter/generated_plugins.cmake
# Generated files
*.g.dart
*.freezed.dart
*.mocks.dart
# Environment
.env
.env.local
.env.*.local
# Coverage
coverage/
# Temporary
*.tmp
*.temp
*.cache
pubspec.lock
Adaptation rules
Apply only minimal adaptations. Keep the foundation identical.
- Keep the folder structure (
lib/theme/,lib/providers/,lib/l10n/). - Keep the theme catalog pattern; only adapt seed colors if the user explicitly requests different ones.
- Keep
shared_preferencesas the default persistence layer. Do not swap to Hive or ObjectBox unless the user explicitly asks. - Keep the default locales as English and Italian. Add more only when the user requests them.
- Keep the
flutter-github-deployreference as a delegation, not an inline duplicate. - Do not add architecture patterns (Clean Architecture, BLoC, etc.) unless the user explicitly asks.
- Do not add Firebase, analytics, or push notifications unless the user explicitly asks.
- Do not add test files unless the user explicitly asks.
Integration with flutter-github-deploy
After the foundation is complete:
- Check if
flutter-github-deployis in the available skills. - If yes, load it and follow its workflow to add the tag-driven GitHub Actions Android release flow.
- If no, append this section to the README:
## GitHub Release Build (Optional) To add automated signed APK releases via GitHub Actions, apply the `flutter-github-deploy` skill.
Important caveats
- This skill is designed for new projects. Running it on an existing project may overwrite
lib/main.dartandpubspec.yaml. - The demo
HomePageinlib/main.dartis intentionally minimal. It should be replaced with real app screens. shared_preferencesis suitable for small config data (theme ID, locale). Do not use it for large datasets or complex relational data.- A local
flutter build apk --releasemay fail without local Android signing files. The debug build will work for development.
Implementation checklist for OpenCode
- inspect the target directory; run
flutter create --platforms=android .only if needed - update
pubspec.yamlwith dependencies andgenerate: true - create
l10n.yamland starter ARB files - create
lib/theme/app_theme.dart - create
lib/providers/theme_provider.dartandlib/providers/locale_provider.dart - rewrite
lib/main.dartwithProviderScope,ConsumerWidget,MaterialApp, and demoHomePage - create or update
.gitignorewith the comprehensive template - check for
flutter-github-deployskill availability and apply if present - generate or update
README.mdwith a friendly title and startup commands - run
flutter pub get,flutter gen-l10n, andflutter analyze - verify the app runs and theme/locale toggles work
Expected outcome
After applying this skill, the target repository should be a runnable Flutter Android project with:
- A working Material 3 theme system with multiple light and dark options
- English and Italian localization via
AppLocalizations - Riverpod-managed theme and locale state persisted across app restarts
- A comprehensive
.gitignoreready for VSCode and Flutter development - A friendly README with install and run commands
- Optionally, the
flutter-github-deployrelease pipeline if that skill was available
The user can then immediately start building features on top of this foundation.