Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unhandled Exception: type '_Map<String, dynamic>' is not a subtype of type 'Map<String, Function>' of 'other' #798

Open
codelovercc opened this issue Feb 8, 2024 · 4 comments
Labels
P3 A lower priority bug or feature request package:intl_translation type-enhancement A request for a change that isn't a bug

Comments

@codelovercc
Copy link

codelovercc commented Feb 8, 2024

About intl_translation: ^0.19.0 and Flutter intl plugin (version 1.18.4-2022.2) on Android Studio.

% flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.16.9, on macOS 12.7.1 21G920 darwin-x64, locale zh-Hans-CN)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.1)
[✓] VS Code (version 1.85.1)
[✓] Connected device (2 available)
[✓] Network resources

• No issues found!

intl_translation: ^0.19.0 generate messages_en.dart below:

// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that provides messages for a en locale. All the
// messages from the main program should be duplicated here with the same
// function name.
// @dart=2.12
// Ignore issues from commonly used lints in this file.
// ignore_for_file:unnecessary_brace_in_string_interps
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
// ignore_for_file:unused_import, file_names

import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';

final messages = MessageLookup();

typedef String? MessageIfAbsent(
    String? messageStr, List<Object>? args);

class MessageLookup extends MessageLookupByLibrary {
  @override
  String get localeName => 'en';

  static m0(name) => "Hello ${name} from test";

  @override
  final Map<String, dynamic> messages = _notInlinedMessages(_notInlinedMessages);

  static Map<String, dynamic> _notInlinedMessages(_) => {
      'testArgsMessage': m0,
    'testMessage': MessageLookupByLibrary.simpleMessage('This is a test message for localization.')
  };
}

and Flutter intl plugin generate messages_en.dart below:

// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that provides messages for a en locale. All the
// messages from the main program should be duplicated here with the same
// function name.

// Ignore issues from commonly used lints in this file.
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes

import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';

final messages = new MessageLookup();

typedef String MessageIfAbsent(String messageStr, List<dynamic> args);

class MessageLookup extends MessageLookupByLibrary {
  String get localeName => 'en';

  static String m0(companyName, year) => "Copy right ${companyName} ${year}.";

  final messages = _notInlinedMessages(_notInlinedMessages);
  static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
        "appTitle":
            MessageLookupByLibrary.simpleMessage("member end"),
        "copyRight": m0,
        "home": MessageLookupByLibrary.simpleMessage("Home"),
        "loginBannerText":
            MessageLookupByLibrary.simpleMessage("Your dream starts from here"),
        "loginBannerTitle": MessageLookupByLibrary.simpleMessage("Marketing"),
        "mine": MessageLookupByLibrary.simpleMessage("Mine"),
        "poweredBy": MessageLookupByLibrary.simpleMessage("Powered by"),
        "testMessage": MessageLookupByLibrary.simpleMessage(
            "This is a test message for localization.")
      };
}

The difference between them is method _notInlinedMessages and field messages, intl_translation: ^0.19.0 defined messages type as Map<String, dynamic> and method _notInlinedMessages returns type as Map<String, dynamic>, but Flutter intl plugin defined method _notInlinedMessages returns type as Map<String, Function>, they should be in the same type, this cause the runtime exception when I call MessageLookupByLibrary.message.addAll to merge these two messages map to one map.

@codelovercc codelovercc added the type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) label Feb 8, 2024
@eernstg
Copy link
Member

eernstg commented Feb 8, 2024

Random observation, may or may not be relevant: Search for @dart, only one of the generated libraries has it.

@codelovercc
Copy link
Author

codelovercc commented Feb 8, 2024

I use intl and intl_translation in dart only package, use intl and intl_util in Flutter project, every generated messages_*.dart files have @dart=2.12 by intl_translation. In other word, intl_translation generate libraries with @dart=2.12, and intl_util does not.

intl_translation generated files samples:

messages_all.dart ↓

// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that looks up messages for specific locales by
// delegating to the appropriate library.
// @dart=2.12
export 'messages_all_locales.dart' show initializeMessages;

messages_all_locales.dart ↓

// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that looks up messages for specific locales by
// delegating to the appropriate library.
// @dart=2.12
// Ignore issues from commonly used lints in this file.
// ignore_for_file:implementation_imports, file_names
// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
// ignore_for_file:argument_type_not_assignable, invalid_assignment
// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
// ignore_for_file:comment_references

import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';
import 'package:intl/src/intl_helpers.dart';

import 'messages_en.dart' deferred as messages_en;
import 'messages_zh.dart' deferred as messages_zh;

typedef Future<dynamic> LibraryLoader();
Map<String, LibraryLoader> _deferredLibraries = {
  'en': messages_en.loadLibrary,
  'zh': messages_zh.loadLibrary,
};

MessageLookupByLibrary? _findExact(String localeName) {
  switch (localeName) {
    case 'en':
      return messages_en.messages;
    case 'zh':
      return messages_zh.messages;
    default:
      return null;
  }
}

/// User programs should call this before using [localeName] for messages.
Future<bool> initializeMessages(String? localeName) async {
  var availableLocale =
      Intl.verifiedLocale(localeName, (locale) => _deferredLibraries[locale] != null, onFailure: (_) => null);
  if (availableLocale == null) {
    return Future.value(false);
  }
  var lib = _deferredLibraries[availableLocale];
  await (lib == null ? Future.value(false) : lib());
  initializeInternalMessageLookup(() => CompositeMessageLookup());
  messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
  return Future.value(true);
}

bool _messagesExistFor(String locale) {
  try {
    return _findExact(locale) != null;
  } catch (e) {
    return false;
  }
}

MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) {
  var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
  if (actualLocale == null) return null;
  return _findExact(actualLocale);
}

messages_en.dart ↓

// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
// This is a library that provides messages for a en locale. All the
// messages from the main program should be duplicated here with the same
// function name.
// @dart=2.12
// Ignore issues from commonly used lints in this file.
// ignore_for_file:unnecessary_brace_in_string_interps
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
// ignore_for_file:unused_import, file_names

import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';

final messages = MessageLookup();

typedef String? MessageIfAbsent(String? messageStr, List<Object>? args);

class MessageLookup extends MessageLookupByLibrary {
  @override
  String get localeName => 'en';

  static m0(name) => "Hello ${name} from test";

  @override
  final Map<String, dynamic> messages = _notInlinedMessages(_notInlinedMessages);

  static Map<String, dynamic> _notInlinedMessages(_) => {
        'testArgsMessage': m0,
        'testMessage': MessageLookupByLibrary.simpleMessage('This is a test message for localization.')
      };
}

I solved this error by this:

class MultiCompositeMessageLookup extends CompositeMessageLookup {

  @override
  void addLocale(String localeName, Function findLocale) {
    final canonical = Intl.canonicalizedLocale(localeName);
    final newLocale = findLocale(canonical);
    if (newLocale != null) {
      final oldLocale = availableMessages[localeName];
      if (oldLocale != null && newLocale != oldLocale) {
        if (newLocale is! MessageLookupByLibrary) {
          throw Exception('Merge locale messages failed, type ${newLocale.runtimeType} is not supported.');
        }

        if (oldLocale.messages is Map<String, Function> && newLocale.messages is! Map<String, Function>) {
          final newMessages = newLocale.messages.map((key, value) => MapEntry(key, value as Function));
          oldLocale.messages.addAll(newMessages);
        } else {
          oldLocale.messages.addAll(newLocale.messages);
        }
        return;
      }
      super.addLocale(localeName, findLocale);
    }
  }
}

With this code it solved the type error issue, I'm not sure there is an other way to solve this error.

 if (oldLocale.messages is Map<String, Function> && newLocale.messages is! Map<String, Function>) {
          final newMessages = newLocale.messages.map((key, value) => MapEntry(key, value as Function));
          oldLocale.messages.addAll(newMessages);
        } else {
          oldLocale.messages.addAll(newLocale.messages);
        }

@mosuem
Copy link
Member

mosuem commented Feb 13, 2024

What is the "Flutter intl plugin"? Is this an issue on their side?

@codelovercc
Copy link
Author

I don't think so, intl_translation for dart only packages, intl_util for flutter application and packages.
You can see the difference of generated codes between them:

intl_translation: ^0.19.0 ↓

  ...
  @override
  final Map<String, dynamic> messages = _notInlinedMessages(_notInlinedMessages);

  static Map<String, dynamic> _notInlinedMessages(_) => {
  ...

Flutter intl plugin (version 1.18.4-2022.2) IntelliJ/Android Studio

  ...
  final messages = _notInlinedMessages(_notInlinedMessages);
  static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
  ...

Map<String, dynamic> and Map<String, Function>, that why MessageLookupByLibrary.messages.addAll() throws the exception.

@mosuem mosuem added type-enhancement A request for a change that isn't a bug P3 A lower priority bug or feature request and removed type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) labels Feb 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P3 A lower priority bug or feature request package:intl_translation type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

3 participants