NosuchMethodError: The getter 'pasterButtonLabel' was called on null.
Receiver: null
Tried calling: pasteButtonLabel
在最近 app store 提交审核时被拒了,然后得到了一个这样的截图
在 flutter 中可能会会出现各种问题,因为之前遇到过这个问题,但是那是我另一个应用,这个忘了设置了
我快速搞了一下,重新提交了审核
解决方式
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
class ChineseCupertinoLocalizations implements CupertinoLocalizations {
final materialDelegate = GlobalMaterialLocalizations.delegate;
final widgetsDelegate = GlobalWidgetsLocalizations.delegate;
final local = const Locale('zh');
MaterialLocalizations ml;
Future init() async {
ml = await materialDelegate.load(local);
print(ml.pasteButtonLabel);
}
@override
String get alertDialogLabel => ml.alertDialogLabel;
@override
String get anteMeridiemAbbreviation => ml.anteMeridiemAbbreviation;
@override
String get copyButtonLabel => ml.copyButtonLabel;
@override
String get cutButtonLabel => ml.cutButtonLabel;
@override
DatePickerDateOrder get datePickerDateOrder => DatePickerDateOrder.mdy;
@override
DatePickerDateTimeOrder get datePickerDateTimeOrder =>
DatePickerDateTimeOrder.date_time_dayPeriod;
@override
String datePickerDayOfMonth(int dayIndex) {
return dayIndex.toString();
}
@override
String datePickerHour(int hour) {
return hour.toString().padLeft(2, "0");
}
@override
String datePickerHourSemanticsLabel(int hour) {
return "$hour" + "时";
}
@override
String datePickerMediumDate(DateTime date) {
return ml.formatMediumDate(date);
}
@override
String datePickerMinute(int minute) {
return minute.toString().padLeft(2, '0');
}
@override
String datePickerMinuteSemanticsLabel(int minute) {
return "$minute" + "分";
}
@override
String datePickerMonth(int monthIndex) {
return "$monthIndex";
}
@override
String datePickerYear(int yearIndex) {
return yearIndex.toString();
}
@override
String get pasteButtonLabel => ml.pasteButtonLabel;
@override
String get postMeridiemAbbreviation => ml.postMeridiemAbbreviation;
@override
String get selectAllButtonLabel => ml.selectAllButtonLabel;
@override
String timerPickerHour(int hour) {
return hour.toString().padLeft(2, "0");
}
@override
String timerPickerHourLabel(int hour) {
return "$hour".toString().padLeft(2, "0") + "时";
}
@override
String timerPickerMinute(int minute) {
return minute.toString().padLeft(2, "0");
}
@override
String timerPickerMinuteLabel(int minute) {
return minute.toString().padLeft(2, "0") + "分";
}
@override
String timerPickerSecond(int second) {
return second.toString().padLeft(2, "0");
}
@override
String timerPickerSecondLabel(int second) {
return second.toString().padLeft(2, "0") + "秒";
}
static const LocalizationsDelegate<CupertinoLocalizations> delegate =
_ChineseDelegate();
static Future<CupertinoLocalizations> load(Locale locale) async {
var localizaltions = ChineseCupertinoLocalizations();
await localizaltions.init();
return SynchronousFuture<CupertinoLocalizations>(localizaltions);
}
}
class _ChineseDelegate extends LocalizationsDelegate<CupertinoLocalizations> {
const _ChineseDelegate();
@override
bool isSupported(Locale locale) {
return locale.languageCode == 'zh';
}
@override
Future<CupertinoLocalizations> load(Locale locale) {
return ChineseCupertinoLocalizations.load(locale);
}
@override
bool shouldReload(LocalizationsDelegate<CupertinoLocalizations> old) {
return false;
}
}
这个东西弄到项目里
然后在 Application 里配置一下
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
ChineseCupertinoLocalizations.delegate, // 这里加上这个,是自定义的delegate
DefaultCupertinoLocalizations.delegate, // 这个截止目前只包含英文
// 下面两个是Material widgets的delegate, 包含中文
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', 'US'), // English
const Locale('zh', 'Hans'), // China
const Locale('zh', ''), // China
// ... other locales the app supports
],
);
}
}
这样就能完成默认的中文的本地化,当然之前那个 Cupertino 的 delegate 实际上也是借助了 Material 中的 delegate 本地化
这里还要注意,一定要保证你的设备是本地语言是中文(因为很多朋友是使用模拟器开发的,默认是英文)
原因分析
这里要解析一波源码了,为什么会出现这种情况呢
主要原因就是在某个版本,加入了一整套的 Cupertino 相关的支持,但是又因为某些原因遗忘了非英文版本,造成了默认的情况下,不包含其他语言
而在未设置对应语言的 delegate 时,又没有一个默认的 delegate,从而造成空指针异常,进而抛出错误
源码解析
这个是 Cupertino 中,弹出剪切/复制/粘贴/全选那个按钮那个 toolbar 的配置界面,这里会使用到这个类,然而又因为得到的是空的,所以会报空指针异常
而获取的方式是使用CupertinoLocalizations.of(context);
的方式
查看这个方法的定义处又可以跟踪到另一个地方
然后来到这段代码
Future<Map<Type, dynamic>> _loadAll(Locale locale, Iterable<LocalizationsDelegate<dynamic>> allDelegates) {
final Map<Type, dynamic> output = <Type, dynamic>{};
List<_Pending> pendingList;
// Only load the first delegate for each delegate type that supports
// locale.languageCode.
final Set<Type> types = Set<Type>();
final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[];
for (LocalizationsDelegate<dynamic> delegate in allDelegates) {
if (!types.contains(delegate.type) && delegate.isSupported(locale)) {
types.add(delegate.type);
delegates.add(delegate);
}
}
for (LocalizationsDelegate<dynamic> delegate in delegates) {
final Future<dynamic> inputValue = delegate.load(locale);
dynamic completedValue;
final Future<dynamic> futureValue = inputValue.then<dynamic>((dynamic value) {
return completedValue = value;
});
if (completedValue != null) { // inputValue was a SynchronousFuture
final Type type = delegate.type;
assert(!output.containsKey(type));
output[type] = completedValue;
} else {
pendingList ??= <_Pending>[];
pendingList.add(_Pending(delegate, futureValue));
}
}
// All of the delegate.load() values were synchronous futures, we're done.
if (pendingList == null)
return SynchronousFuture<Map<Type, dynamic>>(output);
// Some of delegate.load() values were asynchronous futures. Wait for them.
return Future.wait<dynamic>(pendingList.map<Future<dynamic>>((_Pending p) => p.futureValue))
.then<Map<Type, dynamic>>((List<dynamic> values) {
assert(values.length == pendingList.length);
for (int i = 0; i < values.length; i += 1) {
final Type type = pendingList[i].delegate.type;
assert(!output.containsKey(type));
output[type] = values[i];
}
return output;
});
}
简单来说,这个方法是根据本地的语言读取所有支持这个语言的 delegate
然后问题来了, 没读取到怎么办呢
有几处代码需要关注一下
这里就看出来了,为什么会有空指针异常的原因 3 => 2 => 1 但是 1 也没读取到 ,自然就空指针了
后记
本篇介绍了解决方案 pastelabel
copylabel
各种 label 空指针的原因和解决方案