Creating an Iterative Development Environment for Dart Chrome Packaged Apps

Originally posted on Google+ by +Damon Douglas

This article is intended for those with an existing familiarity of Dart, Chrome Packaged Apps (CPA), and Google App Engine (GAE).  +Chris Buckett describes how to convert a Dart application into a CPA (see article).

The problem with developing CPAs in Dart is the need to compile to javascript prior to deploying in this context.  With every change to the application, we must re-compile.  This article shows how one may leverage the Chromium browser that accompanies the Dart Editor install to create an iterative development environment.  Quick small edits on the Dart side lead to immediate changes to the CPA.  The additional value of this derives from the ability to make cross origin requests, which this article will show with a localhost GAE python application.

Step 1.  Create Web Dart Application

Create a new dart web application and change the dart file to as shown.

import 'dart:html';

final DivElement output = query("#output");

final ButtonElement client_test = query("#client_test");

void main() {

  output.text = "Hello Chrome";

}

Note that the “client test” button will not do anything at this point.

Step 2.  Reference dart.js to the copied file in the containing folder.

The following is the full folder contents in this example:

hello_chrome

        hello_chrome.css

        hello_chrome.dart

        hello_chrome.html

        dart.js

        background.js

        manefest.json

As described, copy “dart.js” into the folder that contains the referencing html file for the application and change the reference to dart.js:

<script type="text/javascript" src="dart.js"></script>

Re-running the application in Chromium should show no change or errors from the previous version.  I loaded the html file directly instead of through the typical editor generated 127.0.0.1 host as this is a similar context as the CPA.

<!DOCTYPE html>

<html>

  <head>

    <meta charset="utf-8">

    <title>Hello_chrome</title>

    <link rel="stylesheet" href="hello_chrome.css">

  </head>

  <body>

    <h1>Hello_chrome</h1>

    <button id="client_test">client test</button>

    <br/><div id="output"></div>

    <script type="application/dart" src="hello_chrome.dart"></script>

    <script type="text/javascript" src="dart.js"></script>

  </body>

</html>

Step 3.  Reference the Main Html File in background.js.

Reference the main html file in the background.js file:

chrome.app.runtime.onLaunched.addListener(function() {

  chrome.app.window.create('hello_chrome.html', {

    'width': 400,

    'height': 800

  });

});

This tells the CPA to load the html when launching the application.

Step 4.  Create the manifest.json file.

Reference background.js and list permissions to “http://www.google.com/*” and “http://localhost:8080/*”.  The first will be used to test a simple HttpRequest cross origin call to an established website.  The second will be used to make a cross origin call to the GAE application.

{

  "name": "Hello World!",

  "description": "My first packaged app.",

  "version": "0.1",

  "app": {

    "background": {

      "scripts": ["background.js"]

    }

  },

  "icons": {

    "16": "icons/icon_16.png",

    "48": "icons/icon_48.png",

    "128": "icons/icon_128.png"

  },

  "permissions": [

    "http://www.google.com/*",

    "http://localhost:8080/*"

  ]

}

Step 5.  Load Unpacked Extension into Chromium.

Selecting first “Developer Mode” in chrome://extensions we point to our project folder.  Launching the CPA shows our familiar application:

Experiment for yourself that changing the output.text in your main in fact changes its display on the CPA by simply reloading and launching the application.

Step 6.  Code Http Request in Dart.

Now we will have the “client test” button do something.  Change the dart file to the following, adding the button click handler and http request to “http://www.google.com”.

import 'dart:html';

final DivElement output = query("#output");

final ButtonElement client_test = query("#client_test");

void main() {

  output.text = "Hello Chrome";

 

  client_test.on.click.add(client_test_clicked);

 

}

void client_test_clicked(Event e) {

  var request = new HttpRequest();

 

  request.on.loadEnd.add((Event e) {

    if (request.status == 200) {

      output.text = request.responseText;

           

    } else {

      output.text = "Error ${request.status}: ${request.statusText}";

    }

  });

 

  request.open("GET","http://www.google.com");

 

  request.send();

}

Note that when you load the html on its own in Chromium outside the CPA context, the “client_test” button still doesn’t do anything.  If you happen to run your application from the editor and click the button, you will see a cross origin request error.  Since, we gave the cross origin permission in the manifest.json file, re-loading and running the CPA will show the results of the request:

Step 7.  Create GAE Python Application

We create a simple web request handler, configuring app.yaml to handle “/client/test”.  The handler simply responds with a JSON {“foo”:”bar”}.  Make sure that the port selected when creating the application is the same you use in the url of the manifest.json of Step 4.  I happen to use 8080.  

client.py:

import webapp2

import simplejson

class TestHandler(webapp2.RequestHandler):

    def get(self):

        result = {'foo':'bar'}

        self.response.out.write(simplejson.dumps(result))

       

app = webapp2.WSGIApplication([

    ('/client/test', TestHandler)

], debug=True)

app.yaml:

- url: /client.*

  script: client.app

First test the application outside of your dart app in a regular browser.  Since my port choice was 8080, I simply load “http://localhost:8080/client/test” and it responds:

Step 8.  Change Dart Http Request

We now change the dart http request to point to our GAE application.  Change the dart file to the following, setting the button click handler and http request to “http://localhost:8080/client/test”.

import 'dart:html';

final DivElement output = query("#output");

final ButtonElement client_test = query("#client_test");

void main() {

  output.text = "Hello Chrome";

 

  client_test.on.click.add(client_test_clicked);

 

}

void client_test_clicked(Event e) {

  var request = new HttpRequest();

 

  request.on.loadEnd.add((Event e) {

    if (request.status == 200) {

      output.text = request.responseText;

           

    } else {

      output.text = "Error ${request.status}: ${request.statusText}";

    }

  });

 

  request.open("GET","http://localhost:8080/client/test");

 

  request.send();

}

Again, if I were to load this on its own or from the Dart Editor I would receive a cross origin error.  However, since we established permissions in the manifest.json from Step 4, we allowed this cross origin request.

Reload and launch the CPA.  Clicking “client test” should yield the following result:

Conclusion

We’ve established a way to quickly create and edit a dart client application that reflects immediately in a CPA without the need to compile to Javascript.  This allows for an iterative development style such that simply saving the changes results in a feedback response right in the target deployment environment context.  Future work will utilize this model to delegate Google Drive interfacing using the GAE backend.

The full contents of this example can be found here: https://docs.google.com/folder/d/0B315YrNkj-ZxRVVYNFYyb3ZKMkE/edit 

Ackowledgements

Special thanks to +Adam Singer, +Gerwin Sturm, and +Chris Buckett for their encouragement and teaching.  

Icons in the example courtesy of: https://github.com/Dartwatch/hello_world_packaged_app.

About the Author

+Damon Douglas is a Pharmacist working in Healthcare Information Technology administration.  To learn more about the author, visit damondouglas.me.