Merge pull request #87 from dart-lang/3_0

rev to 3.0.0-dev
diff --git a/.analysis_options b/.analysis_options
index 19c7129..a10d4c5 100644
--- a/.analysis_options
+++ b/.analysis_options
@@ -1,4 +1,2 @@
 analyzer:
   strong-mode: true
-  exclude:
-    - lib/src/usage_impl_flutter.dart
diff --git a/.travis.yml b/.travis.yml
index 35def79..9e27d1f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,11 +1,3 @@
 language: dart
-dart:
-  - dev
 sudo: false
-
-# before_install:
-#   - "export CHROME_ARGS=--no-sandbox"
-#   - "export DISPLAY=:99.0"
-#   - "sh -e /etc/init.d/xvfb start"
-
 script: ./tool/travis.sh
diff --git a/changelog.md b/changelog.md
index 4379c44..f62e4dd 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,7 +1,9 @@
 # Changelog
 
-## unreleased
-- updated the Flutter example pubspec to use the newer Flutter sdk reference style
+## 3.0.0
+- removed the use of configurable imports
+- removed the Flutter specific entry-point; Flutter apps can now use the
+  regular `dart:io` entrypoint (AnalyticsIO)
 
 ## 2.2.2
 - adjust the Flutter usage client to Flutter API changes
diff --git a/example/example.dart b/example/example.dart
index 27fb5fc..06578c3 100644
--- a/example/example.dart
+++ b/example/example.dart
@@ -5,10 +5,9 @@
 /// A simple web app to hand-test the usage library.
 library usage_example;
 
-import 'dart:async';
 import 'dart:html';
 
-import 'package:usage/usage.dart';
+import 'package:usage/usage_html.dart';
 
 Analytics _analytics;
 String _lastUa;
@@ -22,28 +21,28 @@
 
 String _ua() => (querySelector('#ua') as InputElement).value.trim();
 
-Future<Analytics> getAnalytics() async {
+Analytics getAnalytics() {
   if (_analytics == null || _lastUa != _ua()) {
     _lastUa = _ua();
-    _analytics = await Analytics.create(_lastUa, 'Test app', '1.0');
+    _analytics = new AnalyticsHtml(_lastUa, 'Test app', '1.0');
     _analytics.sendScreenView(window.location.pathname);
   }
 
   return _analytics;
 }
 
-Future _handleFoo() async {
-  Analytics analytics = await getAnalytics();
+void _handleFoo() {
+  Analytics analytics = getAnalytics();
   analytics.sendEvent('main', 'foo');
 }
 
-Future _handleBar() async {
-  Analytics analytics = await getAnalytics();
+void _handleBar() {
+  Analytics analytics = getAnalytics();
   analytics.sendEvent('main', 'bar');
 }
 
-Future _changePage() async {
-  Analytics analytics = await getAnalytics();
+void _changePage() {
+  Analytics analytics = getAnalytics();
   window.history.pushState(null, 'new page', '${++_count}.html');
   analytics.sendScreenView(window.location.pathname);
 }
diff --git a/example/flutter_example/.gitignore b/example/flutter_example/.gitignore
deleted file mode 100644
index 14c7d4c..0000000
--- a/example/flutter_example/.gitignore
+++ /dev/null
@@ -1,9 +0,0 @@
-.DS_Store
-.atom/
-.idea
-.packages
-.pub/
-build/
-ios/.generated/
-packages
-pubspec.lock
diff --git a/example/flutter_example/README.md b/example/flutter_example/README.md
deleted file mode 100644
index 7608a60..0000000
--- a/example/flutter_example/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Example of usage package with Flutter
-
-To run the Flutter example, ensure that the pubspec.yaml points to your Flutter
-path. Then run:
-
-```
-pub get
-flutter run
-```
diff --git a/example/flutter_example/android/AndroidManifest.xml b/example/flutter_example/android/AndroidManifest.xml
deleted file mode 100644
index 2e28bb3..0000000
--- a/example/flutter_example/android/AndroidManifest.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.yourcompany.flutterExample"
-    android:versionCode="1"
-    android:versionName="0.0.1">
-
-    <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="21" />
-    <uses-permission android:name="android.permission.INTERNET"/>
-
-    <application android:name="org.domokit.sky.shell.SkyApplication" android:label="flutter_example" android:icon="@mipmap/ic_launcher">
-        <activity android:name="org.domokit.sky.shell.SkyActivity"
-                  android:launchMode="singleTask"
-                  android:theme="@android:style/Theme.Black.NoTitleBar"
-                  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
-                  android:hardwareAccelerated="true"
-                  android:windowSoftInputMode="adjustResize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/example/flutter_example/android/res/mipmap-hdpi/ic_launcher.png b/example/flutter_example/android/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index f0a55cd..0000000
--- a/example/flutter_example/android/res/mipmap-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/example/flutter_example/android/res/mipmap-mdpi/ic_launcher.png b/example/flutter_example/android/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index eb98dc4..0000000
--- a/example/flutter_example/android/res/mipmap-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/example/flutter_example/android/res/mipmap-xhdpi/ic_launcher.png b/example/flutter_example/android/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index f1783db..0000000
--- a/example/flutter_example/android/res/mipmap-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/example/flutter_example/android/res/mipmap-xxhdpi/ic_launcher.png b/example/flutter_example/android/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 46828a2..0000000
--- a/example/flutter_example/android/res/mipmap-xxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/example/flutter_example/android/res/mipmap-xxxhdpi/ic_launcher.png b/example/flutter_example/android/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index a527671..0000000
--- a/example/flutter_example/android/res/mipmap-xxxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/example/flutter_example/flutter.yaml b/example/flutter_example/flutter.yaml
deleted file mode 100644
index 2f6cd44..0000000
--- a/example/flutter_example/flutter.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-name: flutter_example
-uses-material-design: true
diff --git a/example/flutter_example/lib/main.dart b/example/flutter_example/lib/main.dart
deleted file mode 100644
index eceae54..0000000
--- a/example/flutter_example/lib/main.dart
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2016, the Flutter project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:flutter/material.dart';
-import 'package:usage/usage.dart';
-
-Future main() async {
-  runApp(new Container());
-  Analytics ga = await Analytics.create('UA-67589403-4', 'ga_test', '1.0');
-  runApp(new MaterialApp(
-    title: 'Usage Example',
-    theme: new ThemeData.dark(),
-    routes: <String, WidgetBuilder>{
-      '/': (BuildContext context) => new FlutterDemo(ga)
-    },
-  ));
-}
-
-class FlutterDemo extends StatefulWidget {
-  FlutterDemo(this.ga);
-  Analytics ga;
-  @override
-  State createState() => new _FlutterDemoState();
-}
-
-class _FlutterDemoState extends State<FlutterDemo> {
-  int _times = 0;
-
-  void _handleButtonPressed() {
-    config.ga.sendEvent('button', 'pressed');
-    setState(() {
-      _times++;
-    });
-  }
-
-  void _handleOptIn(bool value) {
-    setState(() {
-      config.ga.enabled = value;
-    });
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return new Scaffold(
-      appBar: new AppBar(
-        title: new Text('Usage Example'),
-      ),
-      body: new Column(
-        children: <Widget>[
-          new Center(
-            child: new Text("Button pressed $_times times."),
-          ),
-          new ListItem(
-            onTap: () => _handleOptIn(!config.ga.enabled),
-            leading: new Checkbox(
-              value: config.ga.enabled,
-              onChanged: _handleOptIn,
-            ),
-            title: new Text("Opt in to analytics"),
-          ),
-        ],
-        mainAxisAlignment: MainAxisAlignment.spaceAround,
-      ),
-      floatingActionButton: new FloatingActionButton(
-        child: new Icon(Icons.add),
-        onPressed: _handleButtonPressed,
-      ),
-    );
-  }
-}
diff --git a/example/flutter_example/pubspec.yaml b/example/flutter_example/pubspec.yaml
deleted file mode 100644
index 52d5cb7..0000000
--- a/example/flutter_example/pubspec.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-name: flutter_example
-description: Create a new Flutter project.
-
-environment:
-  sdk: ">=1.19.0 <2.0.0"
-  flutter: ^0.0.1
-
-dependencies:
-  usage:
-    path: ../..
-  flutter:
-    sdk: flutter
diff --git a/example/ga.dart b/example/ga.dart
index e905ea6..51546fc 100644
--- a/example/ga.dart
+++ b/example/ga.dart
@@ -5,11 +5,9 @@
 /// A simple command-line app to hand-test the usage library.
 library usage_ga;
 
-import 'dart:async';
+import 'package:usage/usage_io.dart';
 
-import 'package:usage/usage.dart';
-
-Future main(List args) async {
+void main(List args) {
   final String DEFAULT_UA = 'UA-55029513-1';
 
   if (args.isEmpty) {
@@ -21,7 +19,7 @@
 
   String ua = args.isEmpty ? DEFAULT_UA : args.first;
 
-  Analytics ga = await Analytics.create(ua, 'ga_test', '1.0');
+  Analytics ga = new AnalyticsIO(ua, 'ga_test', '3.0');
 
   ga.sendScreenView('home').then((_) {
     return ga.sendScreenView('files');
diff --git a/lib/src/usage_impl_flutter.dart b/lib/src/usage_impl_flutter.dart
deleted file mode 100644
index 58f90a3..0000000
--- a/lib/src/usage_impl_flutter.dart
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'dart:async';
-import 'dart:convert' show JSON;
-import 'dart:io';
-
-import 'package:flutter/http.dart' as http;
-import 'package:flutter/services.dart';
-import 'package:path/path.dart' as path;
-
-import '../usage.dart';
-import 'usage_impl.dart';
-
-Future<Analytics> createAnalytics(
-  String trackingId,
-  String applicationName,
-  String applicationVersion, {
-  String analyticsUrl
-}) async {
-    Directory dataDirectory = await PathProvider.getTemporaryDirectory();
-
-    String fileName = '.${applicationName.replaceAll(' ', '_')}';
-    File file = new File(path.join(dataDirectory.path, fileName));
-    await file.create();
-    String contents = await file.readAsString();
-    if (contents.isEmpty) contents = '{}';
-    Map map = JSON.decode(contents);
-
-    return new AnalyticsImpl(
-      trackingId,
-      new FlutterPersistentProperties(applicationName, file, map),
-      new FlutterPostHandler(),
-      applicationName: applicationName,
-      applicationVersion: applicationVersion,
-      analyticsUrl: analyticsUrl
-    );
-}
-
-String _createUserAgent() {
-  // Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en)
-  // Dart/1.8.0-edge.41170 (macos; macos; macos; null)
-  String os = Platform.operatingSystem;
-  String locale = Platform.environment['LANG'];
-  return "Dart/${_dartVersion()} (${os}; ${os}; ${os}; ${locale})";
-}
-
-String _dartVersion() {
-  String ver = Platform.version;
-  int index = ver.indexOf(' ');
-  if (index != -1) ver = ver.substring(0, index);
-  return ver;
-}
-
-class FlutterPostHandler extends PostHandler {
-  final String _userAgent;
-  final HttpClient mockClient;
-
-  FlutterPostHandler({HttpClient this.mockClient}) : _userAgent = _createUserAgent();
-
-  Future sendPost(String url, Map<String, dynamic> parameters) {
-    // Add custom parameters for OS and the Dart version.
-    parameters['cd1'] = Platform.operatingSystem;
-    parameters['cd2'] = 'dart ${_dartVersion()}';
-
-    String data = postEncode(parameters);
-
-    Map<String, String> headers = <String, String>{ 'User-Agent': _userAgent };
-
-    return http.post(url, body: data, headers: headers);
-  }
-}
-
-class FlutterPersistentProperties extends PersistentProperties {
-  File _file;
-  Map _map;
-  FlutterPersistentProperties(String name, this._file, this._map) : super(name);
-
-  dynamic operator[](String key) => _map[key];
-
-  void operator[]=(String key, dynamic value) {
-    if (value == null) {
-      _map.remove(key);
-    } else {
-      _map[key] = value;
-    }
-
-    _file.writeAsString(JSON.encode(_map) + '\n');
-  }
-}
diff --git a/lib/src/usage_impl_html.dart b/lib/src/usage_impl_html.dart
index 6a5922c..184eaa6 100644
--- a/lib/src/usage_impl_html.dart
+++ b/lib/src/usage_impl_html.dart
@@ -6,23 +6,8 @@
 import 'dart:convert' show JSON;
 import 'dart:html';
 
-import '../usage.dart';
 import 'usage_impl.dart';
 
-Future<Analytics> createAnalytics(
-  String trackingId,
-  String applicationName,
-  String applicationVersion, {
-  String analyticsUrl
-}) {
-  return new Future.value(new AnalyticsHtml(
-    trackingId,
-    applicationName,
-    applicationVersion,
-    analyticsUrl: analyticsUrl
-  ));
-}
-
 class AnalyticsHtml extends AnalyticsImpl {
   AnalyticsHtml(String trackingId, String applicationName, String applicationVersion, {
     String analyticsUrl
diff --git a/lib/src/usage_impl_io.dart b/lib/src/usage_impl_io.dart
index fff224a..1a5c73e 100644
--- a/lib/src/usage_impl_io.dart
+++ b/lib/src/usage_impl_io.dart
@@ -8,29 +8,23 @@
 
 import 'package:path/path.dart' as path;
 
-import '../usage.dart';
 import 'usage_impl.dart';
 
-Future<Analytics> createAnalytics(
-  String trackingId,
-  String applicationName,
-  String applicationVersion, {
-  String analyticsUrl
-}) {
-  return new Future.value(new AnalyticsIO(
-    trackingId,
-    applicationName,
-    applicationVersion,
-    analyticsUrl: analyticsUrl
-  ));
-}
-
+/// Create a new Analytics instance.
+///
+/// `trackingId`, `applicationName`, and `applicationVersion` values should be supplied.
+/// `analyticsUrl` is optional, and lets user's substitute their own analytics URL for
+/// the default. `documentDirectory` is where the analytics settings are stored. It
+/// defaults to the user home directory. For regular `dart:io` apps this doesn't need to
+/// be supplied. For Flutter applications, you should pass in a value like
+/// `PathProvider.getApplicationDocumentsDirectory()`.
 class AnalyticsIO extends AnalyticsImpl {
   AnalyticsIO(String trackingId, String applicationName, String applicationVersion, {
-    String analyticsUrl
+    String analyticsUrl,
+    Directory documentDirectory
   }) : super(
     trackingId,
-    new IOPersistentProperties(applicationName),
+    new IOPersistentProperties(applicationName, documentDirPath: documentDirectory?.path),
     new IOPostHandler(),
     applicationName: applicationName,
     applicationVersion: applicationVersion,
@@ -39,18 +33,22 @@
 }
 
 String _createUserAgent() {
-  if (Platform.isMacOS) {
-    return 'Mozilla/5.0 (Macintosh; Intel Mac OS X)';
+  final String locale = getPlatformLocale() ?? '';
+
+  if (Platform.isAndroid) {
+    return 'Mozilla/5.0 (Android; Mobile; ${locale})';
+  } else if (Platform.isIOS) {
+    return 'Mozilla/5.0 (iPhone; U; CPU iPhone OS like Mac OS X; ${locale})';
   } else if (Platform.isMacOS) {
-    return 'Mozilla/5.0 (Windows; Windows)';
+    return 'Mozilla/5.0 (Macintosh; Intel Mac OS X; Macintosh; ${locale})';
+  } else if (Platform.isWindows) {
+    return 'Mozilla/5.0 (Windows; Windows; Windows; ${locale})';
   } else if (Platform.isLinux) {
-    return 'Mozilla/5.0 (Linux; Linux)';
+    return 'Mozilla/5.0 (Linux; Linux; Linux; ${locale})';
   } else {
-    // Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en)
-    // Dart/1.8.0-edge.41170 (macos; macos; macos; null)
+    // Dart/1.8.0 (macos; macos; macos; en_US)
     String os = Platform.operatingSystem;
-    String locale = Platform.environment['LANG'];
-    return "Dart/${_dartVersion()} (${os}; ${os}; ${os}; ${locale})";
+    return "Dart/${getDartVersion()} (${os}; ${os}; ${os}; ${locale})";
   }
 }
 
@@ -60,7 +58,7 @@
   return value == null ? '.' : value;
 }
 
-String _dartVersion() {
+String getDartVersion() {
   String ver = Platform.version;
   int index = ver.indexOf(' ');
   if (index != -1) ver = ver.substring(0, index);
@@ -74,10 +72,6 @@
   IOPostHandler({HttpClient this.mockClient}) : _userAgent = _createUserAgent();
 
   Future sendPost(String url, Map<String, dynamic> parameters) async {
-    // Add custom parameters for OS and the Dart version.
-    parameters['cd1'] = Platform.operatingSystem;
-    parameters['cd2'] = 'dart ${_dartVersion()}';
-
     String data = postEncode(parameters);
 
     HttpClient client = mockClient != null ? mockClient : new HttpClient();
@@ -98,9 +92,10 @@
   File _file;
   Map _map;
 
-  IOPersistentProperties(String name) : super(name) {
+  IOPersistentProperties(String name, { String documentDirPath }) : super(name) {
     String fileName = '.${name.replaceAll(' ', '_')}';
-    _file = new File(path.join(_userHomeDir(), fileName));
+    documentDirPath ??= _userHomeDir();
+    _file = new File(path.join(documentDirPath, fileName));
 
     try {
       if (!_file.existsSync()) _file.createSync();
@@ -129,3 +124,22 @@
     } catch (_) { }
   }
 }
+
+/// Return the string for the platform's locale; return's `null` if the locale
+/// can't be determined.
+String getPlatformLocale() {
+  String locale = Platform.environment['LANG'];
+  if (locale == null) return null;
+
+  if (locale != null) {
+    // Convert `en_US.UTF-8` to `en_US`.
+    int index = locale.indexOf('.');
+    if (index != null) locale = locale.substring(0, index);
+
+    // Convert `en_US` to `en`.
+    index = locale.indexOf('_');
+    if (index != null) locale = locale.substring(0, index);
+  }
+
+  return locale;
+}
diff --git a/lib/usage.dart b/lib/usage.dart
index 53f1812..0d0a5ca 100644
--- a/lib/usage.dart
+++ b/lib/usage.dart
@@ -3,15 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 
 /**
- * `usage` is a wrapper around Google Analytics for both command-line, web, and
- * Flutter apps.
+ * `usage` is a wrapper around Google Analytics for both command-line apps
+ * and web apps.
  *
- * In order to use this library, call the [Analytics.create] static method.
- * You'll get either the command-line, web, or Flutter implementation based on
- * the current platform.
+ * In order to use this library as a web app, import the `analytics_html.dart`
+ * library and instantiate the [AnalyticsHtml] class.
  *
- * When creating a new Analytics instance, you need to provide a Google
- * Analytics tracking ID, the application name, and the application version.
+ * In order to use this library as a command-line app, import the
+ * `analytics_io.dart` library and instantiate the [AnalyticsIO] class.
+ *
+ * For both classes, you need to provide a Google Analytics tracking ID, the
+ * application name, and the application version.
  *
  * Your application should provide an opt-in option for the user. If they
  * opt-in, set the [optIn] field to `true`. This setting will persist across
@@ -24,40 +26,19 @@
 
 import 'dart:async';
 
-import 'src/usage_impl_default.dart'
-   if (dart.library.js) 'src/usage_impl_html.dart'
-   if (dart.library.ui) 'src/usage_impl_flutter.dart'
-   if (dart.library.io) 'src/usage_impl_io.dart'
-   as impl;
-
 // Matches file:/, non-ws, /, non-ws, .dart
 final RegExp _pathRegex = new RegExp(r'file:/\S+/(\S+\.dart)');
 
 /**
- * An interface to a Google Analytics session. You'll get the correct one for
- * your platform by calling the [Analytics.create] static method.
- * [AnalyticsMock] can be used for testing or for some variants of an opt-in
- * workflow.
+ * An interface to a Google Analytics session. [AnalyticsHtml] and [AnalyticsIO]
+ * are concrete implementations of this interface. [AnalyticsMock] can be used
+ * for testing or for some varients of an opt-in workflow.
  *
- * The analytics information is sent on a best-effort basis. Failures to send
- * the GA information will not result in errors from the asynchronous `send`
- * methods.
+ * The analytics information is sent on a best-effort basis. So, failures to
+ * send the GA information will not result in errors from the asynchronous
+ * `send` methods.
  */
 abstract class Analytics {
-  static Future<Analytics> create(
-    String trackingId,
-    String applicationName,
-    String applicationVersion, {
-    String analyticsUrl
-  }) {
-    return impl.createAnalytics(
-      trackingId,
-      applicationName,
-      applicationVersion,
-      analyticsUrl: analyticsUrl
-    );
-  }
-
   /**
    * Tracking ID / Property ID.
    */
diff --git a/lib/usage_html.dart b/lib/usage_html.dart
new file mode 100644
index 0000000..f3dd555
--- /dev/null
+++ b/lib/usage_html.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * In order to use this library import the `usage_html.dart` file and
+ * instantiate the [AnalyticsHtml] class.
+ *
+ * You'll need to provide a Google Analytics tracking ID, the application name,
+ * and the application version.
+ */
+library usage_html;
+
+import 'dart:html';
+
+import 'src/usage_impl.dart';
+import 'src/usage_impl_html.dart';
+
+export 'usage.dart';
+
+/**
+ * An interface to a Google Analytics session, suitable for use in web apps.
+ *
+ * [analyticsUrl] is an optional replacement for the default Google Analytics
+ * URL (`https://www.google-analytics.com/collect`).
+ */
+class AnalyticsHtml extends AnalyticsImpl {
+  AnalyticsHtml(String trackingId, String applicationName, String applicationVersion, {
+    String analyticsUrl
+  }) : super(
+      trackingId,
+      new HtmlPersistentProperties(applicationName),
+      new HtmlPostHandler(),
+      applicationName: applicationName,
+      applicationVersion: applicationVersion,
+      analyticsUrl: analyticsUrl
+  ) {
+    int screenWidth = window.screen.width;
+    int screenHeight = window.screen.height;
+
+    setSessionValue('sr', '${screenWidth}x$screenHeight');
+    setSessionValue('sd', '${window.screen.pixelDepth}-bits');
+    setSessionValue('ul', window.navigator.language);
+  }
+}
diff --git a/lib/usage_io.dart b/lib/usage_io.dart
new file mode 100644
index 0000000..61accf7
--- /dev/null
+++ b/lib/usage_io.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * In order to use this library import the `usage_io.dart` file and
+ * instantiate the [AnalyticsIO] class.
+ *
+ * You'll need to provide a Google Analytics tracking ID, the application name,
+ * and the application version.
+ */
+library usage_io;
+
+import 'src/usage_impl.dart';
+import 'src/usage_impl_io.dart';
+
+export 'usage.dart';
+
+/**
+ * An interface to a Google Analytics session, suitable for use in command-line
+ * applications.
+ *
+ * [analyticsUrl] is an optional replacement for the default Google Analytics
+ * URL (`https://www.google-analytics.com/collect`).
+ */
+class AnalyticsIO extends AnalyticsImpl {
+  AnalyticsIO(String trackingId, String applicationName, String applicationVersion, {
+    String analyticsUrl
+  }) : super(
+    trackingId,
+    new IOPersistentProperties(applicationName),
+    new IOPostHandler(),
+    applicationName: applicationName,
+    applicationVersion: applicationVersion,
+    analyticsUrl: analyticsUrl
+  );
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 1584380..34dd640 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@
 # BSD-style license that can be found in the LICENSE file.
 
 name: usage
-version: 3.0.0-dev.1
+version: 3.0.0
 description: A Google Analytics wrapper for both command-line, web, and Flutter apps.
 homepage: https://github.com/dart-lang/usage
 author: Dart Team <misc@dartlang.org>
diff --git a/readme.md b/readme.md
index e67dd88..9b2331f 100644
--- a/readme.md
+++ b/readme.md
@@ -7,17 +7,38 @@
 [![Build Status](https://travis-ci.org/dart-lang/usage.svg)](https://travis-ci.org/dart-lang/usage)
 [![Coverage Status](https://img.shields.io/coveralls/dart-lang/usage.svg)](https://coveralls.io/r/dart-lang/usage?branch=master)
 
-## Using this library
+## For web apps
 
-In order to use this library, call the `Analytics.create` static method.
-You'll get either the command-line, web, or Flutter implementation based on
-the current platform.
+To use this library as a web app, import the `usage_html.dart` library and
+instantiate the `AnalyticsHtml` class.
 
 When you are creating a new property at [google analytics](https://www.google.com/analytics/)
 make sure to select not the website option, but the **mobile app** option.
 
+## For Flutter apps
+
+Flutter applications can use the `AnalyticsIO` version of this library. They will need
+to specify the documents directory in the constructor, in order to tell the library where
+to save the analytics preferences:
+
+```dart
+import 'package:flutter/services.dart';
+import 'package:usage/usage_io.dart';
+
+void main() {
+  final String UA = ...;
+
+  Analytics ga = new AnalyticsIO(UA, 'ga_test', '3.0',
+    documentsDirectory: PathProvider.getApplicationDocumentsDirectory());
+  ...
+}
+```
+
 ## For command-line apps
 
+To use this library as a command-line app, import the `usage_io.dart` library
+and instantiate the `AnalyticsIO` class.
+
 Note, for CLI apps, the usage library will send analytics pings asynchronously.
 This is useful it that it doesn't block the app generally. It does have one
 side-effect, in that outstanding asynchronous requests will block termination
@@ -39,10 +60,10 @@
 
 ## Using the API
 
-Import the package:
+Import the package (in this example we use the `dart:io` version):
 
 ```dart
-import 'package:usage/usage.dart';
+import 'package:usage/usage_io.dart';
 ```
 
 And call some analytics code:
@@ -50,7 +71,8 @@
 ```dart
 final String UA = ...;
 
-Analytics ga = await Analytics.create(UA, 'ga_test', '1.0');
+Analytics ga = new AnalyticsIO(UA, 'ga_test', '3.0');
+ga.optIn = true;
 
 ga.sendScreenView('home');
 ga.sendException('foo exception');
diff --git a/test/usage_impl_io_test.dart b/test/usage_impl_io_test.dart
index 31e8ac8..98da432 100644
--- a/test/usage_impl_io_test.dart
+++ b/test/usage_impl_io_test.dart
@@ -39,6 +39,16 @@
       expect(props['foo'], null);
     });
   });
+
+  group('usage_impl_io', () {
+    test('getPlatformLocale', () {
+      expect(getPlatformLocale(), isNotNull);
+    });
+
+    test('getDartVersion', () {
+      expect(getDartVersion(), isNotNull);
+    });
+  });
 }
 
 class MockHttpClient implements HttpClient {
diff --git a/tool/travis.sh b/tool/travis.sh
index e6f3aca..a3f3082 100755
--- a/tool/travis.sh
+++ b/tool/travis.sh
@@ -15,11 +15,7 @@
   test/all.dart
 
 # Run the tests.
-dart --conditional_directives -c test/all.dart
-
-# Run the UI/web tests as well.
-#pub build test
-#pub run grinder:test build/test/web.html
+dart -c test/all.dart
 
 # Measure the size of the compiled JS, for the dart:html version of the library.
 dart tool/grind.dart build