3

Below code has no compiler warnings or errors. Running it results in:

lib/main.dart:26:51: Error: The argument type 'Locale?' can't be assigned to the parameter type 'Locale' because 'Locale?' is nullable and 'Locale' isn't.
 - 'Locale' is from 'dart:ui'.
    thisAppsLocaleNotifier = ValueNotifier(window.locale);
                                                  ^
lib/main.dart:27:43: Error: Property 'languageCode' cannot be accessed on 'Locale?' because it is potentially null.
 - 'Locale' is from 'dart:ui'.
Try accessing using ?. instead.
    Localization.langCode = window.locale.languageCode;
                                          ^^^^^^^^^^^^
Failed to compile application.

Neither window nor locale could be null. No parameters here can be null. The error message is quite annoying because trying

thisAppsLocaleNotifier = ValueNotifier(window.locale ?? Locale('en'));

Results in a compiler warning:

The left operand can't be null, so the right operand is never executed.  Try removing the operator and the right operand.

This is the code:

import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

import 'main.i18n.dart';

/// Supported locales. First entry `en` is default.
const List<Locale> supportedLocales = [
  Locale('en', 'US'),
  Locale('de', 'DE'),
  Locale('es', 'ES'),
];

late ValueNotifier<Locale> thisAppsLocaleNotifier;

/// Entry point for example application
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  MyApp() {
    thisAppsLocaleNotifier = ValueNotifier(window.locale);
    Localization.langCode = window.locale.languageCode;
  }

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
      valueListenable: thisAppsLocaleNotifier,
      builder: (BuildContext context, Locale thisAppsLocale, Widget? child) =>
          MaterialApp(
        home: MyHomePage(),
        locale: thisAppsLocale,
        localizationsDelegates: [
          GlobalMaterialLocalizations.delegate,
          GlobalWidgetsLocalizations.delegate,
          GlobalCupertinoLocalizations.delegate,
        ],
        supportedLocales: supportedLocales,
        theme: ThemeData(
          primarySwatch: Colors.orange,
        ),
        title: 'input_country',
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    print('_MyHomePageState build');
    return Scaffold(
      appBar: AppBar(
        title: Text('App Title'.i18n),
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            Text('thisAppsLocale = ${thisAppsLocaleNotifier.value} / langCode ='
                ' ${Localization.langCode}'),
            ButtonBar(
              alignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () => setLocale(Locale('de')),
                  child: Text('de'),
                ),
                ElevatedButton(
                  onPressed: () => setLocale(Locale('en')),
                  child: Text('en'),
                ),
                ElevatedButton(
                  onPressed: () => setLocale(Locale('es')),
                  child: Text('es'),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }

  void setLocale(Locale newLocale) {
    Localization.langCode = newLocale.languageCode;
    thisAppsLocaleNotifier.value = newLocale;
  }
}

And this is pubspec.yaml

name: qq
description: A new Flutter application.
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

What is required to run the code in null-safety mode?

This is the localization class main.i18n.dart:

extension Localization on String {
  static String langCode = 'en';

  String get i18n => localize(this, _t);

  static final Map<String, Map<String, String>> _t = {
    'All': {
      'de': 'Alle',
      'es': 'Todas',
    },
    'Example': {
      'de': 'Beispiel',
      'es': 'Ejemplo',
    },
    'Languages': {
      'de': 'Sprachen',
      'es': 'Idiomas',
    },
    'Selectables': {
      'de': 'Einträge',
      'es': 'Entradas',
    },
  };

  String localize(String english, Map<String, Map<String, String>> t10ns) {
    if (langCode != 'en') {
      Map<String, String>? _t10ns = t10ns[english];
      if (_t10ns == null) {
        print('No translations found for "$english"');
      } else {
        String? translated = _t10ns[langCode];
        if (translated == null) {
          print('Translation to language "$langCode" missing for "$english"');
        } else {
          return translated;
        }
      }
    }
    return english;
  }
}

Output of Flutter doctor:

Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 2.0.3, on Microsoft Windows [Version 10.0.19042.867], locale de-DE)
[√] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
[√] Chrome - develop for the web
[√] Android Studio (version 4.1.0)
[√] Connected device (3 available)

• No issues found!
Jemolah
  • 1,962
  • 3
  • 24
  • 41
  • I added a generic explanation of null safety without running your code in the answer below. I tried to run your code but was obviously missing the file `main.i18n.dart`. Also I cannot find the definition of the Localization class. – Patrick O'Hara Mar 29 '21 at 17:27

2 Answers2

2

It looks like it was a bug with flutter web. It should be fixed in one of the next releases

https://github.com/flutter/engine/pull/24922


What you can do for now to make it work on web and avoid warnings on desktop or mobiles:

Create 3 files:

  • shared.dart
  • mobile.dart
  • web.dart
// shared.dart
export 'web.dart' if (dart.library.io) 'mobile.dart';
// mobile.dart
import 'dart:ui';

Locale getLocale() => window.locale;
// web.dart
import 'dart:ui';

// ignore: unnecessary_non_null_assertion
Locale getLocale() => window.locale!;

Then in your code, only import shared.dart and get the Locale from there:

main.dart
import 'shared.dart';

final Locale locale = getLocale();
Valentin Vignal
  • 6,151
  • 2
  • 33
  • 73
0

There is a comprehensive explanation of null safety here: Null safety in Flutter. It includes a link to the migration guide that will help you migrate your code.

Note that in the pubspec:

environment:
  sdk: ">=2.12.0 <3.0.0"

specifying the sdk version >=2.12.0 enables null safety, with the consequent checks. Specifying any earlier version will disable null safety.

In order to fix your code for null safety, you need to check for non-null values where they are not permitted - the error messages generally point this out. So the errors:

lib/main.dart:26:51: Error: The argument type 'Locale?' can't be assigned to the parameter type 'Locale' because 'Locale?' is nullable and 'Locale' isn't.
 - 'Locale' is from 'dart:ui'.
    thisAppsLocaleNotifier = ValueNotifier(window.locale);

lib/main.dart:27:43: Error: Property 'languageCode' cannot be accessed on 'Locale?' because it is potentially null.
 - 'Locale' is from 'dart:ui'.
Try accessing using ?. instead.
    Localization.langCode = window.locale.languageCode;

can be fixed by asserting that the parameter is not null using window.locale!.

Your frustration with window.locale ?? ... was caused because this does not check for window being null.

Patrick O'Hara
  • 1,999
  • 1
  • 14
  • 18
  • OK. I changed first line to `thisAppsLocaleNotifier = ValueNotifier(window.locale!);` This results in a warning that the parameter cannot be null. It also cannot solve next line in any combination like 'Localization.langCode = window?.locale?.languageCode ?? 'en';` This does not work either. Maybe this problem does not have a solution due to a compiler bug? Running "dart migrate" results in: "Nothing to do." – Jemolah Mar 29 '21 at 17:28
  • I tried to run your code but was obviously missing the file `main.i18n.dart`. Also I cannot find the definition of the `Localization` class – Patrick O'Hara Mar 29 '21 at 17:33
  • 1
    I commented out `main.i18.dart` and the missing `Localization` class, and used `window.locale!`. The compile gives warnings about the `!` being ignored, but the code **runs OK**. I went to Github ro research the spurious warning, and see you raised https://github.com/flutter/flutter/issues/79351 :) – Patrick O'Hara Mar 31 '21 at 14:14
  • 1
    The answer is slightly amiss. The main problem seems to be not null safety in itself but some transient problem with Flutter Web. The error message appears in a fully null-safe app that runs perfectly on Android and iOS. The same source gives the error in Web now. – Gábor Apr 01 '21 at 11:59