ether: sky+syncbase!

Change-Id: I8620a925c28f3eb35306da8e8dc4bf8fb786bcde
diff --git a/Makefile b/Makefile
index af463ae..f51a72d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,15 +1,17 @@
 SHELL := /bin/bash -euo pipefail
 PWD := $(shell pwd)
 V23_GOPATH := $(shell echo `v23 run env | grep GOPATH | cut -d\= -f2`)
-DART_FILES := $(shell find dart/bin dart/lib dart/test -name "*.dart" -not -path "dart/lib/gen/*")
+DART_FILES := $(shell find dart/bin dart/lib dart/test sky_demo/lib -name "*.dart" -not -path "dart/lib/gen/*")
 GO_FILES := $(shell find go/src -name "*.go")
 V23_GO_FILES := $(shell find $(V23_ROOT) -name "*.go")
 
 # Flags for Syncbase service running as Mojo service.
 # See v.io/x/ref/runtime/internal/mojo_util.go for the wonderful magic that
 # makes this work.
+# If you change these flags, be sure to do a "make clean", since the flag values
+# get bound at compile time.
 SYNCBASED_ADDR := 127.0.0.1:4002
-V23_MOJO_FLAGS := --v=5 --root-dir=/tmp/syncbase_mojo --v23.tcp.address=$(SYNCBASED_ADDR) --v23.permissions.literal={\"Admin\":{\"In\":[\"...\"]},\"Write\":{\"In\":[\"...\"]},\"Read\":{\"In\":[\"...\"]},\"Resolve\":{\"In\":[\"...\"]},\"Debug\":{\"In\":[\"...\"]}}
+V23_MOJO_FLAGS := --v=0 --v23.tcp.address=$(SYNCBASED_ADDR) --v23.permissions.literal={\"Admin\":{\"In\":[\"...\"]},\"Write\":{\"In\":[\"...\"]},\"Read\":{\"In\":[\"...\"]},\"Resolve\":{\"In\":[\"...\"]},\"Debug\":{\"In\":[\"...\"]}}
 
 # MOUNTTABLE_ADDR := 127.0.0.1:4001
 ifdef MOUNTTABLE_ADDR
@@ -58,7 +60,7 @@
 
 	THIRD_PARTY_LIBS := $(V23_ROOT)/third_party/cout/linux_amd64
 
-	V23_MOJO_FLAGS += --root-dir=/tmp/syncbase_data --alsologtostderr=false
+	V23_MOJO_FLAGS += --root-dir=/tmp/syncbase_data
 endif
 
 GOPATH := $(V23_GOPATH):$(MOJO_DIR):$(MOJO_DIR)/third_party/go:$(MOJO_BUILD_DIR)/gen/go:$(PWD)/go:$(PWD)/gen/go
@@ -163,16 +165,21 @@
 
 # Lints src and test files with dartanalyzer. This takes a few seconds.
 .PHONY: dartanalyzer
-dartanalyzer: dart/packages gen-mojom
+dartanalyzer: dart/packages sky_demo/packages gen-mojom
 	# TODO(nlacasse): Fix dart mojom binding generator so it does not produce
 	# files that violate dartanalyzer.  For now, we use "grep -v" to hide all
-	# hints from *.mojom.dart files.
-	cd dart && dartanalyzer bin/*.dart lib/*.dart test/*.dart | grep -v '\[hint\].*\.mojom\.dart'
+	# hints and warnings from *.mojom.dart files.
+	cd dart && dartanalyzer bin/*.dart lib/*.dart test/*.dart | grep -v "\.mojom\.dart, line"
+	cd sky_demo && dartanalyzer lib/*.dart | grep -v "\.mojom\.dart, line"
 
 # Installs dart dependencies.
 dart/packages: dart/pubspec.yaml
 	cd dart && pub get
 
+# Installs dart dependencies.
+sky_demo/packages: sky_demo/pubspec.yaml
+	cd sky_demo && pub get
+
 .PHONY: run-syncbase-example
 run-syncbase-example: $(ETHER_BUILD_DIR)/syncbase_server.mojo dart/packages dart/lib/gen/dart-pkg/mojom/lib/mojo/syncbase.mojom.dart | env-check
 	$(MOJO_DIR)/src/mojo/devtools/common/mojo_run --config-file $(PWD)/mojoconfig $(MOJO_SHELL_FLAGS) $(MOJO_ANDROID_FLAGS) https://mojo.v.io/syncbase_example.dart
@@ -181,9 +188,9 @@
 run-echo-example: $(ETHER_BUILD_DIR)/echo_server.mojo dart/packages dart/lib/gen/dart-pkg/mojom/lib/mojo/echo.mojom.dart | env-check
 	$(MOJO_DIR)/src/mojo/devtools/common/mojo_run --config-file $(PWD)/mojoconfig $(MOJO_SHELL_FLAGS) $(MOJO_ANDROID_FLAGS) https://mojo.v.io/echo_example.dart
 
-.PHONY: run-sky-echo
-run-sky-echo: $(ETHER_BUILD_DIR)/echo_server.mojo dart/packages dart/lib/gen/dart-pkg/mojom/lib/mojo/echo.mojom.dart | env-check
-	$(MOJO_DIR)/src/mojo/devtools/common/mojo_run --config-file $(PWD)/sky_echo/mojoconfig $(MOJO_SHELL_FLAGS) $(MOJO_ANDROID_FLAGS) 'mojo:window_manager https://mojo.v.io/sky_echo/lib/main.dart'
+.PHONY: run-sky-demo
+run-sky-demo: $(ETHER_BUILD_DIR)/echo_server.mojo sky_demo/packages $(ETHER_BUILD_DIR)/syncbase_server.mojo dart/lib/gen/dart-pkg/mojom/lib/mojo/echo.mojom.dart dart/lib/gen/dart-pkg/mojom/lib/mojo/syncbase.mojom.dart | env-check
+	$(MOJO_DIR)/src/mojo/devtools/common/mojo_run --config-file $(PWD)/sky_demo/mojoconfig $(MOJO_SHELL_FLAGS) $(MOJO_ANDROID_FLAGS) 'mojo:window_manager https://mojo.v.io/sky_demo/lib/main.dart'
 
 .PHONY: test
 test: dart/packages $(ETHER_BUILD_DIR)/echo_server.mojo $(ETHER_BUILD_DIR)/syncbase_server.mojo gen-mojom | env-check
@@ -224,4 +231,4 @@
 .PHONY: veryclean
 veryclean: clean
 	rm -rf gen
-	rm -rf dart/{.packages,pubspec.lock,packages}
+	rm -rf {dart,sky_demo}/{.packages,pubspec.lock,packages}
diff --git a/dart/bin/echo_example.dart b/dart/bin/echo_example.dart
index 4d62e38..226f1fb 100755
--- a/dart/bin/echo_example.dart
+++ b/dart/bin/echo_example.dart
@@ -6,13 +6,11 @@
   InitializedApplication app = new InitializedApplication.fromHandle(args[0]);
   await app.initialized;
 
-  String url = 'https://mojo.v.io/echo_server.mojo';
-
-  EchoClient c = new EchoClient(app, url);
+  EchoClient c = new EchoClient(
+      app.connectToService, 'https://mojo.v.io/echo_server.mojo');
 
   String input = 'foobar';
   String output = await c.echo(input);
-
   print('in=$input out=$output match=${input == output}');
 
   await c.close();
diff --git a/dart/bin/syncbase_example.dart b/dart/bin/syncbase_example.dart
index a2d7be3..21a6627 100644
--- a/dart/bin/syncbase_example.dart
+++ b/dart/bin/syncbase_example.dart
@@ -6,9 +6,9 @@
   InitializedApplication app = new InitializedApplication.fromHandle(args[0]);
   await app.initialized;
 
-  String url = 'https://mojo.v.io/syncbase_server.mojo';
+  SyncbaseClient c = new SyncbaseClient(
+      app.connectToService, 'https://mojo.v.io/syncbase_server.mojo');
 
-  SyncbaseClient c = new SyncbaseClient(app, url);
   bool exists = await c.app('foo').exists();
   print('app(foo).exists(): $exists');
 
diff --git a/dart/lib/echo_client.dart b/dart/lib/echo_client.dart
index a32e9b4..64869be 100644
--- a/dart/lib/echo_client.dart
+++ b/dart/lib/echo_client.dart
@@ -2,23 +2,25 @@
 
 import 'dart:async';
 
-import 'package:mojo/application.dart' show Application;
+import 'package:mojo/bindings.dart' as bindings;
 
 import 'gen/dart-gen/mojom/lib/mojo/echo.mojom.dart' as mojom;
 
-class EchoClient {
-  final Application _app;
-  final mojom.EchoProxy _proxy;
-  final String url;
+typedef void ConnectToServiceFn(String url, bindings.ProxyBase proxy);
 
-  Future close({bool immediate: false}) {
-    return _proxy.close(immediate: immediate);
+class EchoClient {
+  final mojom.EchoProxy _proxy;
+
+  EchoClient(ConnectToServiceFn cts, String url)
+      : _proxy = new mojom.EchoProxy.unbound() {
+    print('connecting to $url');
+    cts(url, _proxy);
+    print('connected');
   }
 
-  EchoClient(this._app, this.url) : _proxy = new mojom.EchoProxy.unbound() {
-    print('connecting to $url');
-    _app.connectToService(url, _proxy);
-    print('connected');
+  // TODO(nlacasse): Is this necessary?
+  Future close({bool immediate: false}) {
+    return _proxy.close(immediate: immediate);
   }
 
   Future<String> echo(String s) async {
diff --git a/dart/lib/src/app.dart b/dart/lib/src/app.dart
index f47cfdf..6eb61ab 100644
--- a/dart/lib/src/app.dart
+++ b/dart/lib/src/app.dart
@@ -13,13 +13,11 @@
   Future create(mojom.Perms perms) async {
     var v = await _proxy.ptr.appCreate(fullName, perms);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future delete() async {
     var v = await _proxy.ptr.appDelete(fullName);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future<bool> exists() async {
@@ -31,7 +29,6 @@
   Future<mojom.Perms> getPermissions() async {
     var v = await _proxy.ptr.appGetPermissions(fullName);
     if (isError(v.err)) throw v.err;
-
     // TODO(nlacasse): We need to return the version too.  Create a struct type
     // that combines perms and version?
     return v.perms;
@@ -40,6 +37,5 @@
   Future setPermissions(mojom.Perms perms, String version) async {
     var v = await _proxy.ptr.appSetPermissions(fullName, perms, version);
     if (isError(v.err)) throw v.err;
-    return;
   }
 }
diff --git a/dart/lib/src/nosql/database.dart b/dart/lib/src/nosql/database.dart
index 3acee57..afb76ea 100644
--- a/dart/lib/src/nosql/database.dart
+++ b/dart/lib/src/nosql/database.dart
@@ -24,13 +24,11 @@
   Future create(mojom.Perms perms) async {
     var v = await _proxy.ptr.dbCreate(fullName, perms);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future delete() async {
     var v = await _proxy.ptr.dbDelete(fullName);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future<bool> exists() async {
@@ -68,19 +66,16 @@
   Future commit() async {
     var v = await _proxy.ptr.dbCommit(fullName);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future abort() async {
     var v = await _proxy.ptr.dbAbort(fullName);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future<mojom.Perms> getPermissions() async {
     var v = await _proxy.ptr.dbGetPermissions(fullName);
     if (isError(v.err)) throw v.err;
-
     // TODO(nlacasse): We need to return the version too.  Create a struct type
     // that combines perms and version?
     return v.perms;
@@ -89,7 +84,6 @@
   Future setPermissions(mojom.Perms perms, String version) async {
     var v = await _proxy.ptr.dbSetPermissions(fullName, perms, version);
     if (isError(v.err)) throw v.err;
-    return;
   }
 }
 
diff --git a/dart/lib/src/nosql/row.dart b/dart/lib/src/nosql/row.dart
index 2968eb6..882f0b5 100644
--- a/dart/lib/src/nosql/row.dart
+++ b/dart/lib/src/nosql/row.dart
@@ -19,12 +19,10 @@
   Future put(List<int> value) async {
     var v = await _proxy.ptr.rowPut(fullName, value);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future delete() async {
     var v = await _proxy.ptr.rowDelete(fullName);
     if (isError(v.err)) throw v.err;
-    return;
   }
 }
diff --git a/dart/lib/src/nosql/syncgroup.dart b/dart/lib/src/nosql/syncgroup.dart
index 433e37e..65ecc68 100644
--- a/dart/lib/src/nosql/syncgroup.dart
+++ b/dart/lib/src/nosql/syncgroup.dart
@@ -11,37 +11,31 @@
       mojom.SyncGroupSpec spec, mojom.SyncGroupMemberInfo myInfo) async {
     var v = await _proxy.ptr.dbCreateSyncGroup(_dbName, name, spec, myInfo);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future join(mojom.SyncGroupMemberInfo myInfo) async {
     var v = await _proxy.ptr.dbJoinSyncGroup(_dbName, name, myInfo);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future leave() async {
     var v = await _proxy.ptr.dbLeaveSyncGroup(_dbName, name);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future destroy() async {
     var v = await _proxy.ptr.dbDestroySyncGroup(_dbName, name);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future eject(String memberName) async {
     var v = await _proxy.ptr.dbEjectFromSyncGroup(_dbName, name, memberName);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future<mojom.SyncGroupSpec> getSpec() async {
     var v = await _proxy.ptr.dbGetSyncGroupSpec(_dbName, name);
     if (isError(v.err)) throw v.err;
-
     // TODO(nlacasse): We need to return the version too.  Create a struct type
     // that combines spec and version?
     return v.spec;
@@ -50,7 +44,6 @@
   Future setSpec(mojom.SyncGroupSpec spec, String version) async {
     var v = await _proxy.ptr.dbSetSyncGroupSpec(_dbName, name, spec, version);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future<Map<String, mojom.SyncGroupMemberInfo>> getMembers() async {
diff --git a/dart/lib/src/nosql/table.dart b/dart/lib/src/nosql/table.dart
index 19604f7..d3409ec 100644
--- a/dart/lib/src/nosql/table.dart
+++ b/dart/lib/src/nosql/table.dart
@@ -11,13 +11,11 @@
   Future create(mojom.Perms perms) async {
     var v = await _proxy.ptr.tableCreate(fullName, perms);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future delete() async {
     var v = await _proxy.ptr.tableDelete(fullName);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future<bool> exists() async {
@@ -29,7 +27,6 @@
   Future deleteRowRange(List<int> start, List<int> limit) async {
     var v = await _proxy.ptr.tableDeleteRowRange(fullName, start, limit);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Stream<mojom.KeyValue> scan(List<int> start, List<int> limit) {
@@ -62,13 +59,11 @@
   Future setPermissions(String prefix, mojom.Perms perms) async {
     var v = await _proxy.ptr.tableSetPermissions(fullName, prefix, perms);
     if (isError(v.err)) throw v.err;
-    return;
   }
 
   Future deletePermissions(String prefix) async {
     var v = await _proxy.ptr.tableDeletePermissions(fullName, prefix);
     if (isError(v.err)) throw v.err;
-    return;
   }
 }
 
diff --git a/dart/lib/syncbase_client.dart b/dart/lib/syncbase_client.dart
index 3d48c41..a61f687 100644
--- a/dart/lib/syncbase_client.dart
+++ b/dart/lib/syncbase_client.dart
@@ -2,7 +2,7 @@
 
 import 'dart:async';
 
-import 'package:mojo/application.dart' show Application;
+import 'package:mojo/bindings.dart' as bindings;
 
 import 'gen/dart-gen/mojom/lib/mojo/syncbase.mojom.dart' as mojom;
 
@@ -20,26 +20,26 @@
 part 'src/nosql/syncgroup.dart';
 part 'src/nosql/table.dart';
 
+typedef void ConnectToServiceFn(String url, bindings.ProxyBase proxy);
+
 bool isError(mojom.Error err) {
   return err != null && err.id != '';
 }
 
 class SyncbaseClient {
-  final Application _mojoApp;
   final mojom.SyncbaseProxy _proxy;
-  final String url;
 
-  SyncbaseClient(this._mojoApp, this.url)
+  SyncbaseClient(ConnectToServiceFn cts, String url)
       : _proxy = new mojom.SyncbaseProxy.unbound() {
     print('connecting to $url');
-    _mojoApp.connectToService(url, _proxy);
+    cts(url, _proxy);
     print('connected');
   }
 
   // Closes the connection to the syncbase server.
   // TODO(nlacasse): Is this necessary?
   Future close({bool immediate: false}) {
-    return _proxy.close();
+    return _proxy.close(immediate: immediate);
   }
 
   // app returns the app with the given name, which should not contain slashes.
@@ -48,7 +48,6 @@
   Future<mojom.Perms> getPermissions() async {
     var v = await _proxy.ptr.serviceGetPermissions();
     if (isError(v.err)) throw v.err;
-
     // TODO(nlacasse): We need to return the version too.  Create a struct type
     // that combines perms and version?
     return v.perms;
@@ -57,6 +56,5 @@
   Future setPermissions(mojom.Perms perms, String version) async {
     var v = await _proxy.ptr.serviceSetPermissions(perms, version);
     if (isError(v.err)) throw v.err;
-    return;
   }
 }
diff --git a/dart/test/echo_test.dart b/dart/test/echo_test.dart
index 00101eb..00bbf0c 100644
--- a/dart/test/echo_test.dart
+++ b/dart/test/echo_test.dart
@@ -11,9 +11,8 @@
   InitializedApplication app = new InitializedApplication.fromHandle(args[0]);
   await app.initialized;
 
-  String url = 'https://mojo.v.io/echo_server.mojo';
-
-  EchoClient c = new EchoClient(app, url);
+  EchoClient c = new EchoClient(
+      app.connectToService, 'https://mojo.v.io/echo_server.mojo');
 
   tearDown(() {
     app.resetConnections();
diff --git a/dart/test/syncbase_test.dart b/dart/test/syncbase_test.dart
index 2b9fe72..f55ae4a 100755
--- a/dart/test/syncbase_test.dart
+++ b/dart/test/syncbase_test.dart
@@ -17,9 +17,8 @@
   InitializedApplication app = new InitializedApplication.fromHandle(args[0]);
   await app.initialized;
 
-  String url = 'https://mojo.v.io/syncbase_server.mojo';
-
-  SyncbaseClient c = new SyncbaseClient(app, url);
+  SyncbaseClient c = new SyncbaseClient(
+      app.connectToService, 'https://mojo.v.io/syncbase_server.mojo');
 
   tearDown(() {
     app.resetConnections();
diff --git a/sky_demo/lib/main.dart b/sky_demo/lib/main.dart
new file mode 100644
index 0000000..003f819
--- /dev/null
+++ b/sky_demo/lib/main.dart
@@ -0,0 +1,113 @@
+import 'dart:async';
+import 'dart:convert' show UTF8;
+
+import 'package:sky/mojo/embedder.dart' show embedder;
+import 'package:sky/widgets.dart';
+
+import 'package:ether/echo_client.dart' show EchoClient;
+import 'package:ether/syncbase_client.dart'
+    show Perms, SyncbaseClient, SyncbaseTable;
+
+log(String msg) {
+  DateTime now = new DateTime.now();
+  print('$now $msg');
+}
+
+Perms emptyPerms() => new Perms()..json = '{}';
+
+class DemoApp extends App {
+  final EchoClient _echoClient;
+  final SyncbaseClient _syncbaseClient;
+
+  DemoApp()
+      : _echoClient = new EchoClient(
+            embedder.connectToService, 'https://mojo.v.io/echo_server.mojo'),
+        _syncbaseClient = new SyncbaseClient(embedder.connectToService,
+            'https://mojo.v.io/syncbase_server.mojo');
+
+  int seq = 0;
+  SyncbaseTable tb;
+  String sendMsg, recvMsg, putStr, getStr;
+
+  Future doEcho() async {
+    log('DemoApp.doEcho');
+
+    setState(() {
+      sendMsg = seq.toString();
+      recvMsg = '';
+    });
+    seq++;
+    log('setState sendMsg done');
+
+    String recvMsgAsync = await _echoClient.echo(sendMsg);
+
+    setState(() {
+      recvMsg = recvMsgAsync;
+    });
+    log('setState recvMsg done');
+  }
+
+  Future doSyncbaseInit() async {
+    log('DemoApp.doSyncbaseInit');
+    if (tb != null) {
+      log('syncbase already initialized');
+      return;
+    }
+    // TODO(sadovsky): Handle "already exists" case.
+    var app = _syncbaseClient.app('app');
+    await app.create(emptyPerms());
+    var db = app.noSqlDatabase('db');
+    await db.create(emptyPerms());
+    var table = db.table('table');
+    await table.create(emptyPerms());
+    tb = table;
+    log('syncbase is now initialized');
+  }
+
+  Future doPutGet() async {
+    log('DemoApp.doPutGet');
+    await doSyncbaseInit();
+
+    setState(() {
+      putStr = seq.toString();
+      getStr = '';
+    });
+    seq++;
+    log('setState putStr done');
+
+    // TODO(sadovsky): Switch to tb.put/get once they exist.
+    var row = tb.row('key');
+    await row.put(UTF8.encode(putStr));
+    var getBytes = await row.get();
+
+    setState(() {
+      getStr = UTF8.decode(getBytes);
+    });
+    log('setState getStr done');
+  }
+
+  // TODO(sadovsky): I don't think Sky calls App.close().
+  Future close({bool immediate: false}) async {
+    log('DemoApp.close');
+    await _echoClient.close(immediate: immediate);
+    await _syncbaseClient.close(immediate: immediate);
+  }
+
+  Widget build() {
+    return new Container(
+        decoration:
+            const BoxDecoration(backgroundColor: const Color(0xFF00ACC1)),
+        child: new Flex([
+          new RaisedButton(child: new Text('doEcho'), onPressed: doEcho),
+          new Text('sendMsg: $sendMsg'),
+          new Text('recvMsg: $recvMsg'),
+          new RaisedButton(child: new Text('doPutGet'), onPressed: doPutGet),
+          new Text('putStr: $putStr'),
+          new Text('getStr: $getStr')
+        ], direction: FlexDirection.vertical));
+  }
+}
+
+void main() {
+  runApp(new DemoApp());
+}
diff --git a/sky_echo/mojoconfig b/sky_demo/mojoconfig
similarity index 75%
rename from sky_echo/mojoconfig
rename to sky_demo/mojoconfig
index 74d997d..ab18775 100644
--- a/sky_echo/mojoconfig
+++ b/sky_demo/mojoconfig
@@ -6,13 +6,13 @@
       'host': 'https://mojo.v.io/',
       'mappings': [
         ('packages/', [
-          # For sky/widgets.dart.
-          '@{ETHER_DIR}/sky_echo/packages'
+          # For sky_demo packages.
+          '@{ETHER_DIR}/sky_demo/packages',
         ]),
         ('', [
-          # For echo_server.mojo.
+          # For echo_server.mojo and syncbase_server.mojo.
           '@{ETHER_BUILD_DIR}',
-          # For sky_echo/lib/main.dart.
+          # For sky_demo/lib/main.dart.
           '@{ETHER_DIR}',
         ]),
       ],
diff --git a/sky_demo/pubspec.yaml b/sky_demo/pubspec.yaml
new file mode 100644
index 0000000..ca5894e
--- /dev/null
+++ b/sky_demo/pubspec.yaml
@@ -0,0 +1,8 @@
+name: sky_demo
+dependencies:
+  sky: any
+  sky_tools: any
+  ether: any
+dependency_overrides:
+  ether:
+    path: ../dart
diff --git a/sky_echo/lib/main.dart b/sky_echo/lib/main.dart
deleted file mode 100644
index e461537..0000000
--- a/sky_echo/lib/main.dart
+++ /dev/null
@@ -1,97 +0,0 @@
-import 'package:sky/widgets.dart';
-
-class HelloWorldApp extends App {
-  Widget build() {
-    return new Center(child: new Text('Hello, world!'));
-  }
-}
-
-void main() {
-  runApp(new HelloWorldApp());
-}
-
-
-/*
-import 'package:sky/widgets.dart';
-
-import 'dart:async';
-
-import 'package:mojo/core.dart';
-import 'package:mojo/bindings.dart';
-import 'package:sky/mojo/embedder.dart' show embedder;
-
-import 'package:ether/echo_client.dart' show EchoClient;
-
-class EchoApp extends App {
-  EchoApp() : super();
-
-  EchoClient c = new EchoClient(app, 'https://mojo.v.io/echo_server.mojo');
-  String sentMsg = '';
-  String recvMsg = '';
-  bool connected = false;
-
-  void _connect() {
-    if (connected) return;
-    //embedder.connectToService('mojo:echo_server', echoProxy);
-    //embedder.connectToService('https://core.mojoapps.io/go_echo_server.mojo', echoProxy); // works with echo_server.mojo, but must use --enable-multiprocess for this one
-    embedder.connectToService('https://core.mojoapps.io/go_forward_echo_server.mojo', echoProxy);
-
-    connected = true;
-  }
-
-  Future doEcho({bool immediate: false}) async {
-    setState(() {
-      sendMsg = 'hello';
-    });
-    print('Sent message $sendMsg');
-
-    recvMsg = await c.echo(sendMsg);
-    _connect();
-    String msg = 'Hello ' + recvMsg;
-    setState(() {
-      sentMsg = msg;
-      print('Sending message $sentMsg');
-    });
-    try {
-      //final EchoEchoStringResponseParams result = await echoProxy.ptr.echoString(msg);
-      String endpoint = '/@5@wsh@172.17.166.74:33841@cbf4008c9abb8a430b1b455058e1e7ba@s@alexfandrianto@alexfandrianto0.mtv.corp.google.com-18361@@/mojo:go_echo_server/mojo::examples::Echo';
-      final ForwardEchoEchoForwardResponseParams result = await echoProxy.ptr.echoForward(msg, endpoint);
-
-      setState(() {
-        recvMsg = result.value;
-        print('Got message $recvMsg');
-      });
-    } catch(e) {
-      print('Error echoing: ' + e.toString());
-      return false;
-    }
-    return true;
-  }
-
-  Future close({bool immediate: false}) async {
-    await echoProxy.close(immediate: immediate);
-    return;
-  }
-
-  Widget build() {
-    return new Container(
-      decoration: const BoxDecoration(
-        backgroundColor: const Color(0xFF00ACC1)
-      ),
-      child: new Flex([
-        new RaisedButton(
-          child: new Text('Click here'),
-          onPressed: doEcho
-        ),
-        new Text('Sent message $sentMsg'),
-        new Text('Received message $recvMsg')
-      ],
-      direction: FlexDirection.vertical)
-    );
-  }
-}
-
-void main() {
-  runApp(new EchoApp());
-}
-*/
diff --git a/sky_echo/pubspec.yaml b/sky_echo/pubspec.yaml
deleted file mode 100644
index 9d18ee0..0000000
--- a/sky_echo/pubspec.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-name: sky_echo
-dependencies:
-  sky: any
-  sky_tools: any
diff --git a/tools/sync.sh b/tools/sync.sh
new file mode 100755
index 0000000..c0b768c
--- /dev/null
+++ b/tools/sync.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+####################
+# Mojo
+
+cd $MOJO_DIR/src
+git pull
+gclient sync
+# Build for desktop.
+./mojo/tools/mojob.py gn
+./mojo/tools/mojob.py build
+# Build for Android.
+./mojo/tools/mojob.py gn --android
+./mojo/tools/mojob.py build --android
+
+####################
+# Sky
+
+cd $SKY_DIR/src
+git pull
+gclient sync
+# Build for desktop.
+./sky/tools/gn
+ninja -C out/Debug
+# Build for Android.
+./sky/tools/gn --android
+ninja -C out/android_Debug