diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4d33943..446e513 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest container: - image: google/dart:beta + image: google/dart:dev steps: - uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index 714afaf..b77fd3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # https://dart.dev/guides/libraries/private-files .packages .dart_tool/ +.idea/ pubspec.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index b7a3c57..fdc54f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 4.0.0-nullsafety +- Updated to support 2.12.0 and null safety. + ## 3.4.2 - A number of cleanups to improve the package health score. diff --git a/example/example.dart b/example/example.dart index bb7b345..46cc29b 100644 --- a/example/example.dart +++ b/example/example.dart @@ -9,26 +9,26 @@ import 'dart:html'; import 'package:usage/usage_html.dart'; -Analytics _analytics; -String _lastUa; +Analytics? _analytics; +String? _lastUa; int _count = 0; void main() { - querySelector('#foo').onClick.listen((_) => _handleFoo()); - querySelector('#bar').onClick.listen((_) => _handleBar()); - querySelector('#page').onClick.listen((_) => _changePage()); + querySelector('#foo')!.onClick.listen((_) => _handleFoo()); + querySelector('#bar')!.onClick.listen((_) => _handleBar()); + querySelector('#page')!.onClick.listen((_) => _changePage()); } -String _ua() => (querySelector('#ua') as InputElement).value.trim(); +String _ua() => (querySelector('#ua') as InputElement).value!.trim(); Analytics getAnalytics() { if (_analytics == null || _lastUa != _ua()) { _lastUa = _ua(); - _analytics = AnalyticsHtml(_lastUa, 'Test app', '1.0'); - _analytics.sendScreenView(window.location.pathname); + _analytics = AnalyticsHtml(_lastUa!, 'Test app', '1.0'); + _analytics!.sendScreenView(window.location.pathname!); } - return _analytics; + return _analytics!; } void _handleFoo() { @@ -44,5 +44,5 @@ void _handleBar() { void _changePage() { var analytics = getAnalytics(); window.history.pushState(null, 'new page', '${++_count}.html'); - analytics.sendScreenView(window.location.pathname); + analytics.sendScreenView(window.location.pathname!); } diff --git a/lib/src/usage_impl.dart b/lib/src/usage_impl.dart index fc87e95..07837d4 100644 --- a/lib/src/usage_impl.dart +++ b/lib/src/usage_impl.dart @@ -25,10 +25,9 @@ String postEncode(Map map) { class ThrottlingBucket { final int startingCount; int drops; - int _lastReplenish; + late int _lastReplenish; - ThrottlingBucket(this.startingCount) { - drops = startingCount; + ThrottlingBucket(this.startingCount) : drops = startingCount { _lastReplenish = DateTime.now().millisecondsSinceEpoch; } @@ -61,9 +60,9 @@ class AnalyticsImpl implements Analytics { @override final String trackingId; @override - final String applicationName; + final String? applicationName; @override - final String applicationVersion; + final String? applicationVersion; final PersistentProperties properties; final PostHandler postHandler; @@ -76,22 +75,20 @@ class AnalyticsImpl implements Analytics { @override AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut; - String _url; + late String _url; final StreamController> _sendController = StreamController.broadcast(sync: true); AnalyticsImpl(this.trackingId, this.properties, this.postHandler, - {this.applicationName, this.applicationVersion, String analyticsUrl}) { - assert(trackingId != null); - + {this.applicationName, this.applicationVersion, String? analyticsUrl}) { if (applicationName != null) setSessionValue('an', applicationName); if (applicationVersion != null) setSessionValue('av', applicationVersion); _url = analyticsUrl ?? _defaultAnalyticsUrl; } - bool _firstRun; + bool? _firstRun; @override bool get firstRun { @@ -103,7 +100,7 @@ class AnalyticsImpl implements Analytics { } } - return _firstRun; + return _firstRun!; } @override @@ -120,7 +117,7 @@ class AnalyticsImpl implements Analytics { } @override - Future sendScreenView(String viewName, {Map parameters}) { + Future sendScreenView(String viewName, {Map? parameters}) { var args = {'cd': viewName}; if (parameters != null) { args.addAll(parameters); @@ -130,7 +127,7 @@ class AnalyticsImpl implements Analytics { @override Future sendEvent(String category, String action, - {String label, int value, Map parameters}) { + {String? label, int? value, Map? parameters}) { var args = {'ec': category, 'ea': action}; if (label != null) args['el'] = label; if (value != null) args['ev'] = value; @@ -148,7 +145,7 @@ class AnalyticsImpl implements Analytics { @override Future sendTiming(String variableName, int time, - {String category, String label}) { + {String? category, String? label}) { var args = {'utv': variableName, 'utt': time}; if (label != null) args['utl'] = label; if (category != null) args['utc'] = category; @@ -157,12 +154,12 @@ class AnalyticsImpl implements Analytics { @override AnalyticsTimer startTimer(String variableName, - {String category, String label}) { + {String? category, String? label}) { return AnalyticsTimer(this, variableName, category: category, label: label); } @override - Future sendException(String description, {bool fatal}) { + Future sendException(String description, {bool? fatal}) { // We trim exceptions to a max length; google analytics will apply it's own // truncation, likely around 150 chars or so. const maxExceptionLength = 1000; @@ -201,7 +198,7 @@ class AnalyticsImpl implements Analytics { Stream> get onSend => _sendController.stream; @override - Future waitForLastPing({Duration timeout}) { + Future waitForLastPing({Duration? timeout}) { Future f = Future.wait(_futures).catchError((e) => null); if (timeout != null) { @@ -268,6 +265,7 @@ abstract class PersistentProperties { PersistentProperties(this.name); dynamic operator [](String key); + void operator []=(String key, dynamic value); /// Re-read settings from the backing store. This may be a no-op on some diff --git a/lib/src/usage_impl_html.dart b/lib/src/usage_impl_html.dart index f2de075..2d8092d 100644 --- a/lib/src/usage_impl_html.dart +++ b/lib/src/usage_impl_html.dart @@ -15,38 +15,39 @@ import 'usage_impl.dart'; class AnalyticsHtml extends AnalyticsImpl { AnalyticsHtml( String trackingId, String applicationName, String applicationVersion, - {String analyticsUrl}) + {String? analyticsUrl}) : super(trackingId, HtmlPersistentProperties(applicationName), HtmlPostHandler(), applicationName: applicationName, applicationVersion: applicationVersion, analyticsUrl: analyticsUrl) { - var screenWidth = window.screen.width; - var screenHeight = window.screen.height; + var screenWidth = window.screen!.width; + var screenHeight = window.screen!.height; setSessionValue('sr', '${screenWidth}x$screenHeight'); - setSessionValue('sd', '${window.screen.pixelDepth}-bits'); + setSessionValue('sd', '${window.screen!.pixelDepth}-bits'); setSessionValue('ul', window.navigator.language); } } typedef HttpRequestor = Future Function(String url, - {String method, dynamic sendData}); + {String? method, dynamic sendData}); class HtmlPostHandler extends PostHandler { - final HttpRequestor mockRequestor; + final HttpRequestor? mockRequestor; HtmlPostHandler({this.mockRequestor}); @override Future sendPost(String url, Map parameters) { - var viewportWidth = document.documentElement.clientWidth; - var viewportHeight = document.documentElement.clientHeight; + var viewportWidth = document.documentElement!.clientWidth; + var viewportHeight = document.documentElement!.clientHeight; parameters['vp'] = '${viewportWidth}x$viewportHeight'; var data = postEncode(parameters); - var requestor = mockRequestor ?? HttpRequest.request; + Future Function(String, {String method, dynamic sendData}) + requestor = mockRequestor ?? HttpRequest.request; return requestor(url, method: 'POST', sendData: data).catchError((e) { // Catch errors that can happen during a request, but that we can't do // anything about, e.g. a missing internet connection. @@ -58,7 +59,7 @@ class HtmlPostHandler extends PostHandler { } class HtmlPersistentProperties extends PersistentProperties { - Map _map; + late Map _map; HtmlPersistentProperties(String name) : super(name) { var str = window.localStorage[name]; diff --git a/lib/src/usage_impl_io.dart b/lib/src/usage_impl_io.dart index e942c89..eddc1ef 100644 --- a/lib/src/usage_impl_io.dart +++ b/lib/src/usage_impl_io.dart @@ -24,7 +24,7 @@ import 'usage_impl.dart'; class AnalyticsIO extends AnalyticsImpl { AnalyticsIO( String trackingId, String applicationName, String applicationVersion, - {String analyticsUrl, Directory documentDirectory}) + {String? analyticsUrl, Directory? documentDirectory}) : super( trackingId, IOPersistentProperties(applicationName, @@ -75,9 +75,9 @@ String getDartVersion() { class IOPostHandler extends PostHandler { final String _userAgent; - final HttpClient mockClient; + final HttpClient? mockClient; - HttpClient _client; + HttpClient? _client; IOPostHandler({this.mockClient}) : _userAgent = _createUserAgent(); @@ -87,11 +87,11 @@ class IOPostHandler extends PostHandler { if (_client == null) { _client = mockClient ?? HttpClient(); - _client.userAgent = _userAgent; + _client!.userAgent = _userAgent; } try { - var req = await _client.postUrl(Uri.parse(url)); + var req = await _client!.postUrl(Uri.parse(url)); req.write(data); var response = await req.close(); await response.drain(); @@ -108,10 +108,10 @@ class IOPostHandler extends PostHandler { JsonEncoder _jsonEncoder = JsonEncoder.withIndent(' '); class IOPersistentProperties extends PersistentProperties { - File _file; - Map _map; + late File _file; + late Map _map; - IOPersistentProperties(String name, {String documentDirPath}) : super(name) { + IOPersistentProperties(String name, {String? documentDirPath}) : super(name) { var fileName = '.${name.replaceAll(' ', '_')}'; documentDirPath ??= userHomeDir(); _file = File(path.join(documentDirPath, fileName)); @@ -162,18 +162,15 @@ class IOPersistentProperties extends PersistentProperties { /// Return the string for the platform's locale; return's `null` if the locale /// can't be determined. -String getPlatformLocale() { +String? getPlatformLocale() { var locale = Platform.localeName; - if (locale == null) return null; - if (locale != null) { - // Convert `en_US.UTF-8` to `en_US`. - var index = locale.indexOf('.'); - if (index != -1) locale = locale.substring(0, index); + // Convert `en_US.UTF-8` to `en_US`. + var index = locale.indexOf('.'); + if (index != -1) locale = locale.substring(0, index); - // Convert `en_US` to `en-us`. - locale = locale.replaceAll('_', '-').toLowerCase(); - } + // Convert `en_US` to `en-us`. + locale = locale.replaceAll('_', '-').toLowerCase(); return locale; } diff --git a/lib/usage.dart b/lib/usage.dart index 82a5b8f..4136d51 100644 --- a/lib/usage.dart +++ b/lib/usage.dart @@ -44,10 +44,10 @@ abstract class Analytics { String get trackingId; /// The application name. - String get applicationName; + String? get applicationName; /// The application version. - String get applicationVersion; + String? get applicationVersion; /// Is this the first time the tool has run? bool get firstRun; @@ -72,7 +72,7 @@ abstract class Analytics { /// /// [parameters] can be any analytics key/value pair. Useful /// for custom dimensions, etc. - Future sendScreenView(String viewName, {Map parameters}); + Future sendScreenView(String viewName, {Map? parameters}); /// Sends an Event hit to Google Analytics. [label] specifies the event label. /// [value] specifies the event value. Values must be non-negative. @@ -80,7 +80,7 @@ abstract class Analytics { /// [parameters] can be any analytics key/value pair. Useful /// for custom dimensions, etc. Future sendEvent(String category, String action, - {String label, int value, Map parameters}); + {String? label, int? value, Map? parameters}); /// Sends a Social hit to Google Analytics. /// @@ -96,17 +96,17 @@ abstract class Analytics { /// milliseconds). [category] specifies the category of the timing. [label] /// specifies the label of the timing. Future sendTiming(String variableName, int time, - {String category, String label}); + {String? category, String? label}); /// Start a timer. The time won't be calculated, and the analytics information /// sent, until the [AnalyticsTimer.finish] method is called. AnalyticsTimer startTimer(String variableName, - {String category, String label}); + {String? category, String? label}); /// In order to avoid sending any personally identifying information, the /// [description] field must not contain the exception message. In addition, /// only the first 100 chars of the description will be sent. - Future sendException(String description, {bool fatal}); + Future sendException(String description, {bool? fatal}); /// Gets a session variable value. dynamic getSessionValue(String param); @@ -136,7 +136,7 @@ abstract class Analytics { /// allows CLI apps to delay for a short time waiting for GA requests to /// complete, and then do something like call `dart:io`'s `exit()` explicitly /// themselves (or the [close] method below). - Future waitForLastPing({Duration timeout}); + Future waitForLastPing({Duration? timeout}); /// Free any used resources. /// @@ -157,11 +157,11 @@ enum AnalyticsOpt { class AnalyticsTimer { final Analytics analytics; final String variableName; - final String category; - final String label; + final String? category; + final String? label; - int _startMillis; - int _endMillis; + late int _startMillis; + int? _endMillis; AnalyticsTimer(this.analytics, this.variableName, {this.category, this.label}) { @@ -172,7 +172,7 @@ class AnalyticsTimer { if (_endMillis == null) { return DateTime.now().millisecondsSinceEpoch - _startMillis; } else { - return _endMillis - _startMillis; + return _endMillis! - _startMillis; } } @@ -220,7 +220,7 @@ class AnalyticsMock implements Analytics { String get clientId => '00000000-0000-4000-0000-000000000000'; @override - Future sendScreenView(String viewName, {Map parameters}) { + Future sendScreenView(String viewName, {Map? parameters}) { parameters ??= {}; parameters['viewName'] = viewName; return _log('screenView', parameters); @@ -228,7 +228,7 @@ class AnalyticsMock implements Analytics { @override Future sendEvent(String category, String action, - {String label, int value, Map parameters}) { + {String? label, int? value, Map? parameters}) { parameters ??= {}; return _log( 'event', @@ -242,7 +242,7 @@ class AnalyticsMock implements Analytics { @override Future sendTiming(String variableName, int time, - {String category, String label}) { + {String? category, String? label}) { return _log('timing', { 'variableName': variableName, 'time': time, @@ -253,12 +253,12 @@ class AnalyticsMock implements Analytics { @override AnalyticsTimer startTimer(String variableName, - {String category, String label}) { + {String? category, String? label}) { return AnalyticsTimer(this, variableName, category: category, label: label); } @override - Future sendException(String description, {bool fatal}) => + Future sendException(String description, {bool? fatal}) => _log('exception', {'description': description, 'fatal': fatal}); @override @@ -271,7 +271,7 @@ class AnalyticsMock implements Analytics { Stream> get onSend => _sendController.stream; @override - Future waitForLastPing({Duration timeout}) => Future.value(); + Future waitForLastPing({Duration? timeout}) => Future.value(); @override void close() {} @@ -300,7 +300,7 @@ String sanitizeStacktrace(dynamic st, {bool shorten = true}) { iter = iter.toList().reversed; for (var match in iter) { - var replacement = match.group(1); + var replacement = match.group(1)!; str = str.substring(0, match.start) + replacement + str.substring(match.end); } diff --git a/pubspec.yaml b/pubspec.yaml index 46fa921..257679b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,15 +3,15 @@ # BSD-style license that can be found in the LICENSE file. name: usage -version: 3.4.2 +version: 4.0.0-nullsafety description: A Google Analytics wrapper for command-line, web, and Flutter apps. homepage: https://github.com/dart-lang/usage environment: - sdk: '>=2.2.0 <3.0.0' + sdk: '>=2.12.0-0 <3.0.0' dependencies: - path: ^1.4.0 + path: ^1.8.0-nullsafety dev_dependencies: pedantic: ^1.9.0 diff --git a/test/src/common.dart b/test/src/common.dart index bcdabf6..304faf9 100644 --- a/test/src/common.dart +++ b/test/src/common.dart @@ -9,7 +9,7 @@ import 'dart:async'; import 'package:test/test.dart'; import 'package:usage/src/usage_impl.dart'; -AnalyticsImplMock createMock({Map props}) => +AnalyticsImplMock createMock({Map? props}) => AnalyticsImplMock('UA-0', props: props); void was(Map m, String type) => expect(m['t'], type); @@ -17,10 +17,10 @@ void has(Map m, String key) => expect(m[key], isNotNull); void hasnt(Map m, String key) => expect(m[key], isNull); class AnalyticsImplMock extends AnalyticsImpl { - MockProperties get mockProperties => properties; - MockPostHandler get mockPostHandler => postHandler; + MockProperties get mockProperties => properties as MockProperties; + MockPostHandler get mockPostHandler => postHandler as MockPostHandler; - AnalyticsImplMock(String trackingId, {Map props}) + AnalyticsImplMock(String trackingId, {Map? props}) : super(trackingId, MockProperties(props), MockPostHandler(), applicationName: 'Test App', applicationVersion: '0.1'); @@ -30,7 +30,7 @@ class AnalyticsImplMock extends AnalyticsImpl { class MockProperties extends PersistentProperties { Map props = {}; - MockProperties([Map props]) : super('mock') { + MockProperties([Map? props]) : super('mock') { if (props != null) this.props.addAll(props); } diff --git a/test/usage_impl_io_test.dart b/test/usage_impl_io_test.dart index 2fc117b..8b15606 100644 --- a/test/usage_impl_io_test.dart +++ b/test/usage_impl_io_test.dart @@ -53,7 +53,7 @@ void defineTests() { class MockHttpClient implements HttpClient { @override - String userAgent; + String? userAgent; int sendCount = 0; int writeCount = 0; bool closed = false; @@ -73,7 +73,7 @@ class MockHttpClientRequest implements HttpClientRequest { MockHttpClientRequest(this.client); @override - void write(Object obj) { + void write(Object? obj) { client.writeCount++; } @@ -93,7 +93,7 @@ class MockHttpClientResponse implements HttpClientResponse { MockHttpClientResponse(this.client); @override - Future drain([E futureValue]) { + Future drain([E? futureValue]) { client.sendCount++; return Future.value(); } diff --git a/test/web_test.dart b/test/web_test.dart index df2da92..eb0b107 100644 --- a/test/web_test.dart +++ b/test/web_test.dart @@ -59,7 +59,7 @@ void defineWebTests() { class MockRequestor { int sendCount = 0; - Future request(String url, {String method, sendData}) { + Future request(String url, {String? method, sendData}) { expect(url, isNotEmpty); expect(method, isNotEmpty); expect(sendData, isNotEmpty);