My flutter app has translations to several languages but that was working fine until I introduced routes to my app for navigation between screens. Now I always get the issue that the texts I'm trying to set are null and it seems to me that the reason for this is that the texts or translations are not initiated yet when the app tries to access them. It doesn't seem like I can use the 'late' modifier here, and despite having googled quite a bit on this issue and trying to adapt many of the solutions found I can't get my app to work any more.

I've only been working with flutter for a few weeks so it's still fairly new to me, and I guess part of the reason I have problems with this is that maybe I don't fully understand the way flutter handles things yet?

Anyway, here's the code for my main:

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My Flutter App',
      localizationsDelegates: [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        Locale('en', ''), // English
        Locale('es', ''), // Spanish
        Locale('de', ''), // German
        Locale('fr', ''), // Frensh
        Locale('nb', ''), // Norweigian
        Locale('sv', '') //  Swedish
      ],
      theme: MyTheme.lightTheme,
      // theme: ThemeData(
      //   primarySwatch: Colors.blue,
      // ),
      home: const MyHomePage(title: "My home page"),
    );
  }
}

MyHomePage:

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
   
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;
  static const TextStyle optionStyle =
  TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
  static const List<Widget> _widgetOptions = <Widget>[
    NewLogPage(title: 'New Log'),  //these will need translations eventually too
    ReadLogPage(title: 'Read Log'),//these will need translations eventually too
    SettingsPage(title: "Settings"),//these will need translations eventually too
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: _widgetOptions.elementAt(_selectedIndex),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: ImageIcon(AssetImage("bottomnav_new_log_inactive.png")),
            activeIcon: ImageIcon(AssetImage("bottomnav_new_log_active.png")),
            label:'New Log'
          ),
          BottomNavigationBarItem(
            icon: ImageIcon(AssetImage("bottomnav_read_log_inactive.png")),
            activeIcon: ImageIcon(AssetImage("bottomnav_read_log_active.png")),
            label: 'Read Tracer',
            backgroundColor: Colors.green,
          ),
          BottomNavigationBarItem(
            icon: ImageIcon(AssetImage("bottomnav_settings_inactive.png")),
            activeIcon: ImageIcon(AssetImage("bottomnav_settings_active.png")),
            label: 'Settings',
            backgroundColor: Colors.purple,
          ),
        ],
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.amber[800],
        showSelectedLabels: false,
        showUnselectedLabels: false,
        onTap: _onItemTapped,
      ),
    );
  }
}

The new log page:

class NewLogPage extends StatefulWidget {
  const NewLogPage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<NewLogPage> createState() => _NewLogPageState();
}

class _NewLogPageState extends State<NewLogPage> {


  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes:{
        '/' : (context) => NewLogSelector(),
        '/newLogScanner' : (context) => NewLogScanner(),
      },
      theme: MyTheme.lightTheme,
    );
  }
}

And finally my selector widget

class NewLogSelector extends StatefulWidget {
  const NewLogSelector({Key? key}) : super(key: key);


  @override
  State<NewLogSelector> createState() => _NewLogSelectorState();
}

class _NewLogSelectorState extends State<NewLogSelector> {


  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
        length: 2,
        child: Scaffold(
          appBar: AppBar(
            toolbarHeight: MyDimensions.appBarHeight,
            flexibleSpace: FlexibleSpaceBar(
              centerTitle: false,
              titlePadding: EdgeInsetsDirectional.only(
                start: MyDimensions.startMargin,
                bottom: 0.0,
                top: MyDimensions.topMargin,
              ),
              title: Column(
                children: [
                  Text(
                    AppLocalizations.of(context)!.title_new_log, // <= not working, null
                    style: MyTextStyle.lightSubHeader,
                  ),
                  SizedBox(height: MyDimensions.titlePadding),
                  Text(
                    // AppLocalizations.of(context)!.instruction_select_product, <= not working, null
                    "text", <=working just fine
                    style: MyTextStyle.lightHeader,
                  ),
                ],
                crossAxisAlignment: CrossAxisAlignment.start,
              ),
            ),
            actions: <Widget> [
              IconButton(
                icon: const Icon(Icons.refresh),
                color: Colors.white,

                onPressed: (){
                  setState(() {

                  });
                },
              ),
            ],
            bottom: TabBar(
              tabs: [
                Tab(text: AppLocalizations.of(context)!.title_general_tab), // <= not working, null
                Tab(text: AppLocalizations.of(context)!.title_specific_tab), // <= not working, null
              ],
            ),
          ),
          body: TabBarView(
            children: [
              ListView.builder(
                itemCount: 3,
                itemBuilder: (context,index){
                  return Card(
                    child: ListTile(
                      title: Text("General ${index+1}"),
                      onTap: () {
                        Navigator.pushNamed(context, '/newLogScanner');
                      },
                    ),
                  );
                },
              ),
              ListView.builder(
                itemCount: 4,
                itemBuilder: (context,index){
                  return Card(
                    child: ListTile(
                      title: Text("Specific ${index+1}"),
                      onTap: () {
                        Navigator.pushNamed(context, '/newLogScanner');
                      },
                    ),
                  );
                },
              ),
            ],
          ),
        ),
      );
  }

So Any suggestions regarding how I can get around this issue?

EDIT: My AppLocalizations class as requested (although abreviated since it's fairly long):

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart' as intl;

import 'app_localizations_de.dart';
import 'app_localizations_en.dart';
import 'app_localizations_es.dart';
import 'app_localizations_fr.dart';
import 'app_localizations_nb.dart';
import 'app_localizations_sv.dart';
abstract class AppLocalizations {
  AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());

  final String localeName;

  static AppLocalizations? of(BuildContext context) {
    return Localizations.of<AppLocalizations>(context, AppLocalizations);
  }

  static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();

  /// A list of this localizations delegate along with the default localizations
  /// delegates.
  ///
  /// Returns a list of localizations delegates containing this delegate along with
  /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
  /// and GlobalWidgetsLocalizations.delegate.
  ///
  /// Additional delegates can be added by appending to this list in
  /// MaterialApp. This list does not have to be used at all if a custom list
  /// of delegates is preferred or required.
  static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
    delegate,
    GlobalMaterialLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
  ];

  /// A list of this localizations delegate's supported locales.
  static const List<Locale> supportedLocales = <Locale>[
    Locale('de'),
    Locale('en'),
    Locale('es'),
    Locale('fr'),
    Locale('nb'),
    Locale('sv')
  ];

// translated phrases here, but removed since it is too long

}

class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
  const _AppLocalizationsDelegate();

  @override
  Future<AppLocalizations> load(Locale locale) {
    return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
  }

  @override
  bool isSupported(Locale locale) => <String>['de', 'en', 'es', 'fr', 'nb', 'sv'].contains(locale.languageCode);

  @override
  bool shouldReload(_AppLocalizationsDelegate old) => false;
}

AppLocalizations lookupAppLocalizations(Locale locale) {


  // Lookup logic when only language code is specified.
  switch (locale.languageCode) {
    case 'de': return AppLocalizationsDe();
    case 'en': return AppLocalizationsEn();
    case 'es': return AppLocalizationsEs();
    case 'fr': return AppLocalizationsFr();
    case 'nb': return AppLocalizationsNb();
    case 'sv': return AppLocalizationsSv();
  }

  throw FlutterError(
    'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
    'an issue with the localizations generation tool. Please file an issue '
    'on GitHub with a reproducible sample app and the gen-l10n configuration '
    'that was used.'
  );
}
1

There are 1 answers

1
Jon Mountjoy On

Your MyHomePage does this:

static const List<Widget> _widgetOptions = [...construct widgets...]

That's basically asking the programming language to construct that list, once, as part of class construction - which may well happen before your program starts running.

But in truth, that shouldn't be a static const list, right? Because you only know at runtime what the localisation will be. So you want to construct that list dynamically, not statically.

So I wonder what will happen if, in _MyHomePageState, you changed it to:

late List<Widget> _widgetOptions;

and then introduced this:

@override
  void initState() {
    super.initState();
    _widgetOptions = [
      NewLogPage(title: 'New Log'),  
      ReadLogPage(title: 'Read Log'),
      SettingsPage(title: "Settings"),
    ];
  }

So now I'm constructing that list at runtime, when the widget is being constructed. I think it's something subtle like that.....