The final beginner chapter moves from a single screen to the boundaries an application has to manage: assets, language, remote data, persistence, navigation, release identity, and evidence that it works. Its localization implementation is historical and has not been rebuilt.
Assets and localization
The source declared asset-based language files:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
flutter:
uses-material-design: true
assets:
- assets/i18n/
# en.json
{
"title": "Hello!"
}
# vi.json
{
"title": "Xin chào!"
}
The two language maps belong in separate files. A localization object and delegate loaded them:
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
class AppLocalizations {
final Locale locale;
AppLocalizations(this.locale);
// Helper method to keep the code in the widgets concise
// Localizations are accessed using an InheritedWidget "of" syntax
static AppLocalizations of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
// Static member to have a simple access to the delegate from the MaterialApp
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
Map<String, String> _localizedStrings;
Future<bool> load() async {
// Load the language JSON file from the "lang" folder
String jsonString =
await rootBundle.loadString('assets/i18n/${locale.languageCode}.json');
Map<String, dynamic> jsonMap = json.decode(jsonString);
_localizedStrings = jsonMap.map((key, value) {
return MapEntry(key, value.toString());
});
return true;
}
// This method will be called from every widget which needs a localized text
String translate(String key) {
return _localizedStrings[key];
}
}
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
// This delegate instance will never change (it doesn't even have fields!)
// It can provide a constant constructor.
const _AppLocalizationsDelegate();
@override
bool isSupported(Locale locale) {
// Include all of your supported language codes here
return ['en', 'vi'].contains(locale.languageCode);
}
@override
Future<AppLocalizations> load(Locale locale) async {
// AppLocalizations class is where the JSON loading actually runs
AppLocalizations localizations = new AppLocalizations(locale);
await localizations.load();
return localizations;
}
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
The application registered the delegate and read translations through it:
import 'package:flutter_localizations/flutter_localizations.dart';
import 'provider/app_localizations.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
supportedLocales: [
const Locale('en'),
const Locale('vi'),
],
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AppContent(),
);
}
}
AppLocalizations.of(context).translate('title')
The old global-locale access and manual selection example were:
import 'dart:ui' as ui;
Locale(ui.window.locale.languageCode, ui.window.locale.countryCode);
import 'dart:ui' as ui;
class MyApp extends StatelessWidget {
static final localeBloc = BlocDefault<Locale>();
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: localeBloc.stream,
builder: (context, snapshot) {
print(snapshot);
return MaterialApp(
locale: (snapshot.hasData
? snapshot.data
: Locale(ui.window.locale.languageCode,
ui.window.locale.countryCode)),
supportedLocales: [
const Locale('en'),
const Locale('vi'),
],
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AppContent(),
);
});
}
}
class AppContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(AppLocalizations.of(context).translate('title'))),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
child: Text('en'),
onPressed: () {
MyApp.localeBloc.push(Locale('en'));
},
),
RaisedButton(
child: Text('vi'),
onPressed: () {
MyApp.localeBloc.push(Locale('vi'));
},
),
],
),
));
}
}
Loading and parsing JSON is asynchronous. The source’s alternative moved the maps into Dart and used bflutter’s localization layer:
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
bflutter: ^0.1.8+2
# lib\provider\i18n\en.dart
const en = {
'title': 'Hello'
};
# lib\provider\i18n\vi.dart
const vi = {
'title': 'Xin chào'
};
import 'package:flutter/widgets.dart';
import 'package:bflutter/provider/base_localizations.dart';
import 'package:cxi_pos/provider/i18n/en.dart';
import 'package:cxi_pos/provider/i18n/vi.dart';
class AppLocalizations extends BaseLocalizations {
AppLocalizations(locale) : super(locale);
// Helper method to keep the code in the widgets concise
// Localizations are accessed using an InheritedWidget "of" syntax
static AppLocalizations of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
// Static member to have a simple access to the delegate from the MaterialApp
static LocalizationsDelegate<AppLocalizations> delegate =
AppLocalizationsDelegate(
['en', 'vi'], (locale) => AppLocalizations(locale));
@override
Map<String, String> load(Locale locale) {
switch (locale.languageCode) {
case 'vi':
return vi;
}
return en;
}
}
import 'dart:ui' as ui;
class MyApp extends StatelessWidget {
static final bloc = MainBloc.instance;
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.localeBloc.stream,
builder: (context, snapshot) {
return MaterialApp(
locale: (snapshot.hasData
? snapshot.data
: Locale(ui.window.locale.languageCode,
ui.window.locale.countryCode)),
supportedLocales: [
const Locale('en'),
const Locale('vi'),
],
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AppContent(),
);
});
}
}
class AppContent extends StatelessWidget {
static final bloc = MainBloc.instance;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(AppLocalizations.of(context).translate('title'))),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
child: Text('en'),
onPressed: () {
bloc.localeBloc.push(Locale('en'));
},
),
RaisedButton(
child: Text('vi'),
onPressed: () {
bloc.localeBloc.push(Locale('vi'));
},
),
],
),
));
}
}
These alternatives have different loading boundaries; neither is a general guarantee against startup stalls. Legacy ui.window and package APIs require version-specific review.
Remote data and persistence
Separate transport failures, response parsing, and presentation state. Cached images and stored application data also have different lifetimes. Flutter’s networking recipe and persistence recipes are useful starting references.
Dialogs, routes, lists, and animation
The original reading list covered Dialog/AlertDialog, route navigation, ListView, and animation. Each adds ownership questions: who closes a route, which state survives navigation, how list items are identified, and who disposes animation resources. The Flutter cookbook groups focused examples.
Version and environment
The source distinguished a semantic version from its build number and recorded these build overrides:
flutter build apk --build-name=1.0.3
flutter build apk --build-number=3
flutter build apk --build-name=1.0.3 --build-number=3
Its environment approach selected a Dart entry point:
flutter run -t lib/Main/main_staging.dart
# Update FLUTTER_TARGET at ios/Flutter/Generated.xcconfig
FLUTTER_TARGET=lib/Main/main_staging.dart
An environment filename is a configuration convention, not protection for secrets embedded in a client application.
Verify the actual application
The chapter closed with testing, profiling, binding observers, and memory tools. A build result, a functional interaction, and measured frame/memory behavior are separate kinds of evidence. This revision preserves the examples without claiming new application or performance results.