Bloc Event Pattern Dynamic Theme In Flutter :
Screenshot 1 :

Screenshot 2 :

Project Structure :


Core Package
- bloc Package
- data Package
- listners Package
- model Package
bloc Package
annotation_bloc.dart
import 'package:custom_theme/core/data/preferences.dart'; import 'package:custom_theme/core/listeners/actions.dart'; import 'package:custom_theme/utils/Translations.dart'; import 'package:custom_theme/utils/styles.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'bloc_base.dart'; import 'object_event.dart'; import 'object_state.dart'; class AnnotationBloc extends AnnotationBase { @override get initialState => ObjectUninitialized(); changeLanguageDialog(BuildContext context, ActionListener actionListener) { showDialog( barrierDismissible: true, context: context, builder: (BuildContext c) { return Dialog( backgroundColor: Colors.transparent, elevation: 0.0, child: Container( padding: EdgeInsets.all(16.0), height: 280, decoration: BoxDecoration( color: Styles.backgroundColor, borderRadius: BorderRadius.all(Radius.circular(10))), child: Column( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( Translations.current.text('language'), maxLines: 1, style: Styles.styleTitle(color: Styles.titleColor), ), SizedBox( height: 10, ), SizedBox( child: Container( color: Styles.placeholderColor, ), height: 0.5, width: double.infinity, ), ListTile( onTap: () async { Navigator.pop(context); await Tools.updateLanguage('pt'); actionListener.onChangeLanguage(Locale('pt')); }, title: Text( Translations.current.text('portuguese'), maxLines: 1, style: Styles.styleTitle(color: Styles.titleColor), ), trailing: Checkbox( value: Translations.current.locale.languageCode .contains('pt'), onChanged: null), ), ListTile( onTap: () { Navigator.pop(context); Tools.updateLanguage('en'); actionListener.onChangeLanguage(Locale('en')); }, title: Text( Translations.current.text('english'), maxLines: 1, style: Styles.styleTitle(color: Styles.titleColor), ), trailing: Checkbox( value: Translations.current.locale.languageCode .contains('en'), onChanged: null), ), ListTile( onTap: () { Navigator.pop(context); Tools.updateLanguage('es'); actionListener.onChangeLanguage(Locale('es')); }, title: Text( Translations.current.text('spanish'), maxLines: 1, style: Styles.styleTitle(color: Styles.titleColor), ), trailing: Checkbox( value: Translations.current.locale.languageCode .contains('es'), onChanged: null), ), FlatButton( onPressed: () { Navigator.pop(context); }, child: Text( Translations.current.text('cancel'), maxLines: 1, style: Styles.styleDescription( color: Styles.titleColor), )) ]))); }); } @override Stream<ObjectState> mapEventToState(ObjectEvent event) async* { if (event is Run) { try { if (currentState is ObjectUninitialized) { } if (currentState is ObjectLoaded) { } } catch (_) { } } else if (event is Reload) { } else if (event is Refresh) { } } AnnotationBloc() : super(); }
bloc_base.dart
import 'object_event.dart'; import 'object_state.dart'; import 'package:bloc/bloc.dart'; abstract class AnnotationBase extends Bloc<ObjectEvent, ObjectState> { }
config_bloc.dart
import 'package:custom_theme/core/data/preferences.dart'; import 'package:custom_theme/utils/Translations.dart'; import 'package:custom_theme/utils/styles.dart'; import 'package:flutter/material.dart'; import 'package:bloc/bloc.dart'; import 'config_event.dart'; class ConfigApp { ThemeData themeData = Styles.themeLight(); Locale locale = Translations.current.locale; } class ConfigAppBloc extends Bloc<ConfigAppEvent, ConfigApp> { @override ConfigApp get initialState => ConfigApp(); ThemeType themeType; ConfigApp configApp; ConfigAppBloc() { themeType = Tools.onThemeType(); configApp = ConfigApp(); dispatch(InitConfig()); } ThemeData _onTheme(ThemeType themeType) { return themeType == ThemeType.Light ? Styles.themeLight() : Styles.themeDark(); } @override Stream<ConfigApp> mapEventToState(ConfigAppEvent event) async* { configApp = ConfigApp(); if (event is InitConfig) { configApp.locale = await Tools.onLanguage(); configApp.themeData = themeType == ThemeType.Light ? Styles.themeLight() : Styles.themeDark(); yield configApp; } else if (event is ConfigChangeTheme) { var configTheme = event; themeType = configTheme.themeType; Tools.updateTheme(configTheme.themeType); configApp.themeData = _onTheme(themeType); yield configApp; } else if (event is ConfigChangeLanguage) { var configLanguage = event; Tools.updateLanguage(configLanguage.locale.languageCode); configApp.locale = configLanguage.locale; configApp.themeData = _onTheme(themeType); yield configApp; } } }
config_event.dart
import 'package:custom_theme/core/data/preferences.dart'; import 'package:flutter/painting.dart'; class ConfigAppEvent { @override String toString() => 'ConfigAppEvent'; } class InitConfig extends ConfigAppEvent { @override String toString() => 'InitConfig'; } class ConfigChangeTheme extends ConfigAppEvent { final ThemeType themeType; ConfigChangeTheme(this.themeType); @override String toString() => 'ConfigChangeTheme'; } class ConfigChangeLanguage extends ConfigAppEvent { final Locale locale; ConfigChangeLanguage(this.locale); @override String toString() => 'ConfigChangeLanguage'; }
object_event.dart
import 'package:equatable/equatable.dart'; abstract class ObjectEvent {} class Run extends ObjectEvent { @override String toString() => 'Run'; } class Reload extends ObjectEvent { @override String toString() => 'Reload'; } class Refresh extends ObjectEvent { @override String toString() => 'Reload'; }
object_state.dart
import 'package:equatable/equatable.dart'; abstract class ObjectState { ObjectState([List props = const []]); } class ObjectError extends ObjectState { @override String toString() => 'ObjectError'; } class ObjectRefresh extends ObjectState { @override String toString() => 'ObjectRefresh'; } class ObjectUninitialized extends ObjectState { @override String toString() => 'ObjectUninitialized'; } class ObjectLoaded extends ObjectState { final List<Object> objects; final bool hasReachedMax; ObjectLoaded({ this.objects, this.hasReachedMax, }) : super([objects, hasReachedMax]); ObjectLoaded copyWith({ List<Object> objects, bool hasReachedMax, }) { return ObjectLoaded( objects: objects ?? this.objects, hasReachedMax: hasReachedMax ?? this.hasReachedMax, ); } @override String toString() => 'ObjectLoaded { posts: ${objects.length}, hasReachedMax: $hasReachedMax }'; }
simple_bloc_delegate.dart
import 'package:bloc/bloc.dart'; class SimpleBlocDelegate extends BlocDelegate { @override void onEvent(Bloc bloc, Object event) { super.onEvent(bloc, event); print(event); } @override void onTransition(Bloc bloc, Transition transition) { super.onTransition(bloc, transition); print(transition); } @override void onError(Bloc bloc, Object error, StackTrace stacktrace) { super.onError(bloc, error, stacktrace); print(error); } }
data Package
preferences.dart
import 'dart:io'; import 'dart:ui'; import 'package:custom_theme/utils/Translations.dart'; import 'package:shared_preferences/shared_preferences.dart'; enum ThemeType { Dark, Light } class Tools { static SharedPreferences prefs; static final ThemeType_KEY = 'ThemeType'; static final LANGUAGE = 'LANGUAGE'; static final Languages = ['pt', 'en', 'es']; static init() async { prefs = await SharedPreferences.getInstance(); } static updateLanguage(String code) async { prefs.setString(LANGUAGE, code.trim()); await Translations.load(Locale(code)); } static updateTheme(ThemeType type) { prefs.setString(ThemeType_KEY, '${type}'); } static Future<Locale> onLanguage() async { String code = prefs.getString(LANGUAGE) ?? Platform.localeName; print('onLanguage : ${code}'); return Translations.filterLocale(Locale(code)); } static ThemeType onThemeType() { var type = prefs.getString(ThemeType_KEY) ?? null; return (type == null) ? ThemeType.Light : (type == '${ThemeType.Light}') ? ThemeType.Light : ThemeType.Dark; } }
listener Package
actions.dart
import 'package:flutter/cupertino.dart'; abstract class ActionListener { void onChangeLanguage(Locale locale); }
ui Package
- page Package
- router.dart file
page Package
setting.dart
import 'package:custom_theme/core/bloc/annotation_bloc.dart'; import 'package:custom_theme/core/bloc/config_bloc.dart'; import 'package:custom_theme/core/bloc/config_event.dart'; import 'package:custom_theme/core/bloc/object_event.dart'; import 'package:custom_theme/core/bloc/object_state.dart'; import 'package:custom_theme/core/data/preferences.dart'; import 'package:custom_theme/core/listeners/actions.dart'; import 'package:custom_theme/utils/Translations.dart'; import 'package:custom_theme/utils/styles.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class SettingPage extends StatelessWidget { @override Widget build(BuildContext context) { ConfigAppBloc themeBloc = BlocProvider.of<ConfigAppBloc>(context); return BlocProvider<AnnotationBloc>( builder: (context) => AnnotationBloc(), child: BlocBuilder<AnnotationBloc, ObjectState>( builder: (context, objectState) { return ThemePage(themeBloc); })); } } class ThemePage extends StatefulWidget { final ConfigAppBloc themeBloc; ThemePage(this.themeBloc); @override _ThemeState createState() => _ThemeState(themeBloc); } class _ThemeState extends State<ThemePage> implements ActionListener{ final ConfigAppBloc themeBloc; AnnotationBloc annotationBloc; _ThemeState(this.themeBloc); @override void initState() { annotationBloc = BlocProvider.of<AnnotationBloc>(context); super.initState(); annotationBloc..dispatch(Run()); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Setting Page'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( Translations.current.text('no_content'), ), Icon(Icons.add, color: Styles.iconColor), Text( '0', style: Styles.styleTitle( color: Styles.titleColor, size: false, fontWeight: FontWeight.bold), ), ], ), ), floatingActionButton: Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ Material( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.0), ), color: Styles.placeholderColor, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Container( child: Text( themeBloc.themeType == ThemeType.Light ? Translations.current.text('mode_dark_true') : Translations.current.text('mode_dark_false'), style: Styles.styleDescription( color: Styles.titleColor, textSizeDescription: 16, ), ), padding: EdgeInsets.only(left: 16.0), ), Switch( value: themeBloc.themeType == ThemeType.Light ? false : true, onChanged: (bool value) { themeBloc.dispatch(ConfigChangeTheme( value ? ThemeType.Dark : ThemeType.Light)); }), ], )), SizedBox( height: 10, ), FlatButton( padding: EdgeInsets.all(0.0), onPressed: () { annotationBloc.changeLanguageDialog(context, this); }, child: Material( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.0), ), color: Styles.placeholderColor, child: Container( padding: EdgeInsets.all(16.0), width: double.infinity, child: Text(Translations.current.text('language'), style: Styles.styleDescription( color: Styles.titleColor, textSizeDescription: 16, ))), ), ) ])); } @override void onChangeLanguage(Locale locale) { themeBloc.dispatch(ConfigChangeLanguage(locale)); } }
router.dart file outside the page Package
router.dart
import 'package:flutter/material.dart'; import 'page/setting.dart'; class Router { static Route<dynamic> generateRoute(RouteSettings settings) { switch (settings.name) { case '/': return MaterialPageRoute(builder: (_) => SettingPage()); case '/register': return MaterialPageRoute(builder: (_) => SettingPage()); default: return MaterialPageRoute(builder: (_) => SettingPage()); } } }
utils Package
styles.dart
import 'dart:ui'; import 'package:custom_theme/utils/utils.dart'; import 'package:flutter/material.dart'; class Styles { static final String fontNameDefault = 'FontLetra'; static final _textSizeTitle = 18.0; static final _textBigTitle = 40.0; static final _textSizeDefault = 16.0; static final _textSizeDescription = 13.0; static Color titleColor; static Color subtitleColor; static Color placeholderColor; static Color iconColor; static Color progressColor; static Color backgroundColor; static Color floattingBackgroundColor; static ThemeData themeData; static ThemeData themeDark() { titleColor = Colors.white; subtitleColor = Colors.white; iconColor = Colors.white; progressColor = colorParse(hexCode: '#F26430'); backgroundColor = colorParse(hexCode: '#1D2026'); placeholderColor = colorParse(hexCode: '#323640'); floattingBackgroundColor = Colors.deepOrange; return ThemeData( scaffoldBackgroundColor: backgroundColor, brightness: Brightness.dark, primaryColor: backgroundColor, accentColor: progressColor, indicatorColor: progressColor, ); } static ThemeData themeLight() { titleColor = Colors.black; subtitleColor = Colors.black; iconColor = Colors.white; progressColor = colorParse(hexCode: '#F26430'); backgroundColor = Colors.white; placeholderColor = Colors.grey[200]; floattingBackgroundColor = Colors.deepOrange; return ThemeData( scaffoldBackgroundColor: backgroundColor, brightness: Brightness.light, primaryColor: backgroundColor, accentColor: progressColor, ); } static styleTitle( {Color color, bool size = true, FontWeight fontWeight = FontWeight.bold}) => TextStyle( fontSize: size ? _textSizeTitle : _textBigTitle, fontWeight: fontWeight, color: color); static styleDefault( {Color color, FontWeight fontWeight = FontWeight.normal, bool size = true}) => TextStyle( fontSize: size ? _textSizeTitle : _textBigTitle, fontFamily: fontNameDefault, fontWeight: fontWeight, color: color); static styleDescription( {Color color, double textSizeDescription = 0, FontWeight fontWeight = FontWeight.normal}) => TextStyle( fontSize: textSizeDescription == 0 ? _textSizeDescription : textSizeDescription, fontWeight: fontWeight, fontFamily: fontNameDefault, color: color); }
Translations.dart
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class Translations { Translations(Locale locale) { current = this; this.locale = locale; } Locale locale; static Map<dynamic, dynamic> _localizedValues; static Translations current; static Translations of(BuildContext context) { var result = Localizations.of<Translations>(context, Translations); return result; } String text(String key) { return _localizedValues[key] ?? '** $key not found'; } Future<Translations> run() async { Translations translations = new Translations(locale); String jsonContent = await rootBundle .loadString("assets/locale/i18n_${locale.languageCode}.json"); _localizedValues = json.decode(jsonContent); return translations; } static Locale filterLocale(Locale locale) { if (locale.languageCode.contains('pt')) { return Locale('pt'); } else if (locale.languageCode.contains('en')) { return Locale('en'); } else if (locale.languageCode.contains('es')) { return Locale('es'); } else { return Locale('en'); } } static bool isSupported(Locale locale) => ['en', 'pt', 'es'].contains(locale.languageCode); static Future<Translations> load(Locale locale) async { locale = filterLocale(locale); Translations translations = new Translations(locale); String jsonContent = await rootBundle .loadString("assets/locale/i18n_${locale.languageCode}.json"); _localizedValues = json.decode(jsonContent); current = await translations.run(); return translations; } get currentLanguage => filterLocale(locale); } class TranslationsDelegate extends LocalizationsDelegate<Translations> { const TranslationsDelegate(); @override bool isSupported(Locale locale) => ['en', 'pt', 'es'].contains(locale.languageCode); @override Future<Translations> load(Locale locale) async { return null; } @override bool shouldReload(TranslationsDelegate old) => false; }
utils.dart
import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'Translations.dart'; String formatDate(String value, {bool format = false}) { var now = DateTime.parse(value); var formatter = DateFormat( format ? 'dd' : 'dd.MM.yyyy', Translations.current.locale.languageCode); return format ? '${formatter.format(now)} ${_weekday(now)}' : formatter.format(now); } String _weekday(DateTime dateTime) { var day = weekday(dateTime); return day.length > 3 ? day.substring(0, 3) : day; } weekday(DateTime dateTime) { switch (dateTime.weekday) { case 1: return Translations.current.text('Monday'); case 2: return Translations.current.text('Tuesday'); case 3: return Translations.current.text('Wednesday'); case 4: return Translations.current.text('Thursday'); case 5: return Translations.current.text('Friday'); case 6: return Translations.current.text('Saturday'); case 7: return Translations.current.text('Sunday'); } } progressWidget() { return Center(child: CircularProgressIndicator()); } String formatHora(String value) { var now = DateTime.parse(value); var formatter = DateFormat('hh:mm', Translations.current.locale.languageCode); return formatter.format(now); } parseColor({String hexCode}) {} colorParse({String hexCode}) { try { String hex = hexCode.replaceAll("#", ""); if (hex.isEmpty) hex = "ffffff"; if (hex.length == 3) { hex = '${hex.substring(0, 1)}${hex.substring(0, 1)}${hex.substring(1, 2)}${hex.substring(1, 2)}${hex.substring(2, 3)}${hex.substring(2, 3)}'; } Color col = Color(int.parse(hex, radix: 16)).withOpacity(1.0); return col; } catch (_) {} return Colors.black; }
Outside the All Package
main.dart
import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'core/bloc/simple_bloc_delegate.dart'; import 'core/bloc/config_bloc.dart'; import 'core/data/preferences.dart'; import 'ui/router.dart'; import 'utils/Translations.dart'; import 'package:bloc/bloc.dart'; Future<Null> main() async { WidgetsFlutterBinding.ensureInitialized(); BlocSupervisor.delegate = SimpleBlocDelegate(); await Tools.init(); await Translations.load(await Tools.onLanguage()); runApp(StartApp()); } class StartApp extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider<ConfigAppBloc>( builder: (context) => ConfigAppBloc(), child: BlocBuilder<ConfigAppBloc, ConfigApp>(builder: (context, config) { return MaterialApp( supportedLocales: [ const Locale('pt', 'PT'), const Locale('es', 'ES'), const Locale('en' 'US'), config.locale ], localizationsDelegates: [ const TranslationsDelegate(), GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate ], localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) { for (Locale supportedLocale in supportedLocales) { if (supportedLocale.languageCode == locale.languageCode || supportedLocale.countryCode == locale.countryCode) { return supportedLocale; } } return supportedLocales.first; }, debugShowCheckedModeBanner: false, theme: config.themeData, initialRoute: '/', onGenerateRoute: Router.generateRoute, ); })); } }
pubspec.yaml
name: custom_theme description: A new Flutter application. version: 1.0.0+1 environment: sdk: ">=2.1.0 <3.0.0" dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.2 intl: flutter_bloc: equatable: shared_preferences: flutter_localizations: sdk: flutter dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true assets: - assets/locale/i18n_en.json - assets/locale/i18n_pt.json - assets/locale/i18n_es.json
assets :
i18n_en.json
{ "no_content": "No content", "mode_dark_true": "Activate dark mode", "mode_dark_false": "Disable dark mode", "language": "Language", "portuguese": "Portuguese", "english": "English", "spanish": "Spanish", "cancel": "Cancel" }
i18n_es.json
{ "no_content": "Sem conteúdo", "mode_dark_true": "Activar el modo oscuro", "mode_dark_false": "Desactivar el modo oscuro", "language": "Idioma", "portuguese": "Portugués", "english": "Inglés", "spanish": "Español", "cancel": "Cancelar" }
i18n_pt.json
{ "no_content": "Sem conteúdo", "mode_dark_true": "Activar modo escuro", "mode_dark_false": "Desactivar modo escuro", "language": "Idiomas", "portuguese": "Português", "english": "Inglês", "spanish": "Espanhol", "cancel": "Cancelar" }
The flutter tutorial is a website that bring you the latest and amazing resources of code. All the languages codes are included in this website. The languages like flutter, android, java,kotlin etc.with the help of this languages any user can develop the beautiful application
For more information about Flutter. visit www.fluttertutorial.in