Flutter Locales Using Hive and Riverpod - flutter

I am using Hive with app_profile data model to store the app settings in local DB, and using riverpod to call HivedataStore (dependency injection).
The problem is that when I update the hive box of the local type, the app needs to be restarted to take effect, but if I update the Theme to Dark it work as it supposed to.
Here is my code:
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// await AppAssets.preloadSVGs();
final dataStore = HiveDataStore();
await dataStore.init();
await dataStore.createDefaultProfile(
appProfile: AppProfile(
onBoardingCompleted: false,
locale: 'en',
themeMode: ThemeModeCustomized.light,
));
runApp(ProviderScope(overrides: [
hiveDataStoreProvider.overrideWithValue(dataStore),
], child: const MyApp()));
}
hive_data_store.dart
class HiveDataStore {
static const profileBoxName = 'appProfile';
static const themeColor = 'themeColor';
Future<void> init() async {
await Hive.initFlutter();
Hive.registerAdapter<AppProfile>(AppProfileAdapter());
Hive.registerAdapter(ThemeModeCustomizedAdapter());
await Hive.openBox<AppProfile>(profileBoxName);
}
Future<void> createDefaultProfile({
required AppProfile appProfile,
}) async {
final box = Hive.box<AppProfile>(profileBoxName);
if (box.isEmpty) {
await box.put('0', appProfile);
} else {
print('box already have these Values : ${box.get(0)?.locale}');
}
}
Future<void> updateBox({
bool? onBoardingCompleted,
String? locale,
ThemeModeCustomized? themeMode,
}) async {
final box = Hive.box<AppProfile>(profileBoxName);
final userProfile = box.get('0');
final newProfile = userProfile!.copyWith(
onBoardingCompleted: onBoardingCompleted,
locale: locale,
themeMode: themeMode);
await box.put('0', newProfile);
}
AppProfile? appProfile() {
final box = Hive.box<AppProfile>(profileBoxName);
return box.get(0);
}
ValueListenable<Box<AppProfile>> appProfileListenable() {
return Hive.box<AppProfile>(profileBoxName).listenable();
}
}
final hiveDataStoreProvider = Provider<HiveDataStore>((ref) {
throw UnimplementedError();
});
myapp.dart
class MyApp extends ConsumerWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final provider = ref.watch(hiveDataStoreProvider);
return ValueListenableBuilder(
valueListenable: provider.appProfileListenable(),
builder: (context, Box<AppProfile> box, __) {
print('myapp rebuilds listenablebuilder');
final appProfile = box.get('0');
return MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppAssets.getLocals(appProfile!),
onGenerateTitle: (context) {
var t = AppLocalizations.of(context);
return t!.appTitle;
},
themeMode: AppAssets.themeModeGeter(appProfile),
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
initialRoute: '/',
routes: {
'/': (context) {
return const HomePage();
}
},
);
});
}
}
homepage.dart
class HomePage extends StatelessWidget {
const HomePage({super.key});
#override
Widget build(BuildContext context) {
print('homepage rebuils');
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.appTitle),
),
body: Center(
child: Consumer(builder: (context, WidgetRef ref, __) {
return Column(
children: [
TextButton(
onPressed: () {
ref.read(hiveDataStoreProvider).updateBox(
locale: 'ar', themeMode: ThemeModeCustomized.dark
);
},
child: const Text('العربية')),
TextButton(
onPressed: () {
ref.read(hiveDataStoreProvider).updateBox(
locale: 'en', themeMode: ThemeModeCustomized.light
);
},
child: const Text('English')),
],
);
}),
),
);
}
}
I think something need to be Changed in MyApp class, I use ValueListenableBuilder because I've seen in it in the Hive official package page.
Why the app need to be restarted to take effect for the locales? Unlike the app Theme which works perfectly.
Here my app_assets.dart code (just extra info):
class AppAssets {
static List<Locale> getLocals(AppProfile appProfile) {
switch (appProfile.locale) {
case 'ar':
return [const Locale('ar', '')];
case 'en':
return [const Locale('en', '')];
case '':
return AppLocalizations.supportedLocales;
default:
return AppLocalizations.supportedLocales;
}
}
static ThemeMode themeModeGeter(AppProfile? appProfile) {
switch (appProfile?.themeMode.name) {
case 'dark':
{
return ThemeMode.dark;
}
case 'light':
{
return ThemeMode.light;
}
case 'system':
{
return ThemeMode.system;
}
default:
ThemeMode.system;
}
return ThemeMode.system;
}
}

my mistake is that I was updating supportedLocales without updating locale in the MaterialApp Widget , I will keep the question though it might have a hint for others ..
supportedLocales: AppAssets.getLocals(appProfile),
locale: Locale('$appProfile.locale',''),

Related

Flutter bloc listener doesn't change the loading when the state is changed

I'm using bloc consumer but the listener doesn't change when the new state is emitted. The way the app works is, first, login page is shown and after login button is hit the loading overlay is shown and then the notesview will be shown. But when the notesview is shown the loading overlay is not hidden. It's hidden only if I press hot restart.
Main
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
const CustomTheme(
initialThemeKey: ThemeKeys.light,
child: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Von Note',
theme: CustomTheme.of(context),
routes: {
createUpdateNoteView: (context) => const CreateUpdateNoteView(),
},
home: BlocProvider<AuthBloc>(
create: (context) => AuthBloc(FirebaseAuthProvider()),
child: const Content()),
);
}
}
class Content extends StatelessWidget {
const Content({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
context.read<AuthBloc>().add(const AuthEventInitialize());
return BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) {
if (state.isLoading) {
LoadingScreen().show(
context: context,
text: state.loadingText.toString(),
);
} else {
LoadingScreen().hide();
}
},
builder: (context, state) {
if (state is AuthStateLoggedIn) {
return const NotesView();
} else if (state is AuthStateNeedVerification) {
return const VerifyEmailView();
} else if (state is AuthStateLoggedOut) {
return const LoginView();
} else if (state is AuthStateRegistering) {
return const RegisterView();
} else {
return const Scaffold(
body: Splash(),
);
}
},
);
}
}
Bloc
on<AuthEventLogIn>((event, emit) async {
emit(const AuthStateLoggedOut(
exception: null,
isLoading: true,
loadingText: 'Please wait while I log you in',
));
final email = event.email;
final password = event.password;
try {
final user = await provider.logIn(
email: email,
password: password,
);
if (user.isEmailVerified) {
emit(const AuthStateLoggedOut(
exception: null,
isLoading: false,
));
emit(AuthStateLoggedIn(
user: user,
isLoading: false,
));
} else {
emit(
const AuthStateLoggedOut(
exception: null,
isLoading: false,
),
);
emit(const AuthStateNeedVerification(isLoading: false));
}
} on Exception catch (e) {
emit(AuthStateLoggedOut(
exception: e,
isLoading: false,
));
}
});
State
class AuthStateLoggedOut extends AuthState with EquatableMixin {
final Exception? exception;
const AuthStateLoggedOut({
required this.exception,
required bool isLoading,
String? loadingText,
}) : super(
isLoading: isLoading,
loadingText: loadingText,
);
#override
List<Object?> get props => [exception, isLoading];
}
Tree
I've tried to change the props in equatable adding loadingtext but didn't work, tried to call LoadingScreen().hide in init notesview didn't work, and tried to call LoadingScreen().hide in builder if state is AuthStateLoggedIn also didn't work.

How to force flutter web preserve current language when enter the URL directly into the browser

I have an application that supports two languages; English and Arabic. The URL path starts with /en/home for English and /ar/home for Arabic. Language switching works fine. The issues that I am facing are:
When user switches language (clicking the button), the browser URL path does not get updated to reflect the selected language.
If the user enters the URL manually in the browser to access the Arabic version /ar/home, the page language keeps showing in English.
Below is a test code that can simulate the problem. Translation files are added as comments at the end of the code.
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:url_strategy/url_strategy.dart';
import 'package:provider/provider.dart';
var urlLang = '';
class L10n {
static final all = [
const Locale('en', ''),
const Locale('ar', ''),
];
}
Locale getSwitchToLanguage(Locale currentLocale) {
if (currentLocale == const Locale('ar', '')) {
return const Locale('en', '');
}
return const Locale('ar', '');
}
class LocaleProvider extends ChangeNotifier {
Locale _locale = const Locale('en', '');
Locale get locale => _locale;
void setLocale(Locale locale) {
if (!L10n.all.contains(locale)) return;
_locale = locale;
notifyListeners();
}
void clearLocale() {
_locale = const Locale('en', '');
notifyListeners();
}
}
void switchLanguage(BuildContext context) {
final provider = Provider.of<LocaleProvider>(context, listen: false);
final siwtchToLocale = getSwitchToLanguage(provider.locale);
provider.setLocale(siwtchToLocale);
}
void main() {
setPathUrlStrategy();
runApp(
const MyApp(),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) => ChangeNotifierProvider(
create: (context) => LocaleProvider(),
builder: (context, child) {
final provider = Provider.of<LocaleProvider>(context);
urlLang = provider.locale.languageCode;
return MaterialApp(
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: L10n.all,
locale: provider.locale,
debugShowCheckedModeBanner: false,
initialRoute: '/$urlLang/home',
// routing
onGenerateRoute: (settings) {
if (settings.name == '/ar/home' || settings.name == '/en/home') {
return MaterialPageRoute(
settings: settings, builder: (context) => const HomePage());
}
return MaterialPageRoute<void>(
settings: settings,
builder: (BuildContext context) => const UnknownScreen(),
);
},
onUnknownRoute: (settings) {
return MaterialPageRoute<void>(
settings: settings,
builder: (BuildContext context) => const UnknownScreen(),
);
},
);
},
);
}
class UnknownScreen extends StatelessWidget {
const UnknownScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("404 page"),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.appTitle),
),
body: Center(
child: Column(children: [
const SizedBox(height: 50),
urlLang == 'ar'
? Text("This is the Arabic version : $urlLang")
: Text("This is the English version : $urlLang"),
const SizedBox(height: 100),
ElevatedButton(
child: Text(
urlLang == 'en' ? "Switch to Arabic" : "Switch to English",
),
onPressed: () {
switchLanguage(context);
},
)
]),
),
);
}
}
/*
app_en.arb file content
{
"appTitle": "Home Page",
"not_used" : "not_used"
}
app_ar.arb file content:
{
"appTitle": "الصفحة الرئيسية - Home Page",
"not_used" : "not_used"
}
*/
/*
pubspec.yaml
name: langissue
description: A new Flutter project.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ">=2.17.3 <3.0.0"
dependencies:
flutter:
sdk: flutter
url_strategy: ^0.2.0
intl: ^0.17.0
flutter_web_plugins:
sdk: flutter
provider: ^6.0.3
async: ^2.8.2
flutter_localizations:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
generate: true
*/
To get changes from the URL by the user Navigator 2.0 should be used. More specifically the method RouterDelegate.setNewRoutePath should be implemented. So, instead of MaterialApp.onGenerateRoute and MaterialApp.initialRoute properties, the MaterialApp.routeInformationParser and MaterialApp.routerDelegate properties should be used.
Consequently, there is a need to implement 2 classes:
The RouteInformationParser is responsible to parse the URL to a custom configuration and the other way around;
The RouterDelegate is responsible to receive the new URL input by the user and notify the URL changes on page changes;
Here's the result for Flutter Web:
From the provided code, the changes are the following:
import 'package:path/path.dart' as path;
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:url_strategy/url_strategy.dart';
import 'package:provider/provider.dart';
const englishLocale = Locale('en', '');
const arabicLocale = Locale('ar', '');
class L10n {
static final all = [
englishLocale,
arabicLocale,
];
}
enum Language {
english,
arabic,
}
const languageByLanguageCode = {
'en': Language.english,
'ar': Language.arabic,
};
final languageCodeByLanguage = {
for (final entry in languageByLanguageCode.entries) entry.value: entry.key
};
Locale getSwitchToLanguage(Locale currentLocale) {
if (currentLocale == arabicLocale) {
return englishLocale;
}
return arabicLocale;
}
class LocaleProvider extends ChangeNotifier {
Locale _locale = englishLocale;
Locale get locale => _locale;
Language get language => languageByLanguageCode[_locale.languageCode]!;
String get languageCode => _locale.languageCode;
void setLocale(Locale locale) {
if (!L10n.all.contains(locale) || _locale == locale) return;
_locale = locale;
notifyListeners();
}
void clearLocale() {
_locale = englishLocale;
notifyListeners();
}
}
void switchLanguage(BuildContext context) {
final provider = Provider.of<LocaleProvider>(context, listen: false);
final siwtchToLocale = getSwitchToLanguage(provider.locale);
provider.setLocale(siwtchToLocale);
}
void main() {
setPathUrlStrategy();
final appRouterDelegate = AppRouterDelegate();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => LocaleProvider()),
ChangeNotifierProxyProvider<LocaleProvider, AppRouterDelegate>(
create: (_) => appRouterDelegate,
update: (_, localeProvider, appRouterDelegate) =>
appRouterDelegate!..updateLocaleProvider(localeProvider),
),
],
child: const MyApp(),
),
);
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _routeLanguageParser = AppRouteInformationParser();
#override
Widget build(BuildContext context) {
final localeProvider = context.watch<LocaleProvider>();
final appRouterDelegate = context.read<AppRouterDelegate>();
return MaterialApp.router(
routeInformationParser: _routeLanguageParser,
routerDelegate: appRouterDelegate,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: L10n.all,
locale: localeProvider.locale,
debugShowCheckedModeBanner: false,
);
}
}
class UnknownScreen extends StatelessWidget {
const UnknownScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("404 page"),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.appTitle),
),
body: Center(
child: Consumer2<LocaleProvider, AppRouterDelegate>(
builder: (_, localeProvider, appRouterDelegate, __) {
return Column(children: [
const Spacer(),
localeProvider.languageCode == 'ar'
? Text(
"This is the Arabic version : ${localeProvider.languageCode}")
: Text(
"This is the English version : ${localeProvider.languageCode}"),
const Spacer(),
ElevatedButton(
child: Text(
localeProvider.languageCode == 'en'
? "Switch to Arabic"
: "Switch to English",
),
onPressed: () {
switchLanguage(context);
},
),
const SizedBox(height: 12),
ElevatedButton(
child: const Text('Go to services'),
onPressed: () => appRouterDelegate.setPage(Page.services),
),
const Spacer()
]);
},
),
),
);
}
}
enum Page {
home,
services,
serviceA,
serviceB,
serviceC,
unknown,
}
const pathByPage = {
Page.home: '/',
Page.services: '/services',
Page.serviceA: '/services/a',
Page.serviceB: '/services/b',
Page.serviceC: '/services/c',
Page.unknown: '/404',
};
final pageByPath = {
for (final entry in pathByPage.entries) entry.value: entry.key
};
final pagesByPage = {
for (final page in Page.values)
page: (pathByPage[page]!.endsWith('/')
? pathByPage[page]!.substring(0, pathByPage[page]!.length - 1)
: pathByPage[page]!)
.split('/')
.fold<List<Page>>(
[],
(pages, pathSegment) {
final pagePath = path.join('/',
path.joinAll(pages.map((page) => pathByPage[page]!)), pathSegment);
final subpage = pageByPath[pagePath];
assert(subpage != null,
'path segment "$pathSegment" of subpage "$page" not defined');
pages.add(subpage!);
return pages;
},
),
};
class AppRouteConfiguration {
final Language language;
final List<Page> pages;
Page get page => pages.last;
AppRouteConfiguration(this.language, this.pages);
}
class AppRouteInformationParser
extends RouteInformationParser<AppRouteConfiguration> {
#override
Future<AppRouteConfiguration> parseRouteInformation(
RouteInformation routeInformation) {
final uri = Uri.parse(routeInformation.location ?? '/');
if (uri.pathSegments.isEmpty) {
return Future.value(AppRouteConfiguration(Language.english, [Page.home]));
}
final language = languageByLanguageCode[uri.pathSegments[0]];
final path = '/${uri.pathSegments.skip(1).join('/')}';
final pages = pagesByPage[pageByPath[path]] ?? [Page.home, Page.unknown];
return Future.value(
AppRouteConfiguration(language ?? Language.english, pages));
}
#override
RouteInformation? restoreRouteInformation(
AppRouteConfiguration configuration) {
return RouteInformation(location: getPath(configuration));
}
}
String getPath(AppRouteConfiguration configuration) =>
'/${languageCodeByLanguage[configuration.language]}${pathByPage[configuration.page]}';
class AppRouterDelegate extends RouterDelegate<AppRouteConfiguration>
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
LocaleProvider? _localeProvider;
var _configuration = AppRouteConfiguration(Language.english, [Page.home]);
Page get page => _configuration.page;
bool get canPop => _configuration.pages.length > 1;
void setPage(Page page) {
_configuration =
AppRouteConfiguration(_configuration.language, pagesByPage[page]!);
notifyListeners();
}
void popPage() {
if (canPop) {
final pages =
_configuration.pages.sublist(0, _configuration.pages.length - 1);
_configuration = AppRouteConfiguration(_configuration.language, pages);
notifyListeners();
}
}
void updateLocaleProvider(LocaleProvider localeProvider) {
_localeProvider = localeProvider;
_configuration =
AppRouteConfiguration(localeProvider.language, _configuration.pages);
notifyListeners();
}
#override
final navigatorKey = GlobalKey<NavigatorState>();
#override
AppRouteConfiguration? get currentConfiguration => _configuration;
#override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [
for (final page in _configuration.pages) ...[
if (_configuration.page == Page.home)
MaterialPage(
name: getPath(_configuration),
child: const HomePage(),
),
if (_configuration.page == Page.services)
MaterialPage(
name: getPath(_configuration),
child: ServicesScreen(),
),
if (_configuration.page == Page.serviceA)
MaterialPage(
name: getPath(_configuration),
child: ServiceScreen(page: _configuration.page),
),
if (_configuration.page == Page.serviceB)
MaterialPage(
name: getPath(_configuration),
child: ServiceScreen(page: _configuration.page),
),
if (_configuration.page == Page.serviceC)
MaterialPage(
name: getPath(_configuration),
child: ServiceScreen(page: _configuration.page),
),
if (_configuration.page == Page.unknown)
MaterialPage(
name: getPath(_configuration),
child: const UnknownScreen(),
),
]
],
onPopPage: (route, result) {
var didPop = route.didPop(result);
if (!didPop) {
return false;
}
didPop = canPop;
if (canPop) popPage();
return didPop;
},
);
}
#override
Future<void> setNewRoutePath(AppRouteConfiguration configuration) async {
_configuration = configuration;
final String language = languageCodeByLanguage[configuration.language]!;
_localeProvider?.setLocale(Locale(language, ''));
notifyListeners();
}
}
class ServiceInfo {
final Page page;
final String name;
const ServiceInfo(this.page, this.name);
}
final serviceNameByPage = {
Page.serviceA: 'Service A',
Page.serviceB: 'Service B',
Page.serviceC: 'Service C',
};
class ServicesScreen extends StatelessWidget {
ServicesScreen({Key? key}) : super(key: key);
final _services = [
for (final entry in serviceNameByPage.entries)
ServiceInfo(entry.key, entry.value)
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Services'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Expanded(
child: ListView.separated(
shrinkWrap: true,
separatorBuilder: (_, __) => const Divider(),
itemBuilder: (_, index) => ElevatedButton(
onPressed: () {
final appRouterDelegate =
context.read<AppRouterDelegate>();
appRouterDelegate.setPage(_services[index].page);
},
child: Text(_services[index].name)),
itemCount: _services.length,
),
),
],
),
),
);
}
}
class ServiceScreen extends StatelessWidget {
final Page page;
const ServiceScreen({Key? key, required this.page}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(serviceNameByPage[page]!),
),
);
}
}

Unable to refresh text in child pages using flutter_localizations,intl

I'm trying to change localized words through my whole app. But it only changes when I restart my App or when I do subscription on lang changes. Is there any native way to change localized word without any subscription and have the same result as I get when restart the app?
As an example by what do I mean under subscription:
//sets_bloc.dart
void subscribeLocale(AppLanguagesCubit cubit) {
cubit.stream.listen((event) {
add(const OnSetCardsDownloading()); // refresh page
});
}
// home_view.dart
MultiBlocProvider(
providers: [
BlocProvider<SetsBloc>(
create: (_) => SetsBloc()
..subscribeLocale(BlocProvider.of<AppLanguagesCubit>(context))
),
//..//
],
child: SetsView(),
// main.dart
/** */
Future<void> main() async {
/** */
await SentryFlutter.init(
(options) {
options.dsn =
/** */
},
appRunner: () => runApp(
MyApp(),
),
);
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
/** */
ExtendedSecureStorage get storage => DiManager.getIt<ExtendedSecureStorage>();
#override
void initState() {
/** */
}
#override
void dispose() {
/** */
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
final FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus &&
currentFocus.focusedChild != null) {
FocusManager.instance.primaryFocus?.unfocus();
}
},
child: MultiBlocProvider(
providers: [
//** */
],
child: BlocBuilder<AppLanguagesCubit, AppLanguagesState>(
builder: (_, AppLanguagesState state) {
return OverlaySupport(
child: MaterialApp(
builder: (context, widget) {
if (widget != null) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 1),
child: widget,
);
}
return widget ?? const SizedBox();
},
locale: state.status == ChangeLangStatus.success
? state.selectedLanguage?.locale
: const Locale('en'),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
debugShowCheckedModeBanner:
environmentConfig == EnvironmentConfig.dev ||
environmentConfig == EnvironmentConfig.qa,
title: getAppName(),
theme: ThemeData(
primarySwatch: Colors.orange,
unselectedWidgetColor:
LibraryColors.secondaryFont.withOpacity(0.5),
canvasColor: Colors.white,
primaryColorBrightness: Brightness.light,
),
home: BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) {
if (state is ***) {
loadLanguage(context);
}
},
builder: (_, AuthState state) {
if (state is ** ) {
return _spinner();
}
if (state is AuthorizationCompleted) {
if (**) {
return MultiBlocProvider(
providers:
/** */
child: CongratsView(),
);
}
if (Platform.isIOS) {
return const CupertinoScaffold(
body: HomeView(
initialTab: AppTab.home,
),
);
}
return const HomeView(
initialTab: AppTab.home,
);
}
if (state is AuthorizationError) {
return WelcomeView();
}
return WelcomeView();
},
),
),
);
}),
),
);
}
Future<void> loadLanguage(BuildContext context) async {
try {
final String? code = await storage.read(key: 'languageCode');
final Locale locale = getLocale(code, context);
await S.load(locale);
BlocProvider.of<AppLanguagesCubit>(context).init(locale);
if (code == null) {
await storage.write(
key: 'languageCode',
value: locale.languageCode,
);
}
CurrentLocaleDi()
..setLocale(locale)
..inject();
} catch (error) {
await ErrorReporter.writeLogs(error);
}
}
Locale getLocale(String? code, BuildContext context) {
if (code == null) {
return Localizations.localeOf(context);
}
return S.delegate.supportedLocales
.firstWhereOrNull((locale) => locale.languageCode == code) ??
S.delegate.supportedLocales.first;
}
Widget _spinner() {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.dark,
child: Scaffold(
body: Center(
child: Image.asset(
LibraryAssets.appLogo,
width: 121,
height: 121,
),
),
),
);
}
String getAppName() {
const String appName = '***';
switch (environmentConfig) {
case EnvironmentConfig.dev:
return '$appName dev';
case EnvironmentConfig.qa:
return '$appName qa';
case EnvironmentConfig.prod:
return appName;
}
}
}
//app_languages_cubit.dart
//** //
class AppLanguagesCubit extends Cubit<AppLanguagesState> {
AppLanguagesCubit()
: super(
const AppLanguagesState.loading(),
);
ExtendedSecureStorage get _storage =>
DiManager.getIt<ExtendedSecureStorage>();
final List<AppLanguage> appLanguages = List<AppLanguage>.from(
S.delegate.supportedLocales.map(
(locale) => AppLanguage(
locale: locale,
code: locale.languageCode,
lang: targetLangs[locale.languageCode] ?? '',
langInNativeForm: locale.scriptCode ?? '',
),
),
);
void init(Locale locale) {
final AppLanguage selectedLanguage = AppLanguage(
code: locale.languageCode,
lang: targetLangs[locale.languageCode] ?? '',
locale: locale,
);
Intl.defaultLocale = selectedLanguage.locale.languageCode;
emit(
AppLanguagesState.success(
status: ChangeLangStatus.success,
selectedLanguage: selectedLanguage,
),
);
}
Future<void> changeLanguage(AppLanguage selectedLanguage) async {
try {
emit(
const AppLanguagesState.loading(),
);
Intl.defaultLocale = selectedLanguage.locale.languageCode;
await S.load(selectedLanguage.locale);
await _storage.write(
key: 'languageCode',
value: selectedLanguage.locale.languageCode,
);
CurrentLocaleDi()
..setLocale(selectedLanguage.locale)
..inject();
emit(
AppLanguagesState.success(
status: ChangeLangStatus.success,
selectedLanguage: selectedLanguage,
),
);
} catch (error) {
await ErrorReporter.writeLogs(error);
emit(
const AppLanguagesState.failure(),
);
}
}
}
//ln10.dart
// GENERATED CODE - DO NOT MODIFY BY HAND
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'intl/messages_all.dart';
// **************************************************************************
// Generator: Flutter Intl IDE plugin
// Made by Localizely
// **************************************************************************
// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars
// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each
// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes
class S {
S();
static S? _current;
static S get current {
assert(_current != null,
'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.');
return _current!;
}
static const AppLocalizationDelegate delegate = AppLocalizationDelegate();
static Future<S> load(Locale locale) {
final name = (locale.countryCode?.isEmpty ?? false)
? locale.languageCode
: locale.toString();
final localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) {
Intl.defaultLocale = localeName;
final instance = S();
S._current = instance;
return instance;
});
}
static S of(BuildContext context) {
final instance = S.maybeOf(context);
assert(instance != null,
'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?');
return instance!;
}
static S? maybeOf(BuildContext context) {
return Localizations.of<S>(context, S);
}
/// `**`
String get sliderText1 {
return Intl.message(
'some text'
name: 'sliderText1',
desc: '',
args: [],
);
}
//** */
}
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
const AppLocalizationDelegate();
List<Locale> get supportedLocales {
return const <Locale>[
Locale.fromSubtags(languageCode: 'en'),
Locale.fromSubtags(languageCode: 'de'),
Locale.fromSubtags(languageCode: 'es'),
Locale.fromSubtags(languageCode: 'fr'),
Locale.fromSubtags(languageCode: 'ja'),
Locale.fromSubtags(languageCode: 'pl'),
Locale.fromSubtags(languageCode: 'pt'),
Locale.fromSubtags(languageCode: 'ru'),
Locale.fromSubtags(languageCode: 'tr'),
Locale.fromSubtags(languageCode: 'uk'),
Locale.fromSubtags(languageCode: 'zh'),
];
}
#override
bool isSupported(Locale locale) => _isSupported(locale);
#override
Future<S> load(Locale locale) => S.load(locale);
#override
bool shouldReload(AppLocalizationDelegate old) => false;
bool _isSupported(Locale locale) {
for (var supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale.languageCode) {
return true;
}
}
return false;
}
}
You can change the whole app without any subscription and set state. You have to use await WidgetsBinding.instance.performReassemble(); which is triggered the whole app with your updated value.This is the function

Flutter console won't show errors

Flutter no longer shows any error message, I'm using android studio, but even if I start the program in console messages still won't appear. For example if mapping an object goes wrong, there will be no error shown in console, I'll have to find it my self
This is my main file:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
ErrorWidget.builder = (FlutterErrorDetails details) => Container(
color: Colors.white,
child: const Center(
child: Text('Error'),
),
);
await SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp],
);
try {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
} catch (e) {}
setupLocator();
await SentryFlutter.init((SentryFlutterOptions options) {
options.reportPackages = false;
options.enableOutOfMemoryTracking = true;
options.enableAppLifecycleBreadcrumbs = false;
options.anrEnabled = true;
options.debug = true;
options.dsn ='';
options.tracesSampleRate = 1.0;
}, appRunner: () => runApp(MyApp(route: route,)));
}
class MyApp extends StatelessWidget {
final String route;
final bool isLoggedIn;
MyApp({
required this.route,
required this.isLoggedIn,
});
#override
Widget build(BuildContext context) {
return GlobalBlocProviders(
isLoggedIn: isLoggedIn,
child: BlocListener<NotificationsBloc, NotificationsState>(
listener: (context, state) {
final route = state.route;
if (route == null) return;
locator<NavigationService>().navigateTo(route);
},
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: TylerTheme,
builder: (BuildContext context, Widget? childWidget) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
alwaysUse24HourFormat: true,
),
child: childWidget!,
);
},
initialRoute: route,
navigatorObservers: [
StackedService.routeObserver,
SentryNavigatorObserver()
],
navigatorKey: StackedService.navigatorKey,
onGenerateRoute: StackedRouter().onGenerateRoute,
),
),
);
}
}
Would be perfect if you have any suggestions. Thank you!
You just have to print the error via print method.
try {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
} catch (e) {
print("Catch Exception is $e");
}

Show a dialog instead of return a widget using Either in Flutter/Dart

I'm a little stuck and can't figure out how the architectural flow should work in this use case. I know almost nothing about functional programming, I'm using this Either from dartz package, a functional programming package. Can someone help me with the following:
I want to show a popup dialog instead of a widget if there is an error. But the design of Either seems to not allow this somehow as this if logic requires a widget of course. Is there a better design which I could accomplish this with?
Learning error handling here
import 'dart:convert';
import 'dart:io';
import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:awesome_dialog/awesome_dialog.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.orange,
),
home: const HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueGrey,
body: Flex(
direction: Axis.horizontal,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Consumer(
builder: (ctx, ref, child) {
if (ref.watch(notifier).state == NotifierState.initial) {
return const Text('Press the button');
} else if (ref.watch(notifier).state == NotifierState.loading) {
return const CircularProgressIndicator();
} else {
return ref.read(notifier).post.fold(
(failure) {
showDialog( /// Error here, expects a widget
context: context,
barrierDismissible: true,
builder: (BuildContext context) => AlertDialog(
title: Text(failure.toString()),
),
);
},
(post) => Text(post.toString()),
);
}
},
),
Consumer(
builder: (ctx, ref, child) {
return ElevatedButton(
onPressed: () {
ref.read(notifier).getOnePost();
},
child: const Text('Get Post'));
},
),
],
),
),
],
),
);
}
}
class PostService {
final httpClient = FakeHttpClient();
Future<Post?> getOnePost() async {
try {
final responseBody = await httpClient.getResponseBody();
return Post.fromJson(responseBody);
} on SocketException {
throw Failure('No Internet connection 😑');
} on HttpException {
throw Failure("Couldn't find the post 😱");
} on FormatException {
throw Failure("Bad response format 👎");
}
}
}
class FakeHttpClient {
Future<String> getResponseBody() async {
await Future.delayed(const Duration(milliseconds: 500));
//! No Internet Connection
// throw SocketException('No Internet');
//! 404
throw HttpException('404');
//! Invalid JSON (throws FormatException)
// return 'abcd';
// return '{"userId":1,"id":1,"title":"nice title","body":"cool body"}';
}
}
enum NotifierState { initial, loading, loaded }
final notifier = ChangeNotifierProvider((ref) => PostChangeNotifier());
class PostChangeNotifier extends ChangeNotifier {
final _postService = PostService();
NotifierState _state = NotifierState.initial;
NotifierState get state => _state;
void _setState(NotifierState state) {
_state = state;
notifyListeners();
}
late Either<Failure, Post?> _post;
Either<Failure, Post?> get post => _post;
// Set post
void _setPost(Either<Failure, Post?> post) {
_post = post;
notifyListeners();
}
// Set one post
void getOnePost() async {
_setState(NotifierState.loading);
await Task(() => _postService.getOnePost())
.attempt()
.mapLeftToFailure()
.run()
.then((value) => _setPost(value as Either<Failure, Post?>));
_setState(NotifierState.loaded);
}
}
extension TaskX<T extends Either<Object, U>, U> on Task<T> {
Task<Either<Failure, U>> mapLeftToFailure() {
return map(
(either) => either.leftMap((obj) {
try {
return obj as Failure;
} catch (e) {
throw obj;
}
}),
);
}
}
class Post {
final int id;
final int userId;
final String title;
final String body;
Post({
required this.id,
required this.userId,
required this.title,
required this.body,
});
static Post? fromMap(Map<String, dynamic> map) {
return Post(
id: map['id'],
userId: map['userId'],
title: map['title'],
body: map['body'],
);
}
static Post? fromJson(String source) => fromMap(json.decode(source));
#override
String toString() {
return 'Post id: $id, userId: $userId, title: $title, body: $body';
}
}
class Failure {
// Use something like "int code;" if you want to translate error messages
final String message;
Failure(this.message);
#override
String toString() => message;
}
you don't call a function instead of widget, You should call class and initialize your dialog in initState
// call show dialog
(failure) {
ShowDialogScreen(failure: failure.toString());
},
// show dialog screen
class ShowDialogScreen extends StatefulWidget {
final String failure;
const ShowDialogScreen({Key key, this.failure}) : super(key: key);
#override
_ShowDialogScreenState createState() => _ShowDialogScreenState();
}
class _ShowDialogScreenState extends State<ShowDialogScreen> {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) => AlertDialog(
title: Text(widget.failure),
),
);
});
}
#override
Widget build(BuildContext context) {
return Container();
}
}