Random posts about coding

Mostly blogging about dart.

Getting Started With Dart and Google Drive

| Comments

Following the example code from google drive quickstart I was able to recreate the sample in Dart. A lot of the heavy lifting is done by the js-interop cause of the dependency on google javascript client api. This solution is fine for now, I’d take a good guess the dart team will provide client apis at some point.

Started off by creating a new sample project dart_drive_quickstart, removed all default code and add js-interop.

create-new-app

remove-defaults

Bring in the javascript client apis as ScriptElement and set a onload handler to a dart callback.

dart_drive_quickstart.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import 'dart:html';
import 'dart:json';
import 'package:js/js.dart' as js;

final String CLIENT_ID = '<YOURCLIENTID>';
final String SCOPE = 'https://www.googleapis.com/auth/drive';

void main() {
  js.scoped((){
    js.context.handleAuthResult = new js.Callback.many((js.Proxy authResult) {
      Map dartAuthResult = JSON.parse(js.context.JSON.stringify(authResult));
      print("dartAuthResult = ${dartAuthResult}");
    });

    js.context.handleClientLoad =  new js.Callback.many(() {
      js.context.window.setTimeout(js.context.checkAuth, 1);
    });

    js.context.checkAuth = new js.Callback.many(() {
      js.context.gapi.auth.authorize(
          js.map({
              'client_id': CLIENT_ID,
              'scope': SCOPE,
              'immediate': true
            }),
            js.context.handleAuthResult);
    });        
  });

  ScriptElement script = new ScriptElement();
  script.src = "http://apis.google.com/js/client.js?onload=handleClientLoad";
  script.type = "text/javascript";
  document.body.children.add(script);
}

Manually adding the script code and hooking up a callback from javascript to dart seemed to work fine. I ran into issues with callbacks not being scoped when they should of been scoped. Another odd thing was the need for the call to setTimeout, the callback to handleAuthResult would not get fired if checkAuth was not called from setTimeout.

The rest of the code included was for the most part a direct translation of the quickstart sample. Added some dart flavoring when appropriate.

Here are some action shots and code. The full project can be found on github dart_drive_quickstart

app-launched

file-choose

file-uploaded

file-opened-in-drive

dart_drive_quickstart.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import 'dart:html';
import 'dart:json';
import 'package:js/js.dart' as js;

final String CLIENT_ID = '299615367852.apps.googleusercontent.com';
final String SCOPE = 'https://www.googleapis.com/auth/drive';

void main() {
  js.scoped((){
    void insertFile(File fileData, [callback = null]) {
      String boundary = '-------314159265358979323846';
      String delimiter = "\r\n--$boundary\r\n";
      String close_delim = "\r\n--$boundary--";

      var reader = new FileReader();
      reader.readAsBinaryString(fileData);
      reader.on.load.add((Event e) {
        var contentType = fileData.type;
        if (contentType.isEmpty) {
          contentType = 'application/octet-stream';
        }

        var metadata = {
          'title' : fileData.name,
          'mimeType' : contentType
        };

        var base64Data = window.btoa(reader.result);
        var sb = new StringBuffer();
        sb
        ..add(delimiter)
        ..add('Content-Type: application/json\r\n\r\n')
        ..add(JSON.stringify(metadata))
        ..add(delimiter)
        ..add('Content-Type: ')
        ..add(contentType)
        ..add('\r\n')
        ..add('Content-Transfer-Encoding: base64\r\n')
        ..add('\r\n')
        ..add(base64Data)
        ..add(close_delim);

        var multipartRequestBody = sb.toString();

        print("multipartRequestBody");
        print(multipartRequestBody);

        js.scoped(() {
          var request = js.context.gapi.client.request(
            js.map({
              'path': '/upload/drive/v2/files',
              'method': 'POST',
              'params': {'uploadType': 'multipart'},
              'headers': {
                'Content-Type': 'multipart/mixed; boundary="$boundary"'
              },
              'body': multipartRequestBody
            }));

          if (callback == null) {
            callback = new js.Callback.many((js.Proxy jsonResp, var rawResp) {
              print(js.context.JSON.stringify(jsonResp));
              print(rawResp);

              Map r = JSON.parse(js.context.JSON.stringify(jsonResp));
              StringBuffer sb = new StringBuffer();
              if (r.containsKey('error')) {
                sb.add(r.toString());
              } else {
                sb.add("${r["title"]} has been uploaded.");
              }

              query('#text').text = sb.toString();
            });
          }

          request.execute(callback);
        });

      });
    };

    void uploadFile(Event evt) {
      js.scoped( () {
        js.context.gapi.client.load('drive', 'v2', new js.Callback.many(() {
          var file = evt.target.files[0];
          insertFile(file);
        }));
      });
    }

    js.context.handleAuthResult = new js.Callback.many((js.Proxy authResult) {
      Map dartAuthResult = JSON.parse(js.context.JSON.stringify(authResult));
      print("dartAuthResult = ${dartAuthResult}");

      var authButton = query('#authorizeButton');
      var filePicker = query('#filePicker');
      authButton.style.display = 'none';
      filePicker.style.display = 'none';

      if (!dartAuthResult.containsKey('error')) {
        // Access token has been successfully retrieved, requests can be sent to the API.
        filePicker.style.display = 'block';
        filePicker.on['change'].add(uploadFile);
      } else {
        authButton.style.display = 'block';
        authButton.on.click.add((Event e) {
          js.scoped(() {
            js.context.gapi.auth.authorize(
                js.map({
                    'client_id': CLIENT_ID,
                    'scope': SCOPE,
                    'immediate': true
                  }),
                  js.context.handleAuthResult);
          });
        });
      }
    });

    js.context.handleClientLoad =  new js.Callback.many(() {
      js.context.window.setTimeout(js.context.checkAuth, 1);
    });

    js.context.checkAuth = new js.Callback.many(() {
      js.context.gapi.auth.authorize(
          js.map({
              'client_id': CLIENT_ID,
              'scope': SCOPE,
              'immediate': true
            }),
            js.context.handleAuthResult);
    });
  });

  ScriptElement script = new ScriptElement();
  script.src = "http://apis.google.com/js/client.js?onload=handleClientLoad";
  script.type = "text/javascript";
  document.body.children.add(script);
}

Comments