Friday, January 22, 2016

Program to Distant Interfaces


For the most part I am content to create Dart examples that run in the SDK. Command-line scripts feel lighter than their browser-based counterparts (and are certainly lighter than their JavaScript compiled counterparts). Plus, I can usually convert my scripts to run on DartPad. But I feel compelled to try my remote proxy pattern websocket implementation in the browser—not because I fear something going wrong. Rather, I am curious to explore coding to an interface in two different locations.

I create a simple web/index.html page to hold a remote proxy car instance. As UIs go, this barely qualifies as bare minimum:



Still, it should be sufficient to test things out.

Surprisingly (at least to me) things go amiss here. I had forgotten that the WebSocket class in the dart:html library differs from the one that I had been using in dart:io. The former conforms to the standard browser websocket interface where the latter is a standard Stream implementation.

Aside from the annoyance of two different interfaces for the same object, there are some practical differences for which I have to account. In the drive.dart script pulled into the web page via <script> tag, I have to add a Completer to await the websocket being ready for use:
import 'dart:async' show Completer;
import 'dart:html' show WebSocket, document, query;

import 'package:proxy_code/web_car.dart';

main() async {
  var socket = new WebSocket('ws://localhost:4040/ws');

  var _c = new Completer();
  socket.onOpen.listen((_){ _c.complete(); });
  await _c.future;

  // Create proxy car with send/receive streams
  ProxyCar car = new ProxyCar(socket);
  // ...
}
The web_car.dart file with the web remote proxy implementation also needs to change slightly, listening to the onMessage property instead of directly to the socket:
class ProxyCar implements AsyncAuto {
  // ...
  ProxyCar(this._socket) {
    _socket.onMessage.listen((e) {
      _state = e.data;
    });
  }
  // ...
}
But once those minor differences are sorted out, everything just works. Darn it.

I hook up the buttons in the page to event listeners in the Dart script:
  document.query('#drive').onClick.listen((_) async {
    print('Drive');
    await car.drive();
    updateState(car.state);
  });
Clicking the button tells the proxy car to drive, which then results in an update to the state from the websocket. The result of this is included in the <pre> tag below the buttons:



And, indeed, the web socket server from the other night is seeing these messages and passing them along to the real car:
$ ./bin/server.dart
[AsyncCar] received: stop
[AsyncCar] received: drive
In the sum total of things, that is a good thing. I was able to take my command-line remote proxy package and use it in the browser with only minor changes. I hadn't counted on those minor changes between websocket implementations. And I had expected that I would need to move the common interface out into a separate file that could be imported into both dart:io and dart:html libraries. That turned out not be necessary--thanks to Dart's beautiful libraries and creating the websockets in the client instead of the libraries.

Since libraries work so well in this case, I have no doubt that they will work when the common subject interface is pulled out into its own file. I do it anyway, creating interface.dart with:
library car;

import 'dart:async' show Future;

// Subject
abstract class AsyncAuto implements Automobile {
  String get state;
  Future drive();
  Future stop();
}
I then import that into both real_car.dart and web_car.dart:
library car;

import 'dart:async' show Future, Stream;
import 'dart:html' show WebSocket;

import 'interface.dart';

// Proxy Subject
class ProxyCar implements AsyncAuto {
  // ...
}
That makes the type analyzer happy and positions me well should I ever decide to create the specific websockets implementations in the dart:html and dart:io libraries. In other words, no matter how I decide I want to code those implementations, I have easy access to the interface to program against.


Day #72


1 comment: