Created
September 23, 2020 01:10
-
-
Save rydmike/ba479df4c50457bcdadac6691bb0f84e to your computer and use it in GitHub Desktop.
Flutter and Dart Web Release Mode Minified Runtime Type Issue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
void main() { | |
runApp(MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
debugShowCheckedModeBanner: false, | |
title: 'Flutter Issue Demo', | |
theme: ThemeData( | |
primarySwatch: Colors.indigo, | |
visualDensity: VisualDensity.adaptivePlatformDensity, | |
), | |
home: IssueDemo(title: 'Web Minified RunTime Type Issue'), | |
); | |
} | |
} | |
enum TestEnum { testValue1, testValue2 } | |
class IssueDemo extends StatelessWidget { | |
IssueDemo({Key key, this.title}) : super(key: key); | |
final String title; | |
@override | |
Widget build(BuildContext context) { | |
TestEnum value1 = TestEnum.testValue1; | |
final List<String> splitEnum = value1.toString().split('.'); | |
return Scaffold( | |
appBar: AppBar( | |
elevation: 0, | |
title: Text(title), | |
), | |
body: Center( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
Text('enum TestEnum { testValue1, testValue2 }\n'), | |
Text('value1 = $value1'), | |
Text('splitEnum = $splitEnum \n'), | |
Text('splitEnum.length = ${splitEnum.length} (Expected: 2)\n'), | |
Text('splitEnum[0] = ${splitEnum[0]} (Expected: TestEnum)'), | |
Text('value1.runtimeType.toString() = ' | |
'${value1.runtimeType.toString()} (Expected: TestEnum)'), | |
// | |
// If you uncomment the Text() widget further below the build will fail due to | |
// the unexpected and unhandled exceptions it throws in a Web release build. | |
// It throws an exception if the String from the part from the used enum | |
// before the "." does not match the runtime type of the same enum. | |
// | |
// In a release mode Web build that | |
// type has been minified to something else, so they will never match | |
// and the exception will always be thrown. In Web debug and profile mode | |
// builds this minification does not happen and the code runs OK, it used | |
// run OK on older version of Web Beta too, but apparently this | |
// minification is a new optimization. | |
// | |
// (On devices and desktop the code also works OK in release mode.) | |
// | |
// Uncomment the next widget to break the build in Web release mode. | |
// | |
// Text( | |
// 'Enum to string: ${EnumToString.convertToString(TestEnum.testValue1)}'), | |
], | |
), | |
), | |
); | |
} | |
} | |
// The code below is 1:1 copy of the package code from the enum_to_string | |
// package on pub.dev, version 1.0.11, that lead to the discovery of this issue. | |
// | |
// The debugPrints are my additions to be able to detect where the code breaks | |
// in the Web --release build. | |
// | |
// https://pub.dev/packages/enum_to_string | |
// The package is made by Ryan Knell. MIT License. | |
class NotAnEnumException implements Exception { | |
dynamic value; | |
NotAnEnumException(this.value); | |
@override | |
String toString() => | |
'${value.toString()} of type ${value.runtimeType.toString()} is not an enum item.'; | |
} | |
class EnumToString { | |
static bool _isEnumItem(enumItem) { | |
final splitted_enum = enumItem.toString().split('.'); | |
debugPrint('The issue is here in release build'); | |
debugPrint('splitted_enum.length=${splitted_enum.length} (Expected: 2)'); | |
debugPrint('splitted_enum[0]=${splitted_enum[0]} (Expected: TestEnum)'); | |
debugPrint('enumItem.runtimeType.toString() ' | |
'${enumItem.runtimeType.toString()} (Expected: TestEnum)'); | |
return splitted_enum.length > 1 && | |
splitted_enum[0] == enumItem.runtimeType.toString(); | |
} | |
/// Convert an enum to a string | |
/// | |
/// Pass in the enum value, so TestEnum.valueOne into [enumItem] | |
/// It will return the striped off value so "valueOne". | |
/// | |
/// If you pass in the option [camelCase]=true it will convert it to words | |
/// So TestEnum.valueOne will become Value One | |
static String convertToString(enumItem, {bool camelCase = false}) { | |
if (enumItem == null) return null; | |
debugPrint('Place 01'); | |
if (!_isEnumItem(enumItem)) { | |
debugPrint('Place 02'); | |
throw NotAnEnumException(enumItem); | |
} | |
debugPrint('Place 03'); | |
final _tmp = enumItem.toString().split('.')[1]; | |
debugPrint('Place 04'); | |
return !camelCase ? _tmp : camelCaseToWords(_tmp); | |
} | |
@Deprecated( | |
'Renamed function to EnumToString.convertToString to make it clearer') | |
static String parse(enumItem, {bool camelCase = false}) => | |
convertToString(enumItem, camelCase: camelCase); | |
/// An alias for parse(item, camelCase: true) | |
/// | |
@Deprecated( | |
'Deprecated in favour of using convertToString(item, camelCase: true)') | |
static String parseCamelCase(enumItem) { | |
return EnumToString.convertToString(enumItem, camelCase: true); | |
} | |
/// Given a string, find and return its matching enum value | |
/// | |
/// You need to pass in the values of the enum object. So TestEnum.values | |
/// in the first argument. The matching value is the second argument. | |
/// | |
/// Example final result = EnumToString.fromString(TestEnum.values, "valueOne") | |
/// result == TestEnum.valueOne //true | |
/// | |
static T fromString<T>(List<T> enumValues, String value) { | |
if (value == null || enumValues == null) return null; | |
return enumValues.singleWhere( | |
(enumItem) => | |
EnumToString.convertToString(enumItem)?.toLowerCase() == | |
value?.toLowerCase(), | |
orElse: () => null); | |
} | |
/// Get the index of the enum value | |
/// | |
/// Pass in the enum values to argument one, so TestEnum.values | |
/// Pass in the matching string to argument 2, so "valueOne" | |
/// | |
/// Eg. final index = EnumToString.indexOf(TestEnum.values, "valueOne") | |
/// index == 0 //true | |
static int indexOf<T>(List<T> enumValues, String value) => | |
enumValues.indexOf(fromString<T>(enumValues, value)); | |
static List<String> toList<T>(List<T> enumValues, {bool camelCase = false}) { | |
if (enumValues == null) return null; | |
final _enumList = enumValues | |
.map((t) => !camelCase | |
? EnumToString.convertToString(t) | |
: EnumToString.convertToString(t, camelCase: true)) | |
.toList(); | |
return _enumList; | |
} | |
/// Get a list of enums given a list of strings. | |
/// Basically just EnumToString.fromString, but using lists | |
/// | |
/// Returns null for items that are not found. | |
/// | |
/// As with fromString it is not case sensitive | |
/// | |
/// Eg. EnumToString.fromList(TestEnum.values, ["valueOne", "value2"] | |
static List<T> fromList<T>(List<T> enumValues, List valueList) { | |
if (valueList == null || enumValues == null) return null; | |
return List<T>.from(valueList | |
.map((item) => item == null ? null : fromString(enumValues, item))); | |
} | |
} | |
// adapted from https://github.com/hexrcs/quartet_dart/blob/master/lib/src/split/words.dart | |
const String UPPERCASE = | |
r'A-Z\xC0-\xD6\xD8-\xDE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178\u0179\u017B\u017D\u0181\u0182\u0184\u0186\u0187\u0189-\u018B\u018E-\u0191\u0193\u0194\u0196-\u0198\u019C\u019D\u019F\u01A0\u01A2\u01A4\u01A6\u01A7\u01A9\u01AC\u01AE\u01AF\u01B1-\u01B3\u01B5\u01B7\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A\u023B\u023D\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1C90-\u1CBA\u1CBD-\u1CBF\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E\u213F\u2145\u2160-\u216F\u2183\u24B6-\u24CF\u2C00-\u2C2E\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AE\uA7B0-\uA7B4\uA7B6\uA7B8\uFF21-\uFF3A'; | |
const String DIACRITICAL_MARK = | |
r'\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F'; | |
const String LOWERCASE = | |
r'a-z\xAA\xB5\xBA\xDF-\xF6\xF8-\xFF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02B8\u02C0\u02C1\u02E0-\u02E4\u0345\u0371\u0373\u0377\u037A-\u037D\u0390\u03AC-\u03CE\u03D0\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1DBF\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6\u1FC7\u1FD0-\u1FD3\u1FD6\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6\u1FF7\u2071\u207F\u2090-\u209C\u210A\u210E\u210F\u2113\u212F\u2134\u2139\u213C\u213D\u2146-\u2149\u214E\u2170-\u217F\u2184\u24D0-\u24E9\u2C30-\u2C5E\u2C61\u2C65\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73\u2C74\u2C76-\u2C7D\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B-\uA69D\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7F8-\uA7FA\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A'; | |
const String DIGIT = r'\d'; | |
const String DINGBAT_BLOCK = r'\u2700-\u27BF'; | |
const String NON_CHAR = r'\x00-\x2F\x3A-\x40\x5B-\x60\x7b-\xBF\xD7\xF7'; | |
const String GENERAL_PUNCTUATION = r'\u2000-\u206F'; | |
const String WHITESPACE = | |
r'\t-\r \x85\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000'; | |
const String WORD = '(?:[' + | |
UPPERCASE + | |
'][' + | |
DIACRITICAL_MARK + | |
']*)?(?:[' + | |
LOWERCASE + | |
'][' + | |
DIACRITICAL_MARK + | |
']*)+|(?:[' + | |
UPPERCASE + | |
'][' + | |
DIACRITICAL_MARK + | |
']*)+(?![' + | |
LOWERCASE + | |
'])|[' + | |
DIGIT + | |
']+|[' + | |
DINGBAT_BLOCK + | |
']|[^' + | |
NON_CHAR + | |
GENERAL_PUNCTUATION + | |
WHITESPACE + | |
']+'; | |
String camelCaseToWords(String subject, | |
[Pattern customPattern = defaultPattern]) { | |
if (subject is! String || subject.isEmpty) { | |
return ''; | |
} | |
RegExp pattern; | |
if (customPattern is String) { | |
pattern = RegExp(customPattern); | |
} else if (customPattern is RegExp) { | |
pattern = customPattern; | |
} | |
final words = pattern.allMatches(subject).map((m) => m.group(0)).toList(); | |
words[0] = words[0][0].toUpperCase() + words[0].substring(1); | |
for (var i = 1; i < words.length; i++) { | |
words[i] = words[i].toLowerCase(); | |
} | |
return words.join(' '); | |
} | |
const String defaultPattern = WORD; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Sample code for Flutter issue:
flutter/flutter#66236