From 09c6b8a5d977433d3e9ecf29bae7354245139329 Mon Sep 17 00:00:00 2001 From: unreal0 Date: Mon, 12 Nov 2018 14:43:13 +0800 Subject: [PATCH 1/2] fixed query --- .vscode/launch.json | 19 +++++ example/android/.project | 17 ++++ .../org.eclipse.buildship.core.prefs | 2 + example/android/app/.classpath | 6 ++ example/android/app/.project | 23 +++++ .../org.eclipse.buildship.core.prefs | 2 + example/android/app/build.gradle | 22 +++-- example/android/build.gradle | 15 +++- .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/android/gradlew | 0 example/lib/application_constants.dart | 10 +-- example/lib/diet_plan.dart | 34 ++++---- example/lib/main.dart | 84 ++++++++++++------- lib/network/parse_http_client.dart | 1 + lib/network/parse_query.dart | 63 +++++++++----- lib/objects/parse_object.dart | 3 +- lib/objects/parse_response.dart | 20 +++-- lib/objects/parse_user.dart | 19 +++-- 18 files changed, 247 insertions(+), 95 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 example/android/.project create mode 100644 example/android/.settings/org.eclipse.buildship.core.prefs create mode 100644 example/android/app/.classpath create mode 100644 example/android/app/.project create mode 100644 example/android/app/.settings/org.eclipse.buildship.core.prefs mode change 100644 => 100755 example/android/gradlew diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..5ce652a27 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Flutter", + "type": "dart", + "request": "launch", + "program": "example/lib/main.dart" + }, + { + "name": "Flutter", + "request": "launch", + "type": "dart" + } + ] +} diff --git a/example/android/.project b/example/android/.project new file mode 100644 index 000000000..3964dd3f5 --- /dev/null +++ b/example/android/.project @@ -0,0 +1,17 @@ + + + android + Project android created by Buildship. + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/example/android/.settings/org.eclipse.buildship.core.prefs b/example/android/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 000000000..e8895216f --- /dev/null +++ b/example/android/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir= +eclipse.preferences.version=1 diff --git a/example/android/app/.classpath b/example/android/app/.classpath new file mode 100644 index 000000000..eb19361b5 --- /dev/null +++ b/example/android/app/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/example/android/app/.project b/example/android/app/.project new file mode 100644 index 000000000..ac485d7c3 --- /dev/null +++ b/example/android/app/.project @@ -0,0 +1,23 @@ + + + app + Project app created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/example/android/app/.settings/org.eclipse.buildship.core.prefs b/example/android/app/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 000000000..b1886adb4 --- /dev/null +++ b/example/android/app/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir=.. +eclipse.preferences.version=1 diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 9883bcabb..c88be5c97 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -11,6 +11,16 @@ if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" @@ -24,10 +34,10 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.flutterpluginexample" - minSdkVersion 16 + minSdkVersion 21 targetSdkVersion 27 - versionCode 1 - versionName "1.0" + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } @@ -46,6 +56,8 @@ flutter { dependencies { testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.1' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + // implementation "com.android.support:appcompat-v7:27.1.0" } +// apply plugin: 'com.google.gms.google-services' diff --git a/example/android/build.gradle b/example/android/build.gradle index 447688755..7a4441f30 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -5,7 +5,8 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.2' + classpath 'com.google.gms:google-services:4.0.1' } } @@ -13,6 +14,7 @@ allprojects { repositories { google() jcenter() + maven { url "https://jitpack.io" } } } @@ -27,3 +29,14 @@ subprojects { task clean(type: Delete) { delete rootProject.buildDir } + +subprojects { + project.configurations.all { + resolutionStrategy.eachDependency { details -> + if (details.requested.group == 'com.android.support' + && !details.requested.name.contains('multidex') ) { + details.useVersion "26.1.0" + } + } + } +} diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index aa901e1e0..9372d0f3f 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/example/android/gradlew b/example/android/gradlew old mode 100644 new mode 100755 diff --git a/example/lib/application_constants.dart b/example/lib/application_constants.dart index 7ddb84685..a14245220 100644 --- a/example/lib/application_constants.dart +++ b/example/lib/application_constants.dart @@ -1,6 +1,6 @@ abstract class ApplicationConstants { - static const String APP_NAME = ""; - static const String PARSE_APPLICATION_ID = ""; - static const String PARSE_MASTER_KEY = ""; - static const String PARSE_SERVER_URL = ""; -} \ No newline at end of file + static const String APP_NAME = "MyApp"; + static const String PARSE_APPLICATION_ID = "myAppId"; + static const String PARSE_MASTER_KEY = "123456"; + static const String PARSE_SERVER_URL = "http://118.24.162.252:2018/parse"; +} diff --git a/example/lib/diet_plan.dart b/example/lib/diet_plan.dart index b1ec90d51..bba2671f7 100644 --- a/example/lib/diet_plan.dart +++ b/example/lib/diet_plan.dart @@ -12,32 +12,32 @@ class DietPlan extends ParseObject { num fat; num status; - static const String DIET_PLAN = 'Diet_Plans'; - static const String NAME = 'Name'; - static const String DESCRIPTION = 'Description'; - static const String PROTEIN = 'Protein'; - static const String CARBS = 'Carbs'; - static const String FAT = 'Fat'; - static const String STATUS = 'Status'; + static const String DIET_PLAN = 'post'; + static const String NAME = 'title'; + // static const String DESCRIPTION = 'text'; + // static const String PROTEIN = 'Protein'; + // static const String CARBS = 'Carbs'; + // static const String FAT = 'Fat'; + // static const String STATUS = 'Status'; @override dynamic fromJson(Map objectData) { this.name = objectData[NAME]; - this.description = objectData[DESCRIPTION]; - this.protein = objectData[PROTEIN]; - this.carbs = objectData[CARBS]; - this.fat = objectData[FAT]; - this.status = objectData[STATUS]; + // this.description = objectData[DESCRIPTION]; + // this.protein = objectData[PROTEIN]; + // this.carbs = objectData[CARBS]; + // this.fat = objectData[FAT]; + // this.status = objectData[STATUS]; return this; } Map toJson() => { NAME: name, - DESCRIPTION: description, - PROTEIN: protein, - CARBS: carbs, - FAT: fat, - STATUS: status, + // DESCRIPTION: description, + // PROTEIN: protein, + // CARBS: carbs, + // FAT: fat, + // STATUS: status, }; @override diff --git a/example/lib/main.dart b/example/lib/main.dart index 73d25c00d..32cb1c38f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -20,42 +20,50 @@ class _MyAppState extends State { void initState() { super.initState(); initParse(); - getAllItems(); - getSingleItem(); - //query(); - //queryByContainedIn(); - initUser(); + // getAllItems(); + // getSingleItem(); + query(); + // queryByContainedIn(); + // initUser(); } Future initParse() async { - // Initialize parse - Parse().initialize( - ApplicationConstants.PARSE_APPLICATION_ID, + Parse().initialize(ApplicationConstants.PARSE_APPLICATION_ID, ApplicationConstants.PARSE_SERVER_URL, masterKey: ApplicationConstants.PARSE_MASTER_KEY); } void getAllItems() { DietPlan().getAll().then((response) { - if (response.success){ - + if (response.success) { for (var plan in response.result) { - print(ApplicationConstants.APP_NAME + ": " + (plan as DietPlan).name); + if ((plan as DietPlan).name != null) { + print(ApplicationConstants.APP_NAME + + ": ${DietPlan.DIET_PLAN}------ " + + (plan as DietPlan).name); + } else { + print(ApplicationConstants.APP_NAME + + ": ${DietPlan.DIET_PLAN}---${(plan as DietPlan).objectId}--- null"); + } } - } else { - print(ApplicationConstants.APP_NAME + ": " + response.exception.message); + print( + ApplicationConstants.APP_NAME + ": " + response.exception.message); } }); } void getSingleItem() { - DietPlan().get('R5EonpUDWy').then((response) { - if (response.success){ - print(ApplicationConstants.APP_NAME + ": " + (response.result as DietPlan).toString()); + DietPlan().get('2pNUgv1CKA').then((response) { + if (response.success) { + print("response.result: ${response.result}"); + print(ApplicationConstants.APP_NAME + + ":: ${DietPlan.DIET_PLAN}---${(response.result as DietPlan).objectId}--- " + + (response.result as DietPlan).toString()); } else { - print(ApplicationConstants.APP_NAME + ": " + response.exception.message); + print( + ApplicationConstants.APP_NAME + ": " + response.exception.message); } }); } @@ -65,13 +73,18 @@ class _MyAppState extends State { QueryBuilder() ..object = DietPlan() ..field = DietPlan.NAME - ..equals = ['Paleo'] - ..query().then((response){ - - if (response.success){ - print(ApplicationConstants.APP_NAME + ": " + ((response.result as List)[0] as DietPlan).toString()); + ..equals = ['fff444'] + ..query().then((response) { + if (response.success) { + print(ApplicationConstants.APP_NAME + + "::: ${DietPlan.DIET_PLAN}---${((response.result as List)[0] as DietPlan).name}--- " + + ((response.result as List)[0] as DietPlan) + .objectId + .toString()); } else { - print(ApplicationConstants.APP_NAME + ": " + response.exception.message); + print(ApplicationConstants.APP_NAME + + ": " + + response.exception.message); } }); } @@ -81,22 +94,31 @@ class _MyAppState extends State { QueryBuilder() ..object = DietPlan() ..field = DietPlan.NAME - ..contains = ['Diet'] - ..query().then((response){ - - if (response.success){ - print(ApplicationConstants.APP_NAME + ": " + ((response.result as List)[0] as DietPlan).toString()); + ..containedIn = ['dfg'] + ..query().then((response) { + if (response.success) { + print("queryByContainedIn-result: ${response.result}"); + print(ApplicationConstants.APP_NAME + + ": " + + ((response.result as List).length > 0 + ? ((response.result as List)[0] as DietPlan) + .toString() + : "")); } else { - print(ApplicationConstants.APP_NAME + ": " + response.exception.message); + print(ApplicationConstants.APP_NAME + + ": " + + response.exception.message); } }); } Future initUser() async { - User().createNewUser("TestFlutter", "TestPassword123", "TestEmail@Email.com"); + User() + .createNewUser("TestFlutter", "TestPassword123", "TestEmail@Email.com"); + // User().signUp(); User().login().then((val) { - print(val); + print("val: $val"); }); } diff --git a/lib/network/parse_http_client.dart b/lib/network/parse_http_client.dart index d5427ea75..96c0f2e48 100644 --- a/lib/network/parse_http_client.dart +++ b/lib/network/parse_http_client.dart @@ -18,6 +18,7 @@ class ParseHTTPClient extends http.BaseClient { request.headers['Content-Type'] = 'application/json'; if (data.masterKey != null) request.headers['X-Parse-Master-Key'] = data.masterKey; + print("request: $request"); return _client.send(request); } } diff --git a/lib/network/parse_query.dart b/lib/network/parse_query.dart index daacc7126..f968897c5 100644 --- a/lib/network/parse_query.dart +++ b/lib/network/parse_query.dart @@ -8,7 +8,7 @@ import 'package:parse_server_sdk/objects/parse_response.dart'; class QueryBuilder implements ParseBaseObject { // BaseParams - String _className; + // String _className; ParseObject object; final ParseHTTPClient client = ParseHTTPClient(); String path; @@ -72,12 +72,24 @@ class QueryBuilder implements ParseBaseObject { existsMap = _runThroughQueryParamsWithSearchTerms(contains, "term", field); - //String query = r"""where={"Name":{"$text":{"$search":{"$term":"Diet"}}}}"""; - String query = "where=${JsonEncoder().convert(existsMap)}"; + print("existsMap: $existsMap"); + + // String query = r"""where={"Name":{"$text":{"$search":{"$term":"gg"}}}}"""; + // String s1 = existsMap.toString(); + // print("s1: $s1"); + String query = "where=${JsonEncoder().convert(existsMap.toString())}"; + // String s = r'{"title":{"$text":{"$search":{"$term":"gg"}}}}'; + // String query = "where=${JsonEncoder().convert(s)}"; + + // query: where="{\"title\":{\"$text\":{\"$search\":{\"$term\":\"gg\"}}}}" + // where : {\"title\":{\"$text\":{\"$search\":{\"$term\":\"gg\"}}}} + // existsMap: {title: {"text":"{\"search\":\"{\\\"$term\\\":\\\"gg\\\"}\"}"}} + // existsMap: {title: {"$text":"{\"$search\":\"{\\\"$term\\\":\\\"gg\\\"}\"}"}} + // request: GET http://118.24.162.252:2018/parse/classes/post?where=%22%7B%5C%22title%5C%22:%7B%5C%22$text%5C%22:%7B%5C%22$search%5C%22:%7B%5C%22$term%5C%22:%5C%22gg%5C%22%7D%7D%7D%7D%22 if (limit != 0) query += '?limit=$limit'; if (skip != 0) query += '?skip=$skip'; - + print("query: $query"); return query; } @@ -88,18 +100,18 @@ class QueryBuilder implements ParseBaseObject { if (list.isNotEmpty) { if (list.length == 1) { - params = list[0]; + params = '"${list[0]}"'; } else { for (var listItem in list) { - params += "$listItem, "; + params += '"$listItem", '; } params.substring(0, params.length - 2); } } - mapToReturn[queryParam] = params; - + mapToReturn['"$queryParam"'] = params; + print(mapToReturn); return mapToReturn; } @@ -107,14 +119,16 @@ class QueryBuilder implements ParseBaseObject { List list, String queryParam, String fieldName) { Map mapToReturn = Map(); Map mapWithParamData = Map(); - + List s = new List(); for (var item in list) { - mapWithParamData["\$$queryParam"] = item; + // mapWithParamData['"\$$queryParam"'] = '"$item"'; + s.add('"$item"'); } + mapWithParamData['"\$$queryParam"'] = s; - var params = JsonEncoder().convert(mapWithParamData).toString(); - - mapToReturn[fieldName] = params; + // var params = JsonEncoder().convert(mapWithParamData).toString(); + // mapToReturn[fieldName] = params; + mapToReturn['"$fieldName"'] = mapWithParamData.toString(); return mapToReturn; } @@ -127,17 +141,26 @@ class QueryBuilder implements ParseBaseObject { Map searchEntry = Map(); for (var item in list) { - mapWithParamData["\$$queryParam"] = item; + mapWithParamData['"\$$queryParam"'] = '"$item"'; } - var jsonMapWithParamData = JsonEncoder().convert(mapWithParamData); - searchEntry['search'] = jsonMapWithParamData; + // var jsonMapWithParamData = JsonEncoder().convert(mapWithParamData); + // searchEntry['\$search'] = jsonMapWithParamData; + searchEntry['"\$search"'] = mapWithParamData.toString(); + + // var jsonSearchEntry = JsonEncoder().convert(searchEntry); + // textEntry['\$text'] = jsonSearchEntry; + textEntry['"\$text"'] = searchEntry.toString(); + + // var params = JsonEncoder().convert(textEntry); + // mapToReturn['$fieldName'] = params; - var jsonSearchEntry = JsonEncoder().convert(searchEntry); - textEntry['text'] = jsonSearchEntry; + // textEntry[fieldName] = textEntry.toString(); + // print(textEntry.toString()); + mapToReturn['"$fieldName"'] = textEntry.toString(); + // print(mapToReturn.toString()); - var params = JsonEncoder().convert(textEntry).toString(); - mapToReturn[fieldName] = params; + // mapToReturn = textEntry; return mapToReturn; } diff --git a/lib/objects/parse_object.dart b/lib/objects/parse_object.dart index 5c39cd38f..ea4559f94 100644 --- a/lib/objects/parse_object.dart +++ b/lib/objects/parse_object.dart @@ -55,8 +55,9 @@ class ParseObject implements ParseBaseObject { String uri = _client.data.serverUrl + "$path"; if (objectId != null) uri += "/$objectId"; - + print("uri: $uri"); return this._client.get(uri).then((value) { + print("value: $value"); return ParseResponse.handleResponse(this, value); }); } diff --git a/lib/objects/parse_response.dart b/lib/objects/parse_response.dart index 5b933346e..b65392724 100644 --- a/lib/objects/parse_response.dart +++ b/lib/objects/parse_response.dart @@ -12,7 +12,8 @@ class ParseResponse { dynamic result; ParseException exception; - static Future _handleSuccess(ParseResponse response, ParseObject object, String responseBody) async { + static Future _handleSuccess( + ParseResponse response, ParseObject object, String responseBody) async { response.success = true; var map = JsonDecoder().convert(responseBody) as Map; @@ -29,16 +30,20 @@ class ParseResponse { } static ParseResponse _checkForEmptyResult(ParseResponse response) { - if (response.result == null || (response.result as List).length == 0) { - response.exception = ParseException(); - response.exception.message = "No result found for query"; - response.success = false; + // || + // (response.result as List).length == 0 || + // (response.result as ParseObject) == null + if (response.result == null) { + response.exception = ParseException(); + response.exception.message = "No result found for query"; + response.success = false; } return response; } - static List _handleMultipleResults(ParseObject object, dynamic map) { + static List _handleMultipleResults( + ParseObject object, dynamic map) { var resultsList = List(); for (var value in map) { @@ -60,7 +65,8 @@ class ParseResponse { return response; } - static Future handleResponse(ParseObject object, Response value) async { + static Future handleResponse( + ParseObject object, Response value) async { var response = ParseResponse(); if (value != null) { diff --git a/lib/objects/parse_user.dart b/lib/objects/parse_user.dart index 61074f158..525ce89f6 100644 --- a/lib/objects/parse_user.dart +++ b/lib/objects/parse_user.dart @@ -9,6 +9,7 @@ import 'package:parse_server_sdk/network/parse_http_client.dart'; class User implements ParseBaseObject { final String className = '_User'; final ParseHTTPClient client = ParseHTTPClient(); + String path = "/classes/_User"; Map objectData = {}; @@ -52,6 +53,7 @@ class User implements ParseBaseObject { } Map _handleResponse(String response) { + print("response: $response"); Map responseData = JsonDecoder().convert(response); if (responseData.containsKey('objectId')) { objectData = responseData; @@ -65,14 +67,15 @@ class User implements ParseBaseObject { if (sessionId != null) objectData.remove('sessionToken'); } - Future> signUp([Map objectInitialData]) async { + Future> signUp( + [Map objectInitialData]) async { if (objectInitialData != null) { objectData = {}..addAll(objectData)..addAll(objectInitialData); } _resetObjectId(); Map bodyData = {}; - bodyData["email"] = userData.username; + bodyData["email"] = userData.emailAddress; bodyData["password"] = userData.password; bodyData["username"] = userData.username; @@ -81,8 +84,8 @@ class User implements ParseBaseObject { Uri url = Uri( scheme: tempUri.scheme, host: tempUri.host, - path: "${tempUri.path}$path" - ); + port: tempUri.port, + path: "${tempUri.path}$path"); final response = this.client.post(url, headers: { @@ -92,6 +95,7 @@ class User implements ParseBaseObject { return response.then((value) { _handleResponse(value.body); + print(value.body); return objectData; }); } @@ -102,18 +106,19 @@ class User implements ParseBaseObject { Uri url = Uri( scheme: tempUri.scheme, host: tempUri.host, + port: tempUri.port, path: "${tempUri.path}/login", queryParameters: { "username": userData.username, "password": userData.password - } - ); - + }); + print("url: $url"); final response = this.client.post(url, headers: { 'X-Parse-Revocable-Session': "1", }); return response.then((value) { _handleResponse(value.body); + // print(value.body); return objectData; }); } From 1ae4d0949660d8fb2def7de7ba44ae6b3ec916c4 Mon Sep 17 00:00:00 2001 From: unreal0 Date: Sat, 5 Jan 2019 18:11:02 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=95=B4=E7=90=86,=E4=BF=AE=E6=94=B9readme?= =?UTF-8?q?,=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 5 - README.md | 4 +- example/README.md | 11 +- .../android/app/src/main/AndroidManifest.xml | 2 +- example/lib/ApplicationBloc.dart | 38 ++++ example/lib/IncrementBloc.dart | 32 +++ example/lib/application_constants.dart | 1 + example/lib/blocProvider.dart | 40 ++++ example/lib/count_bloc.dart | 19 ++ example/lib/gametest/GameCommunication.dart | 116 +++++++++++ example/lib/gametest/GamePage.dart | 163 +++++++++++++++ example/lib/gametest/StartPage.dart | 197 ++++++++++++++++++ .../lib/gametest/WebSocketsNotifications.dart | 115 ++++++++++ example/lib/liveQueryBloc.dart | 73 +++++++ example/lib/main.dart | 99 +++++++-- example/lib/myHome.dart | 128 ++++++++++++ example/lib/parseData.dart | 23 ++ example/pubspec.yaml | 12 +- lib/network/parse_livequery.dart | 158 +++++++++++++- lib/objects/parse_object.dart | 34 ++- 20 files changed, 1219 insertions(+), 51 deletions(-) create mode 100644 example/lib/ApplicationBloc.dart create mode 100644 example/lib/IncrementBloc.dart create mode 100644 example/lib/blocProvider.dart create mode 100644 example/lib/count_bloc.dart create mode 100644 example/lib/gametest/GameCommunication.dart create mode 100644 example/lib/gametest/GamePage.dart create mode 100644 example/lib/gametest/StartPage.dart create mode 100644 example/lib/gametest/WebSocketsNotifications.dart create mode 100644 example/lib/liveQueryBloc.dart create mode 100644 example/lib/myHome.dart create mode 100644 example/lib/parseData.dart diff --git a/.vscode/launch.json b/.vscode/launch.json index 5ce652a27..a8dd56dea 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,11 +9,6 @@ "type": "dart", "request": "launch", "program": "example/lib/main.dart" - }, - { - "name": "Flutter", - "request": "launch", - "type": "dart" } ] } diff --git a/README.md b/README.md index 495fb4bd8..827603fc0 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Parse Server Dart + A rewrite of a library hosted on GitHub. This is not my own content but based on a library already created and looks to be abandoned. https://github.com/lotux/parse_server_dart -## Join in! -Want to get involved? Join our Slack channel and help out! FlutterParseSDK.Slack.com +## example 目录有完整测试 ## Getting Started diff --git a/example/README.md b/example/README.md index 17ecae4d2..3ae09ee5f 100644 --- a/example/README.md +++ b/example/README.md @@ -1,8 +1,9 @@ -# flutter_plugin_example +# flutter_parse_client_example -Demonstrates how to use the flutter_plugin plugin. +Demonstrates how to use the flutter parse client. -## Getting Started +## html ok -For help getting started with Flutter, view our online -[documentation](https://flutter.io/). +## websoket ok + +## bloc diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 9fbcae13f..c3f5a810c 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -14,7 +14,7 @@ FlutterApplication and put your custom class here. --> _syncController = StreamController.broadcast(); + Stream get outParseStream => _syncController.stream; + + /// + StreamController _cmdController = StreamController.broadcast(); + StreamSink get getMovieGenres => _cmdController.sink; + + List _genresList; + + ApplicationBloc(LiveQuery liveQuery) { + // _genresList = liveQuery.channel as List; + // Read all genres from Internet + // api.movieGenres().then((list) { + // _genresList = list; + // }); + _syncController.stream.listen((_) { + _syncController.sink.addStream(liveQuery.channel.stream); + }); + // _cmdController.stream.listen((_) { + // _syncController.sink + // .add(UnmodifiableListView(_genresList.genres)); + // }); + } + + void dispose() { + _syncController.close(); + _cmdController.close(); + } +} diff --git a/example/lib/IncrementBloc.dart b/example/lib/IncrementBloc.dart new file mode 100644 index 000000000..22d95db8c --- /dev/null +++ b/example/lib/IncrementBloc.dart @@ -0,0 +1,32 @@ +import 'dart:async'; + +import 'package:Parse_example/blocProvider.dart'; + +class IncrementBloc implements BlocBase { + int _counter; + + // 处理counter的stream + StreamController _counterController = StreamController(); + StreamSink get _inAdd => _counterController.sink; + Stream get outCounter => _counterController.stream; + + // 处理业务逻辑的stream + StreamController _actionController = StreamController(); + StreamSink get incrementCounter => _actionController.sink; + + // 构造器 + IncrementBloc() { + _counter = 0; + _actionController.stream.listen(_handleLogic); + } + + void dispose() { + _actionController.close(); + _counterController.close(); + } + + void _handleLogic(data) { + _counter = _counter + 1; + _inAdd.add(_counter); + } +} diff --git a/example/lib/application_constants.dart b/example/lib/application_constants.dart index a14245220..917f4e902 100644 --- a/example/lib/application_constants.dart +++ b/example/lib/application_constants.dart @@ -3,4 +3,5 @@ abstract class ApplicationConstants { static const String PARSE_APPLICATION_ID = "myAppId"; static const String PARSE_MASTER_KEY = "123456"; static const String PARSE_SERVER_URL = "http://118.24.162.252:2018/parse"; + static const String PARSE_LIVE_SERVER_URL = "ws://118.24.162.252:2018/parse"; } diff --git a/example/lib/blocProvider.dart b/example/lib/blocProvider.dart new file mode 100644 index 000000000..5e268cbdd --- /dev/null +++ b/example/lib/blocProvider.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +abstract class BlocBase { + void dispose(); +} + +class BlocProvider extends StatefulWidget { + BlocProvider({ + Key key, + @required this.child, + @required this.bloc, + }) : super(key: key); + + final T bloc; + final Widget child; + + @override + _BlocProviderState createState() => _BlocProviderState(); + + static T of(BuildContext context) { + final type = _typeOf>(); + BlocProvider provider = context.ancestorWidgetOfExactType(type); + return provider.bloc; + } + + static Type _typeOf() => T; +} + +class _BlocProviderState extends State> { + @override + void dispose() { + widget.bloc.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} diff --git a/example/lib/count_bloc.dart b/example/lib/count_bloc.dart new file mode 100644 index 000000000..15ce9dc17 --- /dev/null +++ b/example/lib/count_bloc.dart @@ -0,0 +1,19 @@ +import 'dart:async'; +import 'package:Parse_example/blocProvider.dart'; +import 'package:rxdart/rxdart.dart'; + +class CountBLoC implements BlocBase { + int _count = 0; + var _countController = StreamController.broadcast(); + var _subject = BehaviorSubject(); + Stream get stream => _subject.stream; + int get value => _count; + + increment() { + _countController.sink.add(++_count); + } + + dispose() { + _countController.close(); + } +} diff --git a/example/lib/gametest/GameCommunication.dart b/example/lib/gametest/GameCommunication.dart new file mode 100644 index 000000000..ecd4ea8a3 --- /dev/null +++ b/example/lib/gametest/GameCommunication.dart @@ -0,0 +1,116 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'WebSocketsNotifications.dart'; + +/// +/// Again, application-level global variable +/// +GameCommunication game = new GameCommunication(); + +class GameCommunication { + static final GameCommunication _game = new GameCommunication._internal(); + + /// + /// At first initialization, the player has not yet provided any name + /// + String _playerName = ""; + + /// + /// Before the "join" action, the player has no unique ID + /// + String _playerID = ""; + + factory GameCommunication() { + return _game; + } + + GameCommunication._internal() { + /// + /// Let's initialize the WebSockets communication + /// + sockets.initCommunication(); + + /// + /// and ask to be notified as soon as a message comes in + /// + sockets.addListener(_onMessageReceived); + } + + /// + /// Getter to return the player's name + /// + String get playerName => _playerName; + + /// ---------------------------------------------------------- + /// Common handler for all received messages, from the server + /// ---------------------------------------------------------- + _onMessageReceived(serverMessage) { + /// + /// As messages are sent as a String + /// let's deserialize it to get the corresponding + /// JSON object + /// + Map message = json.decode(serverMessage); + + switch (message["action"]) { + + /// + /// When the communication is established, the server + /// returns the unique identifier of the player. + /// Let's record it + /// + case 'connect': + _playerID = message["data"]; + break; + + /// + /// For any other incoming message, we need to + /// dispatch it to all the listeners + /// + default: + _listeners.forEach((Function callback) { + callback(message); + }); + break; + } + } + + /// ---------------------------------------------------------- + /// Common method to send requests to the server + /// ---------------------------------------------------------- + send(String action, String data) { + /// + /// When a player joins, we need to record the name + /// he provides + /// + if (action == 'join') { + _playerName = data; + } + + /// + /// Send the action to the server + /// To send the message, we need to serialize the JSON + /// + sockets.send(json.encode({"action": action, "data": data})); + } + + /// ========================================================== + /// + /// Listeners to allow the different pages to be notified + /// when messages come in + /// + ObserverList _listeners = new ObserverList(); + + /// --------------------------------------------------------- + /// Adds a callback to be invoked in case of incoming + /// notification + /// --------------------------------------------------------- + addListener(Function callback) { + _listeners.add(callback); + } + + removeListener(Function callback) { + _listeners.remove(callback); + } +} diff --git a/example/lib/gametest/GamePage.dart b/example/lib/gametest/GamePage.dart new file mode 100644 index 000000000..82d081475 --- /dev/null +++ b/example/lib/gametest/GamePage.dart @@ -0,0 +1,163 @@ +import 'package:Parse_example/gametest/GameCommunication.dart'; +import 'package:flutter/material.dart'; + +class GamePage extends StatefulWidget { + GamePage({ + Key key, + this.opponentName, + this.character, + }) : super(key: key); + + /// + /// Name of the opponent + /// + final String opponentName; + + /// + /// Character to be used by the player for his/her moves ("X" or "O") + /// + final String character; + + @override + _GamePageState createState() => _GamePageState(); +} + +class _GamePageState extends State { + /// + /// One game in terms of grid cells. + /// When the user plays, one of this cells is filled with "X" or "O" + /// + List grid = ["", "", "", "", "", "", "", "", ""]; + + @override + void initState() { + super.initState(); + + /// + /// Ask to be notified when a message from the server + /// comes in. + /// + game.addListener(_onAction); + } + + @override + void dispose() { + game.removeListener(_onAction); + super.dispose(); + } + + /// --------------------------------------------------------- + /// The opponent took an action + /// Handler of these actions + /// --------------------------------------------------------- + _onAction(message) { + switch (message["action"]) { + + /// + /// The opponent resigned, so let's leave this screen + /// + case 'resigned': + Navigator.of(context).pop(); + break; + + /// + /// The opponent played a move. + /// So record it and rebuild the board + /// + case 'play': + var data = (message["data"] as String).split(';'); + grid[int.parse(data[0])] = data[1]; + + // Force rebuild + setState(() {}); + break; + } + } + + /// --------------------------------------------------------- + /// This player resigns + /// We need to send this notification to the other player + /// Then, leave this screen + /// --------------------------------------------------------- + _doResign() { + game.send('resign', ''); + Navigator.of(context).pop(); + } + + @override + Widget build(BuildContext context) { + return new SafeArea( + top: false, + bottom: false, + child: new Scaffold( + appBar: new AppBar( + title: new Text('Game against: ${widget.opponentName}', + style: new TextStyle(fontSize: 16.0)), + actions: [ + new RaisedButton( + onPressed: _doResign, + child: new Text('Resign'), + ), + ]), + body: _buildBoard(), + ), + ); + } + + /// -------------------------------------------------------- + /// Builds the Game Board. + /// -------------------------------------------------------- + Widget _buildBoard() { + return new SafeArea( + top: false, + bottom: false, + child: new GridView.builder( + gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + ), + itemCount: 9, + itemBuilder: (BuildContext context, int index) { + return _gridItem(index); + }, + ), + ); + } + + Widget _gridItem(int index) { + Color color = grid[index] == "X" ? Colors.blue : Colors.red; + + return new InkWell( + onTap: () { + /// + /// The user taps a cell. + /// If the latter is empty, let's put this player's character + /// and notify the other player. + /// Repaint the board + /// + if (grid[index] == "") { + grid[index] = widget.character; + + /// + /// To send a move, we provide the cell index + /// and the character of this player + /// + game.send('play', '$index;${widget.character}'); + + /// Force the board repaint + setState(() {}); + } + }, + child: new GridTile( + child: new Card( + child: new FittedBox( + fit: BoxFit.contain, + child: new Text(grid[index], + style: new TextStyle( + fontSize: 50.0, + color: color, + ))), + ), + ), + ); + } +} diff --git a/example/lib/gametest/StartPage.dart b/example/lib/gametest/StartPage.dart new file mode 100644 index 000000000..8b3bd15e8 --- /dev/null +++ b/example/lib/gametest/StartPage.dart @@ -0,0 +1,197 @@ +import 'package:Parse_example/gametest/GameCommunication.dart'; +import 'package:Parse_example/gametest/GamePage.dart'; +import 'package:flutter/material.dart'; + +class StartPage extends StatefulWidget { + @override + _StartPageState createState() => _StartPageState(); +} + +class _StartPageState extends State { + static final TextEditingController _name = new TextEditingController(); + String playerName; + List playersList = []; + + @override + void initState() { + super.initState(); + + /// + /// Ask to be notified when messages related to the game + /// are sent by the server + /// + game.addListener(_onGameDataReceived); + } + + @override + void dispose() { + game.removeListener(_onGameDataReceived); + super.dispose(); + } + + /// ------------------------------------------------------------------- + /// This routine handles all messages that are sent by the server. + /// In this page, only the following 2 actions have to be processed + /// - players_list + /// - new_game + /// ------------------------------------------------------------------- + _onGameDataReceived(message) { + switch (message["action"]) { + + /// + /// Each time a new player joins, we need to + /// * record the new list of players + /// * rebuild the list of all the players + /// + case "players_list": + playersList = message["data"]; + + // force rebuild + setState(() {}); + break; + + /// + /// When a game is launched by another player, + /// we accept the new game and automatically redirect + /// to the game board. + /// As we are not the new game initiator, we will be + /// using the "O" + /// + case 'new_game': + Navigator.push( + context, + new MaterialPageRoute( + builder: (BuildContext context) => new GamePage( + opponentName: message["data"], // Name of the opponent + character: 'O', + ), + )); + break; + } + } + + /// ----------------------------------------------------------- + /// If the user has not yet joined, let the user enter + /// his/her name and join the list of players + /// ----------------------------------------------------------- + Widget _buildJoin() { + if (game.playerName != "") { + return new Container(); + } + return new Container( + padding: const EdgeInsets.all(16.0), + child: new Column( + children: [ + new TextField( + controller: _name, + keyboardType: TextInputType.text, + decoration: new InputDecoration( + hintText: 'Enter your name', + contentPadding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), + border: new OutlineInputBorder( + borderRadius: new BorderRadius.circular(32.0), + ), + icon: const Icon(Icons.person), + ), + ), + new Padding( + padding: const EdgeInsets.all(8.0), + child: new RaisedButton( + onPressed: _onGameJoin, + child: new Text('Join...'), + ), + ), + ], + ), + ); + } + + /// ------------------------------------------------------ + /// The user wants to join, so let's send his/her name + /// As the user has a name, we may now show the other players + /// ------------------------------------------------------ + _onGameJoin() { + game.send('join', _name.text); + + /// Force a rebuild + setState(() {}); + } + + /// ------------------------------------------------------ + /// Builds the list of players + /// ------------------------------------------------------ + Widget _playersList() { + /// + /// If the user has not yet joined, do not display + /// the list of players + /// + if (game.playerName == "") { + return new Container(); + } + + /// + /// Display the list of players. + /// For each of them, put a Button that could be used + /// to launch a new game + /// + List children = playersList.map((playerInfo) { + return new ListTile( + title: new Text(playerInfo["name"]), + trailing: new RaisedButton( + onPressed: () { + _onPlayGame(playerInfo["name"], playerInfo["id"]); + }, + child: new Text('Play'), + ), + ); + }).toList(); + + return new Column( + children: children, + ); + } + + /// -------------------------------------------------------------- + /// We launch a new Game, we need to: + /// * send the action "new_game", together with the ID + /// of the opponent we choosed + /// * redirect to the game board + /// As we are the game initiator, we will play with the "X" + /// -------------------------------------------------------------- + _onPlayGame(String opponentName, String opponentId) { + // We need to send the opponentId to initiate a new game + game.send('new_game', opponentId); + + Navigator.push( + context, + new MaterialPageRoute( + builder: (BuildContext context) => new GamePage( + opponentName: opponentName, + character: 'X', + ), + )); + } + + @override + Widget build(BuildContext context) { + return new SafeArea( + bottom: false, + top: false, + child: Scaffold( + appBar: new AppBar( + title: new Text('TicTacToe'), + ), + body: SingleChildScrollView( + child: new Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _buildJoin(), + new Text('List of players:'), + _playersList(), + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/gametest/WebSocketsNotifications.dart b/example/lib/gametest/WebSocketsNotifications.dart new file mode 100644 index 000000000..09b6938d3 --- /dev/null +++ b/example/lib/gametest/WebSocketsNotifications.dart @@ -0,0 +1,115 @@ +import 'package:flutter/foundation.dart'; +import 'package:web_socket_channel/io.dart'; + +/// +/// Application-level global variable to access the WebSockets +/// +WebSocketsNotifications sockets = new WebSocketsNotifications(); + +const String _SERVER_ADDRESS = "ws://118.24.162.252:2018/parse"; + +class WebSocketsNotifications { + static final WebSocketsNotifications _sockets = + new WebSocketsNotifications._internal(); + factory WebSocketsNotifications() { + return _sockets; + } + + WebSocketsNotifications._internal(); + + /// + /// The WebSocket "open" channel + /// + IOWebSocketChannel _channel; + + /// + /// Is the connection established? + /// + bool _isOn = false; + + /// + /// Listeners + /// List of methods to be called when a new message + /// comes in. + /// + ObserverList _listeners = new ObserverList(); + + /// ---------------------------------------------------------- + /// Initialization the WebSockets connection with the server + /// ---------------------------------------------------------- + initCommunication() async { + /// + /// Just in case, close any previous communication + /// + reset(); + + /// + /// Open a new WebSocket communication + /// + try { + _channel = new IOWebSocketChannel.connect(_SERVER_ADDRESS); + _isOn = true; + + /// + /// Start listening to new notifications / messages + /// + _channel.stream.listen(_onReceptionOfMessageFromServer, + onError: (error, StackTrace stackTrace) { + // error handling + }, onDone: () { + // communication has been closed + _isOn = false; + }); + } catch (e) { + /// + /// General error handling + /// TODO + /// + } + } + + /// ---------------------------------------------------------- + /// Closes the WebSocket communication + /// ---------------------------------------------------------- + reset() { + if (_channel != null) { + if (_channel.sink != null) { + _channel.sink.close(); + _isOn = false; + } + } + } + + /// --------------------------------------------------------- + /// Sends a message to the server + /// --------------------------------------------------------- + send(String message) { + if (_channel != null) { + if (_channel.sink != null && _isOn) { + _channel.sink.add(message); + } + } + } + + /// --------------------------------------------------------- + /// Adds a callback to be invoked in case of incoming + /// notification + /// --------------------------------------------------------- + addListener(Function callback) { + _listeners.add(callback); + } + + removeListener(Function callback) { + _listeners.remove(callback); + } + + /// ---------------------------------------------------------- + /// Callback which is invoked each time that we are receiving + /// a message from the server + /// ---------------------------------------------------------- + _onReceptionOfMessageFromServer(message) { + _listeners.forEach((Function callback) { + callback(message); + }); + } +} diff --git a/example/lib/liveQueryBloc.dart b/example/lib/liveQueryBloc.dart new file mode 100644 index 000000000..bf679b278 --- /dev/null +++ b/example/lib/liveQueryBloc.dart @@ -0,0 +1,73 @@ +import 'dart:async'; + +import 'package:Parse_example/blocProvider.dart'; +import 'package:Parse_example/parseData.dart'; +import 'package:parse_server_sdk/network/parse_livequery.dart'; +import 'package:rxdart/rxdart.dart'; + +class LiveQueryBloc implements BlocBase { + /// + /// A stream only meant to return whether THIS movie is part of the parseLives + /// + final BehaviorSubject _parseLiveStreamController = + BehaviorSubject(); + Stream get outParseLiveStream => + _parseLiveStreamController.stream.asBroadcastStream(); + + /// + /// Stream of all the parseLives + /// + final StreamController _parseLiveController = + StreamController.broadcast(); + Stream get outParseLiveStreams => _parseLiveController.stream; + Sink get inParseLives => _parseLiveController.sink; + + /// + /// Constructor + /// + LiveQueryBloc({ParseData parseData, LiveQuery liveQuery}) { + // liveQuery.connect(); + print("LiveQueryBloc: $LiveQueryBloc"); + // Future> s = + // (liveQuery.channel as IOWebSocketChannel).stream.toList(); + // var v = liveQuery.channel.closeCode; + // print("channel.closeCode: $v"); + // if (liveQuery.channel.closeCode == null) liveQuery.connect(); + var s = liveQuery.channel.stream; + _parseLiveStreamController.addStream(s); + // var s = Stream.fromFuture(liveQuery.channel); + // _isParseLiveController.addStream(s); + // _isParseLiveController.stream.asBroadcastStream(); + + _parseLiveStreamController.stream.listen((_) { + var v = liveQuery.channel.closeCode; + print("channel.closeCode: $v"); + // print("readyState: $liveQuery.c") + // if (liveQuery.channel.closeCode != null) liveQuery.connect(); + + print("liveQuery.channel: ${liveQuery.channel} \n---> $_"); + // _isParseLiveController.sink.add(_); + // _isParseLiveController.sink.addStream(liveQuery.channel); + // print(JsonEncoder().convert(_)); + + // Map actionData = JsonDecoder().convert(_); + // print(JsonEncoder().convert(actionData)); + // print(eventCallbacks); + // liveQuery.close(); + }); + + // + // We are listening to all parseLives + // + // _parseLiveController.stream + // // but, we only consider the one that matches THIS one + // .map((list) => list.any((item) => item.id == parseData.id)) + // // if any, we notify that it is part of the parseLives + // .listen((isParseLive) => _isParseLiveController.add(isParseLive)); + } + + void dispose() { + _parseLiveController.close(); + _parseLiveStreamController.close(); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 32cb1c38f..d2111ff5b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,12 +1,18 @@ import 'dart:async'; +import 'dart:convert'; +import 'package:Parse_example/application_constants.dart'; +import 'package:Parse_example/diet_plan.dart'; +import 'package:Parse_example/myHome.dart'; +import 'package:english_words/english_words.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_plugin_example/application_constants.dart'; -import 'package:flutter_plugin_example/diet_plan.dart'; +import 'package:parse_server_sdk/network/parse_http_client.dart'; +import 'package:parse_server_sdk/network/parse_livequery.dart'; import 'package:parse_server_sdk/objects/parse_object.dart'; import 'package:parse_server_sdk/network/parse_query.dart'; import 'package:parse_server_sdk/objects/parse_user.dart'; import 'package:parse_server_sdk/parse.dart'; +import 'package:web_socket_channel/io.dart'; void main() => runApp(new MyApp()); @@ -15,23 +21,80 @@ class MyApp extends StatefulWidget { _MyAppState createState() => new _MyAppState(); } -class _MyAppState extends State { +class _MyAppState extends State with WidgetsBindingObserver { @override void initState() { - super.initState(); + WidgetsBinding.instance.addObserver(this); initParse(); + super.initState(); // getAllItems(); // getSingleItem(); - query(); + // query(); // queryByContainedIn(); // initUser(); + // updatePost(); } + LiveQuery live; + AppLifecycleState _lastLifecycleState; Future initParse() async { // Initialize parse Parse().initialize(ApplicationConstants.PARSE_APPLICATION_ID, ApplicationConstants.PARSE_SERVER_URL, - masterKey: ApplicationConstants.PARSE_MASTER_KEY); + masterKey: ApplicationConstants.PARSE_MASTER_KEY, + liveQueryUrl: ApplicationConstants.PARSE_LIVE_SERVER_URL); + // parse.liveQuery().connect(); + live = Parse().liveQuery(); + // live.connect(); + live.subscribe("post"); + + // live.subscribe("post"); + // live.on('subscribed', updatePost); + + // Parse().liveQuery().on("update", () => print("object updated!!")); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + // live.subscribe("post"); + // _timerLink = new Timer(const Duration(milliseconds: 1), () { + // _retrieveDynamicLink(); + // }); + } + print("state: $state"); + setState(() { + _lastLifecycleState = state; + }); + } + + t() { + print('subscription opened'); + } + + updatePost() { + DietPlan().get('2pNUgv1CKA').then((response) { + if (response.success) { + print("response.result: ${response.result}"); + var dd = response.result as DietPlan; + dd.objectId = "2pNUgv1CKA"; + dd.name = new WordPair.random().asPascalCase; + // Map bodyData = {}; + // bodyData["title"] = userData.emailAddress; + // print(ApplicationConstants.APP_NAME + + // ":: ${DietPlan.DIET_PLAN}---${(response.result as DietPlan).objectId}--- " + + // (response.result as DietPlan).toString()); + print("dd: $dd"); + var m = new Map(); + m.putIfAbsent("title", () => dd.name); + dd.createObjectData(m); + // return dd; + dd.save(); + } else { + print( + ApplicationConstants.APP_NAME + ": " + response.exception.message); + } + }); } void getAllItems() { @@ -115,7 +178,7 @@ class _MyAppState extends State { Future initUser() async { User() .createNewUser("TestFlutter", "TestPassword123", "TestEmail@Email.com"); - // User().signUp(); + User().signUp(); User().login().then((val) { print("val: $val"); @@ -124,15 +187,23 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { + final title = 'WebSocket Demo'; return new MaterialApp( - home: new Scaffold( - appBar: new AppBar( - title: const Text('Plugin example app'), - ), - body: new Center( - child: new Text('Running Parse init'), - ), + title: title, + // home: new StartPage(), + home: new MyHomePage( + title: title, + channel: live.channel, + // new IOWebSocketChannel.connect("ws://118.24.162.252:2018/parse"), + liveQuery: live, + f: updatePost, ), ); } + + @override + void dispose() { + live.channel.sink.close(); + super.dispose(); + } } diff --git a/example/lib/myHome.dart b/example/lib/myHome.dart new file mode 100644 index 000000000..2773d5c3e --- /dev/null +++ b/example/lib/myHome.dart @@ -0,0 +1,128 @@ +import 'dart:async'; + +import 'package:Parse_example/blocProvider.dart'; +import 'package:Parse_example/liveQueryBloc.dart'; +import 'package:flutter/material.dart'; +import 'package:parse_server_sdk/network/parse_livequery.dart'; +import 'package:parse_server_sdk/objects/parse_object.dart'; +import 'package:parse_server_sdk/parse.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +class MyHomePage extends StatefulWidget { + final String title; + WebSocketChannel channel; + LiveQuery liveQuery; + final Function f; + MyHomePage( + {Key key, + @required this.title, + @required this.channel, + this.liveQuery, + this.f}) + : super(key: key) { + // this.liveQuery.subscribe("post"); + // this.channel = liveQuery.channel as WebSocketChannel; + } + + @override + _MyHomePageState createState() => new _MyHomePageState(); +} + +class _MyHomePageState extends State { + TextEditingController _controller = new TextEditingController(); + LiveQueryBloc bloc; + + var info; + + @override + void initState() { + bloc = new LiveQueryBloc(liveQuery: widget.liveQuery); + info = ""; + super.initState(); + } + + @override + Widget build(BuildContext context) { + // final IncrementBloc bloc = BlocProvider.of(context); + // final LiveQueryBloc bloc = BlocProvider.of(context); + return new Scaffold( + appBar: new AppBar( + title: new Text(widget.title), + ), + body: new Padding( + padding: const EdgeInsets.all(20.0), + child: new Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + new Form( + child: new TextFormField( + controller: _controller, + decoration: new InputDecoration(labelText: 'Send a message'), + ), + ), + new StreamBuilder( + stream: bloc.outParseLiveStream, + builder: (context, snapshot) { + return new Padding( + padding: const EdgeInsets.symmetric(vertical: 24.0), + child: new Text(snapshot.hasData ? '${snapshot.data}' : ''), + ); + }, + ), + new Text(widget.liveQuery.channel.closeCode.toString()), + new Text(info.toString()) + ], + ), + ), + floatingActionButton: new FloatingActionButton( + onPressed: _sendMessage, + tooltip: 'Send message', + child: new Icon(Icons.send), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); + } + + void _sendMessage() { + // if (_controller.text.isNotEmpty) { + widget.f(); + + // widget.liveQuery.channel.closeCode != null && + // if (widget.liveQuery.channel.closeCode == 1002) { + // widget.liveQuery = Parse().liveQuery(); + // widget.liveQuery.subscribe("post"); + // bloc = new LiveQueryBloc(liveQuery: widget.liveQuery); + // } + + // widget.liveQuery.close(); + + // widget.liveQuery.subscribe("post", widget.channel, widget.f); + + // widget.channel.sink.add(_controller.text); + // widget.channel.sink.add(widget.function()); + // } + } + + @override + void didUpdateWidget(MyHomePage oldWidget) { + // TODO: implement didUpdateWidget + + setState(() { + info = + "closeCode: ${widget.liveQuery.channel.closeCode}--${DateTime.now().toString()}"; + }); + if (widget.liveQuery.channel.closeCode == 1002) { + widget.liveQuery = Parse().liveQuery(); + widget.liveQuery.subscribe("post"); + bloc = new LiveQueryBloc(liveQuery: widget.liveQuery); + } + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + // widget.channel.sink.close(); + // liveQuery.dispose(); + bloc.dispose(); + super.dispose(); + } +} diff --git a/example/lib/parseData.dart b/example/lib/parseData.dart new file mode 100644 index 000000000..2a48822ff --- /dev/null +++ b/example/lib/parseData.dart @@ -0,0 +1,23 @@ +class ParseData extends Object { + final int id; + final voteAverage; + final String title; + // final String posterPath; + // final String overview; + + ParseData(this.id, this.voteAverage, this.title); + + ParseData.fromJSON(Map json) + : id = json['objectId'], + voteAverage = json['data'], + title = json['title']; + // posterPath = json['poster_path'], + // overview = json['overview']; + + @override + bool operator ==(dynamic other) => + identical(this, other) || this.id == other.id; + + @override + int get hashCode => id; +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 152aa9e24..95d1e5eb6 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,4 +1,4 @@ -name: flutter_plugin_example +name: Parse_example description: Demonstrates how to use the flutter_plugin plugin. dependencies: @@ -7,10 +7,11 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.2 + english_words: ^3.1.4 + rxdart: ^0.19.0 + # cupertino_icons: ^0.1.2 dev_dependencies: - parse_server_sdk: path: ../ @@ -19,23 +20,18 @@ dev_dependencies: # The following section is specific to Flutter. flutter: - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true - # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg - # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.io/assets-and-images/#resolution-aware. - # For details regarding adding assets from package dependencies, see # https://flutter.io/assets-and-images/#from-packages - # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a diff --git a/lib/network/parse_livequery.dart b/lib/network/parse_livequery.dart index 99219ae33..03bce017c 100644 --- a/lib/network/parse_livequery.dart +++ b/lib/network/parse_livequery.dart @@ -1,11 +1,14 @@ +import 'dart:async'; import "dart:convert"; import 'package:web_socket_channel/io.dart'; import 'package:parse_server_sdk/network/parse_http_client.dart'; import 'dart:io'; +import 'package:web_socket_channel/web_socket_channel.dart'; +import 'package:web_socket_channel/status.dart' as status; class LiveQuery { final ParseHTTPClient client; - var channel; + IOWebSocketChannel channel; Map connectMessage; Map subscribeMessage; Map eventCallbacks = {}; @@ -14,30 +17,112 @@ class LiveQuery { connectMessage = { "op": "connect", "applicationId": client.data.applicationId, + "masterKey": client.data.masterKey }; subscribeMessage = { "op": "subscribe", "requestId": 1, "query": { - "className": null, + "className": "post", "where": {}, } }; + // channel = new IOWebSocketChannel.connect(client.data.liveQueryURL); + // _parseController = StreamController.broadcast(); + // _parseController.stream = channel.stream; + // connect(); + } + + connect() { + channel = new IOWebSocketChannel.connect(client.data.liveQueryURL); } subscribe(String className) async { + connect(); + // var v = channel.closeCode; + // print("channel.closeCode: $v"); + // if (channel.closeCode == null) connect(); + + // if (channel == null) + // channel = new IOWebSocketChannel.connect(client.data.liveQueryURL); + // channel = new IOWebSocketChannel.connect(client.data.liveQueryURL); + // channel.sink.add(JsonEncoder().convert(connectMessage)); + // subscribeMessage['query']['className'] = className.toString(); + // channel.sink.add(JsonEncoder().convert(subscribeMessage)); + // channel.close(status.goingAway); + // channel.stream.listen((message) { + // // handling of the incoming messages + // }, onError: (error, StackTrace stackTrace) { + // // error handling + // }, onDone: () { + // // communication has been closed + // }); + + // ignore: close_sinks + // var webSocket = await WebSocket.connect(client.data.liveQueryURL); + channel.sink.add(JsonEncoder().convert(connectMessage)); + print(JsonEncoder().convert(connectMessage)); + // subscribeMessage['query']['className'] = className; + channel.sink.add(JsonEncoder().convert(subscribeMessage)); + print(JsonEncoder().convert(subscribeMessage)); + // channel.sink.add(JsonEncoder().convert(parseObject)); + + // channel.stream.listen((message) { + // print(JsonEncoder().convert(message)); + + // Map actionData = JsonDecoder().convert(message); + // print(JsonEncoder().convert(actionData)); + // print(eventCallbacks); + // // if (eventCallbacks.containsKey(actionData['op'])) + // // eventCallbacks[actionData['op']](actionData); + // }); + // close(); + } + + subscribe2(String className, channel) async { + channel.sink.add("received!"); // ignore: close_sinks - var webSocket = await WebSocket.connect(client.data.liveQueryURL); - channel = new IOWebSocketChannel(webSocket); channel.sink.add(JsonEncoder().convert(connectMessage)); - subscribeMessage['query']['className'] = className; + print(JsonEncoder().convert(connectMessage)); + // subscribeMessage['query']['className'] = className; channel.sink.add(JsonEncoder().convert(subscribeMessage)); - channel.stream.listen((message) { - Map actionData = JsonDecoder().convert(message); - if (eventCallbacks.containsKey(actionData['op'])) - eventCallbacks[actionData['op']](actionData); - }); + print(JsonEncoder().convert(subscribeMessage)); + // channel.sink.add(JsonEncoder().convert(parseObject)); + + // var po = { + // "op": "subscribe", + // "requestId": 1, + // "query": { + // "className": "post", + // "where": {"objectId": "2pNUgv1CKA"}, + // "fields": ["title"] // Optional + // }, + // }; + + // channel.sink.add(JsonEncoder().convert(po)); + // channel.sink.add(JsonEncoder().convert(function())); + // function(); + // var po1 = { + // "op": "update", + // "requestId": 1, + // "object": { + // "className": "post", + // "objectId": "2pNUgv1CKA", + // "title": "", + // } + // }; + // channel.sink.add(JsonEncoder().convert(po1)); + + // channel.stream.listen((message) { + // print(JsonEncoder().convert(message)); + + // Map actionData = JsonDecoder().convert(message); + // print(JsonEncoder().convert(actionData)); + // print(eventCallbacks); + // if (eventCallbacks.containsKey(actionData['op'])) + // eventCallbacks[actionData['op']](actionData); + // }); } void on(String op, Function callback) { @@ -45,6 +130,57 @@ class LiveQuery { } void close() { - channel.close(); + channel.sink.close(status.goingAway); } + +// other websoket + // static WebSocket _webSocket1; + // static num _id = 0; + + // void connect() { + // closeSocket(); + // Future futureWebSocket = WebSocket.connect( + // client.data.liveQueryURL); // Api.WS_URL 为服务器端的 websocket 服务 + // futureWebSocket.then((WebSocket ws) { + // _webSocket1 = ws; + // _webSocket1.readyState; + // // 监听事件 + // void onData(dynamic content) { + // _id++; + // _sendMessage("收到"); + // _createNotification("新消息", content + _id.toString()); + // } + + // _webSocket1.listen(onData, + // onError: (a) => print("error"), onDone: () => print("done")); + // }); + // } + + // static void closeSocket() { + // if (_webSocket1 != null) _webSocket1.close(); + // } + + // // 向服务器发送消息 + // static void _sendMessage(String message) { + // _webSocket1.add(message); + // } + + // // 手机状态栏弹出推送的消息 + // static void _createNotification(String title, String content) async { + // print("content: $content"); + // // await LocalNotifications.createNotification( + // // id: _id, + // // title: title, + // // content: content, + // // onNotificationClick: NotificationAction( + // // actionText: "some action", + // // callback: _onNotificationClick, + // // payload: "接收成功!"), + // // ); + // } + + // static _onNotificationClick(String payload) { + // // LocalNotifications.removeNotification(_id); + // _sendMessage("消息已被阅读"); + // } } diff --git a/lib/objects/parse_object.dart b/lib/objects/parse_object.dart index ea4559f94..e07b3a47e 100644 --- a/lib/objects/parse_object.dart +++ b/lib/objects/parse_object.dart @@ -19,8 +19,16 @@ class ParseObject implements ParseBaseObject { path = "/classes/$className"; } + createObjectData(Map objectData) { + // ParseDataUser.init(username, password, emailAddress); + this.objectData = objectData; + // return this; + } + Future create([Map objectInitialData]) async { - objectData = {}..addAll(objectData)..addAll(objectInitialData); + if (objectInitialData != null) { + objectData = {}..addAll(objectData)..addAll(objectInitialData); + } final response = this._client.post("${_client.data.serverUrl}$path", body: JsonEncoder().convert(objectData)); @@ -30,14 +38,30 @@ class ParseObject implements ParseBaseObject { } Future save([Map objectInitialData]) { - objectData = {}..addAll(objectData)..addAll(objectInitialData); + if (objectInitialData != null) { + objectData = {}..addAll(objectData)..addAll(objectInitialData); + } if (objectId == null) { return create(objectData); } else { - final response = this._client.put( - _client.data.serverUrl + "$path/$objectId", - body: JsonEncoder().convert(objectData)); + // Map bodyData = {}; + // bodyData["email"] = objectData.name; + // bodyData["password"] = objectData.password; + + Uri tempUri = Uri.parse(_client.data.serverUrl); + + Uri url = Uri( + scheme: tempUri.scheme, + host: tempUri.host, + port: tempUri.port, + path: "${tempUri.path}$path/$objectId"); + + print("put url: $url"); + print("objectData: $objectData"); + final response = + this._client.put(url, body: JsonEncoder().convert(objectData)); return response.then((value) { + print("value: ${value.body}"); return ParseResponse.handleResponse(this, value); }); }