Localization is nice when its done right. Its somewhat complex, if you try to do it the alleged expert way of writing arbs twice like Google implemented it. And its even more messy if you do not know the trick to implement localizations outside of widgets due to usually needing that for models for example.
Or you could subscribe to my flutter substack and find out the right way.
First up, I am going to detail why we use MaterialApp widget as a base parent widget for our app and which also covers why we use it as a wrapper to unit test widgets. Then I will show you the trick to use on the app side to refer to localization outside of widgets. Then I will show another variation of the same trick for unit testing.
What Does The MaterialApp Widget Do
Now I am detailing and describing both MaterialApp Nagivation API 1.0 and MaterialApp.router Navigation API 2.0. In both cases, the app widget being constructed is the WidgetsApp widget.
The WidgetsApp wraps and sets up these widgets for app use:
-CheckedModeBanner
-DefaultTextStyle
-MediaQuery
-Localizations
-Title
-Navigator
-Overlay
-SemanticDebugger
The other things it sets up is some GlobalKeys such as:
-navigatorKey, nav 1.0
-scaffoldMessengerKey, nav 2.0 via the MaterialApp.router part before the call to WidgetApp.router
So we have two ways we can grab something to get the localization context in that we grab the navigator context to get the localization context to use in our calls. And we need to know both ways as in unit testing when we wrap with MaterialApp we are in nav 1.0 API as we are not using MaterialApp.router to unit test widgets.
So now let me show the set up.
Set Up To Call Localization Outside Of The Widget
First, we have the globals to set. One gets used in MaterialApp nav 1.0 situations and one used with MaterialApp.rotuer nav 2.0 situations:
import 'package:flutter/material.dart'; | |
final navigatorKey = GlobalKey<NavigatorState>(); | |
// navigatorKey is parameter which goes in the MaterialApp | |
// navigagtorKey parameter slot. Then we can do | |
// LocalizationClassSingeton.of(appContect)!.loremIpsum | |
// in places outside the widgets such as the Models. | |
// That also means that to test the models we use a MaterialApp | |
// wrapper in test to pumpwidget with child widget being | |
// anything to insert the navigatorKey parameter and | |
// localizations as then after the pump of widgets we | |
// have the appContext global filled and then can | |
// test the model stuff that has localizations. | |
// | |
BuildContext appContext = navigatorKey.currentState!.context; | |
// if using routes u need something different | |
// scaffoldMessengerKey is used in MaterialApp.routes | |
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>(); | |
// Then | |
BuildContext routeAppContext = scaffoldMessengerKey.currentContext!; | |
navigatorKey goes in the MaterialApp navigatorKey parameter slot and the localization reference outside a widget is then:
LocalizationClassSingeton.of(appContect)!.loremIpsum
And that set up is the same for unit testing as we use MaterialApp and not MaterialApp.router as the wrapper to unit test:
import 'package:flutter/material.dart'; | |
import 'package:flutter_localizations/flutter_localizations.dart'; | |
import 'package:flutter_test/flutter_test.dart'; | |
import 'package:localization_wo_context/generated/l10n.dart'; | |
import 'package:localization_wo_context/generated/l10n.dart'; | |
import 'package:localization_wo_context/generated/l10n.dart'; | |
late final appNavKey; | |
void main() { | |
setUpAll(() { | |
appNavKey = GlobalKey<NavigatorState>(); | |
}); | |
testWidgets('model test', (WidgetTester tester) async { | |
child: | |
MaterialApp( | |
navigatorKey: appNavKey, | |
localizationsDelegates: const [ | |
S.delegate, | |
GlobalMaterialLocalizations.delegate, | |
GlobalWidgetsLocalizations.delegate, | |
GlobalCupertinoLocalizations.delegate, | |
], | |
supportedLocales: const [ | |
Locale('en'), | |
// Locale('de'), | |
], | |
home: Container()); | |
// builds on the hook in the MaterialApp wrapper parameter | |
// navigatorKey so that we can grab the global ref once | |
// the MaterialApp wrapper is test pumped | |
BuildContext testAppContext = appNavKey.currentState!.context; | |
// So now we can test the Model that has localizations | |
//by using | |
// LocalizationClassSingeton.of(testAppppContect)!.loremIpsum | |
// | |
}); | |
} |
When using nav 2.0 API in form of MaterialApp.router keep testing the same and just change to using the scaffoldMessengerKey in the MaterialApp.router scaffoldMessengerkey slot. Then in your model where you need to do the localization use:
LocalizationClassSingeton.of(routeAppContect)!.loremIpsum
Conclusion
Nice and simple way to use localization outside of the widget no matter if you are using nav API 1.0 or nav API 2.0. I think sometimes the Google Developer Expert advocates do the shinny cool thing that gets hits rather than the core things that add to your route to understanding.