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,从而造成空指针异常,进而抛出错误

源码解析

20190109135322.png

这个是 Cupertino 中,弹出剪切/复制/粘贴/全选那个按钮那个 toolbar 的配置界面,这里会使用到这个类,然而又因为得到的是空的,所以会报空指针异常

而获取的方式是使用CupertinoLocalizations.of(context);的方式

查看这个方法的定义处又可以跟踪到另一个地方

20190109135531.png

20190109135615.png

然后来到这段代码

 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

然后问题来了, 没读取到怎么办呢

有几处代码需要关注一下

  1. 20190109143609.png

  2. 20190109143806.png

  3. 20190109144442.png

这里就看出来了,为什么会有空指针异常的原因 3 => 2 => 1 但是 1 也没读取到 ,自然就空指针了

后记

本篇介绍了解决方案 pastelabel copylabel 各种 label 空指针的原因和解决方案