flutter cupertino 复制粘贴弹窗报错的问题
文章目录
1NosuchMethodError: The getter 'pasterButtonLabel' was called on null.
2Receiver: null
3Tried calling: pasteButtonLabel
在最近 app store 提交审核时被拒了,然后得到了一个这样的截图
在 flutter 中可能会会出现各种问题,因为之前遇到过这个问题,但是那是我另一个应用,这个忘了设置了
我快速搞了一下,重新提交了审核
解决方式
1import 'package:flutter/cupertino.dart';
2import 'package:flutter/foundation.dart';
3import 'package:flutter/material.dart';
4import 'package:flutter_localizations/flutter_localizations.dart';
5
6class ChineseCupertinoLocalizations implements CupertinoLocalizations {
7 final materialDelegate = GlobalMaterialLocalizations.delegate;
8 final widgetsDelegate = GlobalWidgetsLocalizations.delegate;
9 final local = const Locale('zh');
10
11 MaterialLocalizations ml;
12
13 Future init() async {
14 ml = await materialDelegate.load(local);
15 print(ml.pasteButtonLabel);
16 }
17
18 @override
19 String get alertDialogLabel => ml.alertDialogLabel;
20
21 @override
22 String get anteMeridiemAbbreviation => ml.anteMeridiemAbbreviation;
23
24 @override
25 String get copyButtonLabel => ml.copyButtonLabel;
26
27 @override
28 String get cutButtonLabel => ml.cutButtonLabel;
29
30 @override
31 DatePickerDateOrder get datePickerDateOrder => DatePickerDateOrder.mdy;
32
33 @override
34 DatePickerDateTimeOrder get datePickerDateTimeOrder =>
35 DatePickerDateTimeOrder.date_time_dayPeriod;
36
37 @override
38 String datePickerDayOfMonth(int dayIndex) {
39 return dayIndex.toString();
40 }
41
42 @override
43 String datePickerHour(int hour) {
44 return hour.toString().padLeft(2, "0");
45 }
46
47 @override
48 String datePickerHourSemanticsLabel(int hour) {
49 return "$hour" + "时";
50 }
51
52 @override
53 String datePickerMediumDate(DateTime date) {
54 return ml.formatMediumDate(date);
55 }
56
57 @override
58 String datePickerMinute(int minute) {
59 return minute.toString().padLeft(2, '0');
60 }
61
62 @override
63 String datePickerMinuteSemanticsLabel(int minute) {
64 return "$minute" + "分";
65 }
66
67 @override
68 String datePickerMonth(int monthIndex) {
69 return "$monthIndex";
70 }
71
72 @override
73 String datePickerYear(int yearIndex) {
74 return yearIndex.toString();
75 }
76
77 @override
78 String get pasteButtonLabel => ml.pasteButtonLabel;
79
80 @override
81 String get postMeridiemAbbreviation => ml.postMeridiemAbbreviation;
82
83 @override
84 String get selectAllButtonLabel => ml.selectAllButtonLabel;
85
86 @override
87 String timerPickerHour(int hour) {
88 return hour.toString().padLeft(2, "0");
89 }
90
91 @override
92 String timerPickerHourLabel(int hour) {
93 return "$hour".toString().padLeft(2, "0") + "时";
94 }
95
96 @override
97 String timerPickerMinute(int minute) {
98 return minute.toString().padLeft(2, "0");
99 }
100
101 @override
102 String timerPickerMinuteLabel(int minute) {
103 return minute.toString().padLeft(2, "0") + "分";
104 }
105
106 @override
107 String timerPickerSecond(int second) {
108 return second.toString().padLeft(2, "0");
109 }
110
111 @override
112 String timerPickerSecondLabel(int second) {
113 return second.toString().padLeft(2, "0") + "秒";
114 }
115
116 static const LocalizationsDelegate<CupertinoLocalizations> delegate =
117 _ChineseDelegate();
118
119 static Future<CupertinoLocalizations> load(Locale locale) async {
120 var localizaltions = ChineseCupertinoLocalizations();
121 await localizaltions.init();
122 return SynchronousFuture<CupertinoLocalizations>(localizaltions);
123 }
124}
125
126class _ChineseDelegate extends LocalizationsDelegate<CupertinoLocalizations> {
127 const _ChineseDelegate();
128
129 @override
130 bool isSupported(Locale locale) {
131 return locale.languageCode == 'zh';
132 }
133
134 @override
135 Future<CupertinoLocalizations> load(Locale locale) {
136 return ChineseCupertinoLocalizations.load(locale);
137 }
138
139 @override
140 bool shouldReload(LocalizationsDelegate<CupertinoLocalizations> old) {
141 return false;
142 }
143}
这个东西弄到项目里
然后在 Application 里配置一下
1class MyApp extends StatelessWidget {
2 // This widget is the root of your application.
3 @override
4 Widget build(BuildContext context) {
5 return MaterialApp(
6 title: 'Flutter Demo',
7 theme: ThemeData(
8 // This is the theme of your application.
9 //
10 // Try running your application with "flutter run". You'll see the
11 // application has a blue toolbar. Then, without quitting the app, try
12 // changing the primarySwatch below to Colors.green and then invoke
13 // "hot reload" (press "r" in the console where you ran "flutter run",
14 // or simply save your changes to "hot reload" in a Flutter IDE).
15 // Notice that the counter didn't reset back to zero; the application
16 // is not restarted.
17 primarySwatch: Colors.blue,
18 ),
19 home: MyHomePage(title: 'Flutter Demo Home Page'),
20 localizationsDelegates: <LocalizationsDelegate<dynamic>>[
21 ChineseCupertinoLocalizations.delegate, // 这里加上这个,是自定义的delegate
22
23 DefaultCupertinoLocalizations.delegate, // 这个截止目前只包含英文
24
25 // 下面两个是Material widgets的delegate, 包含中文
26 GlobalMaterialLocalizations.delegate,
27 GlobalWidgetsLocalizations.delegate,
28 ],
29 supportedLocales: [
30 const Locale('en', 'US'), // English
31 const Locale('zh', 'Hans'), // China
32 const Locale('zh', ''), // China
33 // ... other locales the app supports
34 ],
35 );
36 }
37}
这样就能完成默认的中文的本地化,当然之前那个 Cupertino 的 delegate 实际上也是借助了 Material 中的 delegate 本地化
这里还要注意,一定要保证你的设备是本地语言是中文(因为很多朋友是使用模拟器开发的,默认是英文)
原因分析
这里要解析一波源码了,为什么会出现这种情况呢
主要原因就是在某个版本,加入了一整套的 Cupertino 相关的支持,但是又因为某些原因遗忘了非英文版本,造成了默认的情况下,不包含其他语言
而在未设置对应语言的 delegate 时,又没有一个默认的 delegate,从而造成空指针异常,进而抛出错误
源码解析
这个是 Cupertino 中,弹出剪切/复制/粘贴/全选那个按钮那个 toolbar 的配置界面,这里会使用到这个类,然而又因为得到的是空的,所以会报空指针异常
而获取的方式是使用CupertinoLocalizations.of(context);
的方式
查看这个方法的定义处又可以跟踪到另一个地方
然后来到这段代码
1Future<Map<Type, dynamic>> _loadAll(Locale locale, Iterable<LocalizationsDelegate<dynamic>> allDelegates) {
2 final Map<Type, dynamic> output = <Type, dynamic>{};
3 List<_Pending> pendingList;
4
5 // Only load the first delegate for each delegate type that supports
6 // locale.languageCode.
7 final Set<Type> types = Set<Type>();
8 final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[];
9 for (LocalizationsDelegate<dynamic> delegate in allDelegates) {
10 if (!types.contains(delegate.type) && delegate.isSupported(locale)) {
11 types.add(delegate.type);
12 delegates.add(delegate);
13 }
14 }
15
16 for (LocalizationsDelegate<dynamic> delegate in delegates) {
17 final Future<dynamic> inputValue = delegate.load(locale);
18 dynamic completedValue;
19 final Future<dynamic> futureValue = inputValue.then<dynamic>((dynamic value) {
20 return completedValue = value;
21 });
22 if (completedValue != null) { // inputValue was a SynchronousFuture
23 final Type type = delegate.type;
24 assert(!output.containsKey(type));
25 output[type] = completedValue;
26 } else {
27 pendingList ??= <_Pending>[];
28 pendingList.add(_Pending(delegate, futureValue));
29 }
30 }
31
32 // All of the delegate.load() values were synchronous futures, we're done.
33 if (pendingList == null)
34 return SynchronousFuture<Map<Type, dynamic>>(output);
35
36 // Some of delegate.load() values were asynchronous futures. Wait for them.
37 return Future.wait<dynamic>(pendingList.map<Future<dynamic>>((_Pending p) => p.futureValue))
38 .then<Map<Type, dynamic>>((List<dynamic> values) {
39 assert(values.length == pendingList.length);
40 for (int i = 0; i < values.length; i += 1) {
41 final Type type = pendingList[i].delegate.type;
42 assert(!output.containsKey(type));
43 output[type] = values[i];
44 }
45 return output;
46 });
47}
简单来说,这个方法是根据本地的语言读取所有支持这个语言的 delegate
然后问题来了, 没读取到怎么办呢
有几处代码需要关注一下
这里就看出来了,为什么会有空指针异常的原因 3 => 2 => 1 但是 1 也没读取到 ,自然就空指针了
后记
本篇介绍了解决方案 pastelabel
copylabel
各种 label 空指针的原因和解决方案