App Theme Setter

A powerful, SOLID-compliant dynamic theming package for Flutter. Effortlessly switch themes, customize deep UI elements using extensions, and add new themes in a plug-and-play manner.

Features

  • 🚀 Dynamic Switching: Change themes on the fly with a single method call.
  • 🎨 Deep Customization: Use ThemeExtension to style field decorations, gradients, fonts, and more.
  • 🏗️ SOLID Principles: Clean architecture for maintainability and scalability.
  • 🔌 Plug & Play: Add new themes without modifying existing UI or controller code.
  • 💾 Persistence: Built-in support for saving theme selection across app restarts.

Getting Started

1. Define Your Theme

Extend IAppTheme for each theme in your app. Using extends allows you to inherit the fullThemeData helper.

class MyLightTheme extends IAppTheme {
  @override
  String get id => 'light';

  @override
  String get name => 'Light Mode';

  @override
  ThemeData get themeData => ThemeData(
    useMaterial3: true,
    brightness: Brightness.light,
    primarySwatch: Colors.blue,
  );

  @override
  AppThemeExtension get extension => AppThemeExtension(
    primaryGradientStart: Colors.blue.shade300,
    primaryGradientEnd: Colors.blue.shade700,
    borderRadius: 12.0,
    customFieldDecoration: InputDecoration(
      border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
      filled: true,
      fillColor: Colors.blue.shade50,
    ),
  );
}

2. Persistence (Optional)

Implement IThemePersistence to save the theme choice (e.g., using shared_preferences).

class MyThemePersistence implements IThemePersistence {
  @override
  Future<void> saveThemeId(String themeId) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('selected_theme', themeId);
  }

  @override
  Future<String?> loadThemeId() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getString('selected_theme');
  }
}

3. Provide the Theme

Initialize the ThemeController in your main() and call init() to load saved settings.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final controller = ThemeController(
    initialTheme: MyLightTheme(),
    persistence: MyThemePersistence(), // Pass your persistence implementation
    availableThemes: [MyLightTheme(), MyDarkTheme()],
  );

  // Load saved theme before running app
  await controller.init();

  runApp(
    ThemeProvider(
      controller: controller,
      child: MyApp(),
    ),
  );
}

4. Consume the Theme

Use ValueListenableBuilder to rebuild the MaterialApp when the theme changes.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<IAppTheme>(
      valueListenable: ThemeProvider.of(context),
      builder: (context, currentTheme, _) {
        return MaterialApp(
          theme: currentTheme.fullThemeData, // Includes custom extensions
          home: HomeScreen(),
        );
      },
    );
  }
}

5. Custom Styling Access

Access your custom extensions anywhere in the widget tree:

final appExtension = Theme.of(context).extension<AppThemeExtension>()!;
final decoration = appExtension.customFieldDecoration;

Folder Structure

lib/
├── src/
│   ├── core/           # Interfaces (IAppTheme, IThemePersistence) & Extensions
│   ├── controller/     # ThemeController State Management
│   ├── widgets/        # ThemeProvider Inherited Widget
│   └── themes/         # Concrete Theme Implementations
└── app_theme_setter.dart

License

MIT

Libraries

app_theme_setter
A powerful, SOLID-compliant dynamic theming package for Flutter.