Skip to content

Commit

Permalink
Merge pull request #148 from appwrite/feat-flutter-null-safety
Browse files Browse the repository at this point in the history
feat-flutter-null-safety
  • Loading branch information
eldadfux authored Apr 10, 2021
2 parents d09b929 + fc8478c commit fa66b2c
Show file tree
Hide file tree
Showing 20 changed files with 186 additions and 62 deletions.
6 changes: 6 additions & 0 deletions src/SDK/Language/Flutter.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ public function getFiles()
'template' => '/flutter/example/README.md.twig',
'minify' => false,
],
[
'scope' => 'default',
'destination' => '/example/pubspec.yaml',
'template' => '/flutter/example/pubspec.yaml.twig',
'minify' => false,
],
[
'scope' => 'default',
'destination' => 'CHANGELOG.md',
Expand Down
7 changes: 2 additions & 5 deletions templates/dart/lib/client.dart.twig
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Client {
this.config = {};

assert(endPoint.startsWith(RegExp("http://|https://")), "endPoint $endPoint must start with 'http'");
init();
}


Expand Down Expand Up @@ -53,11 +54,9 @@ class Client {
return this;
}

Future init() async {
if(!initialized) {
void init() {
this.http.options.baseUrl = this.endPoint;
this.http.options.validateStatus = (status) => status! < 400;
}
}

Future<Response> call(HttpMethod method, {String path = '', Map<String, String> headers = const {}, Map<String, dynamic> params = const {}, ResponseType? responseType}) async {
Expand All @@ -69,8 +68,6 @@ class Client {
};
}

await this.init();

// Origin is hardcoded for testing
Options options = Options(
headers: {...this.headers!, ...headers},
Expand Down
3 changes: 3 additions & 0 deletions templates/flutter/example/pubspec.yaml.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: {{ language.params.packageName }}_example
environment:
sdk: '>=2.6.0 <3.0.0'
52 changes: 27 additions & 25 deletions templates/flutter/lib/client.dart.twig
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ part of {{ language.params.packageName }};
class Client {
String endPoint;
String type = 'unknown';
Map<String, String> headers;
Map<String, String> config;
Map<String, String>? headers;
late Map<String, String> config;
bool selfSigned;
bool initialized = false;
Dio http;
PersistCookieJar cookieJar;
late PersistCookieJar cookieJar;

Client({this.endPoint = '{{spec.endpoint}}', this.selfSigned = false, Dio http}) : this.http = http ?? Dio() {
Client({this.endPoint = '{{spec.endpoint}}', this.selfSigned = false, Dio? http}) : this.http = http ?? Dio() {
// Platform is not supported in web so if web, set type to web automatically and skip Platform check
if(kIsWeb) {
type = 'web';
Expand All @@ -34,6 +34,7 @@ class Client {
this.config = {};

assert(endPoint.startsWith(RegExp("http://|https://")), "endPoint $endPoint must start with 'http'");
init();
}

Future<Directory> _getCookiePath() async {
Expand Down Expand Up @@ -67,31 +68,29 @@ class Client {
}

Client addHeader(String key, String value) {
headers[key] = value;
headers![key] = value;

return this;
}

Future init() async {
if(!initialized) {
// if web skip cookie implementation and origin header as those are automatically handled by browsers
if(!kIsWeb) {
// if web skip cookie implementation and origin header as those are automatically handled by browsers
if(!kIsWeb) {
final Directory cookieDir = await _getCookiePath();
cookieJar = new PersistCookieJar(dir:cookieDir.path);
cookieJar = new PersistCookieJar(storage: FileStorage(cookieDir.path));
this.http.interceptors.add(CookieManager(cookieJar));
PackageInfo packageInfo = await PackageInfo.fromPlatform();
addHeader('Origin', 'appwrite-$type://${packageInfo.packageName ?? packageInfo.appName}');
}else{
// if web set httpClientAdapter as BrowserHttpClientAdapter with withCredentials true to make cookies work
addHeader('Origin', 'appwrite-$type://${packageInfo.packageName}');
} else {
// if web set withCredentials true to make cookies work
this.http.options.extra['withCredentials'] = true;
}

this.http.options.baseUrl = this.endPoint;
this.http.options.validateStatus = (status) => status < 400;
}

this.http.options.baseUrl = this.endPoint;
this.http.options.validateStatus = (status) => status! < 400;
}

Future<Response> call(HttpMethod method, {String path = '', Map<String, String> headers = const {}, Map<String, dynamic> params = const {}, ResponseType responseType}) async {
Future<Response> call(HttpMethod method, {String path = '', Map<String, String> headers = const {}, Map<String, dynamic> params = const {}, ResponseType? responseType}) async {
if(selfSigned && !kIsWeb) {
// Allow self signed requests
(http.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (HttpClient client) {
Expand All @@ -100,18 +99,21 @@ class Client {
};
}

await this.init();
if(!initialized) {
await this.init();
}

// Origin is hardcoded for testing
Options options = Options(
headers: {...this.headers, ...headers},
headers: {...this.headers!, ...headers},
method: method.name(),
responseType: responseType
responseType: responseType,
listFormat: ListFormat.multiCompatible
);

try {
if(headers['content-type'] == 'multipart/form-data') {
return await http.request(path, data: FormData.fromMap(params), options: options);
return await http.request(path, data: FormData.fromMap(params, ListFormat.multiCompatible), options: options);
}

if (method == HttpMethod.get) {
Expand All @@ -128,16 +130,16 @@ class Client {
throw {{spec.title | caseUcfirst}}Exception(e.message);
}
if(responseType == ResponseType.bytes) {
if(e.response.headers['content-type'].contains('application/json')) {
final res = json.decode(utf8.decode(e.response.data));
if(e.response!.headers['content-type']!.contains('application/json')) {
final res = json.decode(utf8.decode(e.response!.data));
throw {{spec.title | caseUcfirst}}Exception(res['message'],res['code'], e.response);
} else {
throw {{spec.title | caseUcfirst}}Exception(e.message);
}
}
throw {{spec.title | caseUcfirst}}Exception(e.response.data['message'],e.response.data['code'], e.response.data);
throw {{spec.title | caseUcfirst}}Exception(e.response!.data['message'],e.response!.data['code'], e.response!.data);
} catch(e) {
throw {{spec.title | caseUcfirst}}Exception(e.message);
throw {{spec.title | caseUcfirst}}Exception(e.toString());
}
}
}
4 changes: 2 additions & 2 deletions templates/flutter/lib/exception.dart.twig
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
part of {{ language.params.packageName }};

class {{spec.title | caseUcfirst}}Exception implements Exception {
final String message;
final int code;
final String? message;
final int? code;
final dynamic response;

{{spec.title | caseUcfirst}}Exception([this.message = "", this.code, this.response]);
Expand Down
3 changes: 1 addition & 2 deletions templates/flutter/lib/package.dart.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'dart:io';
import 'dart:convert';
import 'package:universal_html/html.dart' as html;
import 'package:dio/dio.dart';
import 'package:meta/meta.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_web_auth/flutter_web_auth.dart';
import 'package:dio/adapter.dart';
Expand All @@ -14,7 +13,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:package_info_plus/package_info_plus.dart';


export 'package:dio/dio.dart' show Response;
export 'package:dio/dio.dart' show Response, MultipartFile;

part 'client.dart';
part 'enums.dart';
Expand Down
10 changes: 5 additions & 5 deletions templates/flutter/lib/services/service.dart.twig
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
part of {{ language.params.packageName }};

{% macro parameter(parameter) %}
{% if parameter.name == 'orderType' %}{% if parameter.required %}@required {% endif %}{{ 'OrderType orderType = OrderType.asc' }}{% else %}
{% if parameter.required %}@required {% endif %}{{ parameter.type | typeName }} {{ parameter.name | caseCamel }}{{ parameter | escapeDollarSign | paramDefault }}{% endif %}
{% if parameter.name == 'orderType' %}{% if parameter.required %}required {% endif %}{{ 'OrderType orderType = OrderType.asc' }}{% else %}
{% if parameter.required %}required {% endif %}{{ parameter.type | typeName }} {{ parameter.name | caseCamel }}{{ parameter | escapeDollarSign | paramDefault }}{% endif %}
{% endmacro %}
{% macro method_parameters(parameters) %}
{% if parameters.all|length > 0 %}{{ '{' }}{% for parameter in parameters.all %}{{ _self.parameter(parameter) }}{% if not loop.last %}, {% endif %}{% endfor %}{{ '}' }}{% endif %}
Expand Down Expand Up @@ -68,15 +68,15 @@ class {{ service.name | caseUcfirst }} extends Service {

if(kIsWeb) {
html.window.location.href = url.toString();
return null;
return Future.value();
}else{

return FlutterWebAuth.authenticate(
url: url.toString(),
callbackUrlScheme: "appwrite-callback-" + client.config['project']
callbackUrlScheme: "appwrite-callback-" + client.config['project']!
).then((value) async {
Uri url = Uri.parse(value);
Cookie cookie = new Cookie(url.queryParameters['key'], url.queryParameters['secret']);
Cookie cookie = new Cookie(url.queryParameters['key']!, url.queryParameters['secret']!);
cookie.domain = Uri.parse(client.endPoint).host;
cookie.httpOnly = true;
cookie.path = '/';
Expand Down
17 changes: 8 additions & 9 deletions templates/flutter/pubspec.yaml.twig
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@ repository: https://github.com/{{sdk.gitUserName}}/{{sdk.gitRepoName}}
issue_tracker: https://github.com/appwrite/sdk-generator/issues
documentation: {{ spec.contactURL }}
environment:
sdk: '>=2.6.0 <3.0.0'
sdk: '>=2.12.0 <3.0.0'
dependencies:
flutter:
sdk: flutter
cookie_jar: ^1.0.1
dio: ^3.0.10
dio_cookie_manager: ^1.0.0
flutter_web_auth: ^0.2.4
meta: ^1.1.8
package_info_plus: ^0.6.3
path_provider: ^1.6.14
universal_html: ^1.2.3
cookie_jar: ^3.0.1
dio: ^4.0.0
dio_cookie_manager: ^2.0.0
flutter_web_auth: ^0.3.0
package_info_plus: ^1.0.0
path_provider: ^2.0.1
universal_html: ^2.0.8
dev_dependencies:
flutter_test:
Expand Down
20 changes: 19 additions & 1 deletion tests/SDKTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ class SDKTest extends TestCase
'supportException' => true,
],

'flutter' => [
'class' => 'Appwrite\SDK\Language\Flutter',
'build' => [
'mkdir -p tests/sdks/flutter/test',
'cp tests/languages/flutter/tests.dart tests/sdks/flutter/test/appwrite_test.dart',
],
'envs' => [
'flutter-stable' => 'docker run --rm -v $(pwd):/app -w /app/tests/sdks/flutter --env PUB_CACHE=vendor cirrusci/flutter:stable sh -c "flutter pub get && flutter test test/appwrite_test.dart"',
],
'supportException' => true,
],

//Skipping for now, enable it once Java SDK is in Good enough shape
/* 'java' => [
'class' => 'Appwrite\SDK\Language\Java',
Expand Down Expand Up @@ -188,7 +200,7 @@ public function testHTTPSuccess()
throw new \Exception('Failed to fetch spec from Appwrite server');
}

$whitelist = ['php', 'cli', 'node', 'ruby', 'python', 'typescript', 'deno', 'dotnet', 'dart'];
$whitelist = ['php', 'cli', 'node', 'ruby', 'python', 'typescript', 'deno', 'dotnet', 'dart', 'flutter'];

foreach ($this->languages as $language => $options) {
if (!empty($whitelist) && !in_array($language, $whitelist)) {
Expand Down Expand Up @@ -261,6 +273,12 @@ public function testHTTPSuccess()
}

$this->assertIsArray($output);

$removed = '';
do {
$removed = array_shift($output);
} while ($removed != 'Test Started' && sizeof($output) != 0);

$this->assertGreaterThan(10, count($output));

$this->assertEquals('GET:/v1/mock/tests/foo:passed', $output[0] ?? '');
Expand Down
2 changes: 2 additions & 0 deletions tests/languages/cli/test.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
$output = [];
exec($command, $output);

echo "\nTest Started\n";

// Foo Service
$command = "${baseCommand} cli foo get --x='string' --y='123' --z[]='string in array'";
$output = [];
Expand Down
3 changes: 2 additions & 1 deletion tests/languages/dart/tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ void main() async {
client.addHeader('Origin', 'http://localhost');
client.setSelfSigned();

print('\nTest Started');

// Foo Tests

Response response;
response = await foo.get(x: 'string', y: 123, z: ['string in array']);
print(response.data['result']);
Expand Down
2 changes: 2 additions & 0 deletions tests/languages/deno/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ async function start() {

client.addHeader('Origin', 'http://localhost')

console.log('\nTest Started');

// Foo

response = await foo.get('string', 123, ['string in array'])
Expand Down
3 changes: 3 additions & 0 deletions tests/languages/dotnet/tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ $foo = New-Object Appwrite.Foo -ArgumentList $client
$bar = New-Object Appwrite.Bar -ArgumentList $client
$general = New-Object Appwrite.General -ArgumentList $client

Write-Host
Write-Host "Test Started"

$list = $("string in array")
$response = $foo.Get("string", 123, $list) | Await-Task
Print-Response $response.GetResult()
Expand Down
Loading

0 comments on commit fa66b2c

Please sign in to comment.