From dd90d3ccb1454054e592c75b19d43e55a57fa2ce Mon Sep 17 00:00:00 2001
From: Phill <phill.wiggins@gmail.com>
Date: Sun, 24 Mar 2019 19:31:58 +0000
Subject: [PATCH 01/25] Added repo example

---
 CHANGELOG.md                                  |   3 +
 README.md                                     |   2 +-
 example/lib/application_constants.dart        |   6 -
 example/lib/data/base/api_error.dart          |   8 +
 example/lib/data/base/api_response.dart       |  29 +++
 example/lib/data/model/diet_plan.dart         |  37 ++++
 .../contract_provider_diet_plan.dart          |  20 ++
 .../diet_plan/provider_api_diet_plan.dart     |  74 +++++++
 .../diet_plan/provider_db_diet_plan.dart      | 160 +++++++++++++++
 .../diet_plan/repository_diet_plan.dart       | 174 ++++++++++++++++
 example/lib/diet_plan.dart                    |  39 ----
 .../constants/application_constants.dart      |   4 +
 .../lib/domain/utils/collection_utils.dart    |   5 +
 example/lib/{ => ui}/main.dart                |  62 ++++--
 example/pubspec.yaml                          |   4 +
 .../repository_diet_plan_api_test.dart        | 192 ++++++++++++++++++
 .../repository_diet_plan_db_test.dart         | 185 +++++++++++++++++
 .../diet_plan/repository_diet_plan_test.dart  | 130 ++++++++++++
 .../repository/repository_mock_utils.dart     |  39 ++++
 lib/src/base/parse_constants.dart             |   2 +-
 20 files changed, 1111 insertions(+), 64 deletions(-)
 delete mode 100644 example/lib/application_constants.dart
 create mode 100644 example/lib/data/base/api_error.dart
 create mode 100644 example/lib/data/base/api_response.dart
 create mode 100644 example/lib/data/model/diet_plan.dart
 create mode 100644 example/lib/data/repositories/diet_plan/contract_provider_diet_plan.dart
 create mode 100644 example/lib/data/repositories/diet_plan/provider_api_diet_plan.dart
 create mode 100644 example/lib/data/repositories/diet_plan/provider_db_diet_plan.dart
 create mode 100644 example/lib/data/repositories/diet_plan/repository_diet_plan.dart
 delete mode 100644 example/lib/diet_plan.dart
 create mode 100644 example/lib/domain/constants/application_constants.dart
 create mode 100644 example/lib/domain/utils/collection_utils.dart
 rename example/lib/{ => ui}/main.dart (77%)
 create mode 100644 example/test/data/repository/diet_plan/repository_diet_plan_api_test.dart
 create mode 100644 example/test/data/repository/diet_plan/repository_diet_plan_db_test.dart
 create mode 100644 example/test/data/repository/diet_plan/repository_diet_plan_test.dart
 create mode 100644 example/test/data/repository/repository_mock_utils.dart

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04efbf907..45c7ff7bc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 1.0.17
+
+
 ## 1.0.16
 Bug fixes
 Fixed object delete
diff --git a/README.md b/README.md
index c910a5f9b..37f1b35c6 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ Want to get involved? Join our Slack channel and help out! (http://flutter-parse
 To install, either add to your pubspec.yaml
 ```yml
 dependencies:  
-    parse_server_sdk: ^1.0.16
+    parse_server_sdk: ^1.0.17
 ```
 or clone this repository and add to your project. As this is an early development with multiple contributors, it is probably best to download/clone and keep updating as an when a new feature is added.
 
diff --git a/example/lib/application_constants.dart b/example/lib/application_constants.dart
deleted file mode 100644
index 9e77a8bc0..000000000
--- a/example/lib/application_constants.dart
+++ /dev/null
@@ -1,6 +0,0 @@
-abstract class ApplicationConstants {
-  static const String keyAppName = "";
-  static const String keyParseApplicationId = "";
-  static const String keyParseMasterKey = "";
-  static const String keyParseServerUrl = "";
-}
\ No newline at end of file
diff --git a/example/lib/data/base/api_error.dart b/example/lib/data/base/api_error.dart
new file mode 100644
index 000000000..ccb11e256
--- /dev/null
+++ b/example/lib/data/base/api_error.dart
@@ -0,0 +1,8 @@
+class ApiError {
+  ApiError(this.code, this.message, this.isTypeOfException, this.type);
+
+  final int code;
+  final String message;
+  final bool isTypeOfException;
+  final String type;
+}
diff --git a/example/lib/data/base/api_response.dart b/example/lib/data/base/api_response.dart
new file mode 100644
index 000000000..787eec3b3
--- /dev/null
+++ b/example/lib/data/base/api_response.dart
@@ -0,0 +1,29 @@
+import 'package:parse_server_sdk/parse_server_sdk.dart';
+
+import 'api_error.dart';
+
+class ApiResponse {
+  ApiResponse(this.success, this.statusCode, this.result, this.error);
+
+  final bool success;
+  final int statusCode;
+  final dynamic result;
+  final ApiError error;
+
+  dynamic getResult<T extends ParseObject>() {
+    return result;
+  }
+}
+
+ApiResponse getApiResponse<T extends ParseObject>(ParseResponse response) {
+  return ApiResponse(response.success, response.statusCode, response.result,
+      getApiError(response.error));
+}
+
+ApiError getApiError(ParseError response) {
+  if (response == null) {
+    return null;
+  }
+  return ApiError(response.code, response.message, response.isTypeOfException,
+      response.type);
+}
diff --git a/example/lib/data/model/diet_plan.dart b/example/lib/data/model/diet_plan.dart
new file mode 100644
index 000000000..8819d6189
--- /dev/null
+++ b/example/lib/data/model/diet_plan.dart
@@ -0,0 +1,37 @@
+import 'dart:core';
+
+import 'package:parse_server_sdk/parse_server_sdk.dart';
+
+const String keyDietPlan = 'Diet_Plans';
+const String keyName = 'Name';
+const String keyDescription = 'Description';
+const String keyProtein = 'Protein';
+const String keyCarbs = 'Carbs';
+const String keyFat = 'Fat';
+const String keyStatus = 'Status';
+
+class DietPlan extends ParseObject implements ParseCloneable {
+  DietPlan() : super(keyDietPlan);
+  DietPlan.clone() : this();
+
+  @override
+  DietPlan clone(Map<String, dynamic> map) => DietPlan.clone()..fromJson(map);
+
+  String get name => get<String>(keyName);
+  set name(String name) => set<String>(keyName, name);
+
+  String get description => get<String>(keyDescription);
+  set description(String description) => set<String>(keyDescription, name);
+
+  num get protein => get<num>(keyProtein);
+  set protein(num protein) => super.set<num>(keyProtein, protein);
+
+  num get carbs => get<num>(keyCarbs);
+  set carbs(num carbs) => set<num>(keyCarbs, carbs);
+
+  num get fat => get<num>(keyFat);
+  set fat(num fat) => set<num>(keyFat, fat);
+
+  int get status => get<int>(keyStatus);
+  set status(int status) => set<int>(keyStatus, status);
+}
diff --git a/example/lib/data/repositories/diet_plan/contract_provider_diet_plan.dart b/example/lib/data/repositories/diet_plan/contract_provider_diet_plan.dart
new file mode 100644
index 000000000..9728acecd
--- /dev/null
+++ b/example/lib/data/repositories/diet_plan/contract_provider_diet_plan.dart
@@ -0,0 +1,20 @@
+import 'package:flutter_plugin_example/data/base/api_response.dart';
+import 'package:flutter_plugin_example/data/model/diet_plan.dart';
+
+abstract class DietPlanProviderContract {
+  Future<ApiResponse> add(DietPlan item);
+
+  Future<ApiResponse> addAll(List<DietPlan> items);
+
+  Future<ApiResponse> update(DietPlan item);
+
+  Future<ApiResponse> updateAll(List<DietPlan> items);
+
+  Future<ApiResponse> remove(DietPlan item);
+
+  Future<ApiResponse> getById(String id);
+
+  Future<ApiResponse> getAll();
+
+  Future<ApiResponse> getNewerThan(DateTime date);
+}
diff --git a/example/lib/data/repositories/diet_plan/provider_api_diet_plan.dart b/example/lib/data/repositories/diet_plan/provider_api_diet_plan.dart
new file mode 100644
index 000000000..0050ceb20
--- /dev/null
+++ b/example/lib/data/repositories/diet_plan/provider_api_diet_plan.dart
@@ -0,0 +1,74 @@
+import 'package:flutter_plugin_example/data/base/api_response.dart';
+import 'package:flutter_plugin_example/data/model/diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/contract_provider_diet_plan.dart';
+import 'package:parse_server_sdk/parse_server_sdk.dart';
+
+class DietPlanProviderApi implements DietPlanProviderContract {
+  DietPlanProviderApi();
+
+  @override
+  Future<ApiResponse> add(DietPlan item) async {
+    return getApiResponse<DietPlan>(await item.save());
+  }
+
+  @override
+  Future<ApiResponse> addAll(List<DietPlan> items) async {
+    final List<DietPlan> responses = List<DietPlan>();
+
+    for (final DietPlan item in items) {
+      final ApiResponse response = await add(item);
+
+      if (!response.success) {
+        return response;
+      }
+
+      responses.add(response.result);
+    }
+
+    return ApiResponse(true, 200, responses, null);
+  }
+
+  @override
+  Future<ApiResponse> getAll() async {
+    return getApiResponse<DietPlan>(await DietPlan().getAll());
+  }
+
+  @override
+  Future<ApiResponse> getById(String id) async {
+    return getApiResponse<DietPlan>(await DietPlan().getObject(id));
+  }
+
+  @override
+  Future<ApiResponse> getNewerThan(DateTime date) async {
+    final QueryBuilder<DietPlan> query = QueryBuilder<DietPlan>(DietPlan())
+      ..whereGreaterThan(keyVarCreatedAt, date);
+    return getApiResponse<DietPlan>(await query.query());
+  }
+
+  @override
+  Future<ApiResponse> remove(DietPlan item) async {
+    return getApiResponse<DietPlan>(await item.delete());
+  }
+
+  @override
+  Future<ApiResponse> update(DietPlan item) async {
+    return getApiResponse<DietPlan>(await item.save());
+  }
+
+  @override
+  Future<ApiResponse> updateAll(List<DietPlan> items) async {
+    final List<DietPlan> responses = List<DietPlan>();
+
+    for (final DietPlan item in items) {
+      final ApiResponse response = await update(item);
+
+      if (!response.success) {
+        return response;
+      }
+
+      responses.add(response.result);
+    }
+
+    return ApiResponse(true, 200, responses, null);
+  }
+}
diff --git a/example/lib/data/repositories/diet_plan/provider_db_diet_plan.dart b/example/lib/data/repositories/diet_plan/provider_db_diet_plan.dart
new file mode 100644
index 000000000..17e7e78f4
--- /dev/null
+++ b/example/lib/data/repositories/diet_plan/provider_db_diet_plan.dart
@@ -0,0 +1,160 @@
+import 'dart:convert' as json;
+
+import 'package:flutter_plugin_example/data/base/api_error.dart';
+import 'package:flutter_plugin_example/data/base/api_response.dart';
+import 'package:flutter_plugin_example/data/model/diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/contract_provider_diet_plan.dart';
+import 'package:sembast/sembast.dart';
+
+class DietPlanProviderDB implements DietPlanProviderContract {
+  DietPlanProviderDB(this._db, this._store);
+
+  final Store _store;
+  final Database _db;
+
+  @override
+  Future<ApiResponse> add(DietPlan item) async {
+    final Map<String, dynamic> values = convertItemToStorageMap(item);
+    final Record recordToAdd = Record(_store, values, item.objectId);
+    final Record recordFromDB = await _db.putRecord(recordToAdd);
+    return ApiResponse(
+        true, 200, convertRecordToItem(record: recordFromDB), null);
+  }
+
+  @override
+  Future<ApiResponse> addAll(List<DietPlan> items) async {
+    final List<DietPlan> itemsInDb = List<DietPlan>();
+
+    for (final DietPlan item in items) {
+      final ApiResponse response = await add(item);
+      if (response.success) {
+        final DietPlan itemInDB = response.result;
+        itemsInDb.add(itemInDB);
+      }
+    }
+
+    if (itemsInDb.isEmpty) {
+      return errorResponse;
+    } else {
+      return ApiResponse(true, 200, itemsInDb, null);
+    }
+  }
+
+  @override
+  Future<ApiResponse> getAll() async {
+    final List<DietPlan> foodItems = List<DietPlan>();
+
+    final List<SortOrder> sortOrders = List<SortOrder>();
+    sortOrders.add(SortOrder(keyName));
+    final Finder finder = Finder(sortOrders: sortOrders);
+    final List<Record> records = await _store.findRecords(finder);
+
+    if (records.isNotEmpty) {
+      for (final Record record in records) {
+        final DietPlan userFood = convertRecordToItem(record: record);
+        foodItems.add(userFood);
+      }
+    } else {
+      return errorResponse;
+    }
+
+    return ApiResponse(true, 200, foodItems, null);
+  }
+
+  @override
+  Future<ApiResponse> getById(String id) async {
+    final Record record = await _store.getRecord(id);
+    if (record != null) {
+      final DietPlan userFood = convertRecordToItem(record: record);
+      return ApiResponse(true, 200, userFood, null);
+    } else {
+      return errorResponse;
+    }
+  }
+
+  @override
+  Future<ApiResponse> getNewerThan(DateTime date) async {
+    final List<DietPlan> foodItems = List<DietPlan>();
+
+    final Finder finder = Finder(
+        filter:
+            Filter.greaterThan('keyUpdatedAt', date.millisecondsSinceEpoch));
+
+    final List<Record> records = await _store.findRecords(finder);
+
+    for (final Record record in records) {
+      final DietPlan convertedDietPlan = convertRecordToItem(record: record);
+      foodItems.add(convertedDietPlan);
+    }
+
+    if (records == null) {
+      return errorResponse;
+    }
+
+    return ApiResponse(true, 200, foodItems, null);
+  }
+
+  @override
+  Future<ApiResponse> remove(DietPlan item) async {
+    await _store.delete(item.objectId);
+    return ApiResponse(true, 200, null, null);
+  }
+
+  @override
+  Future<ApiResponse> updateAll(List<DietPlan> items) async {
+    final List<DietPlan> updatedItems = List<DietPlan>();
+
+    for (final DietPlan item in items) {
+      final ApiResponse response = await update(item);
+      if (response.success) {
+        final DietPlan responseItem = response.result;
+        updatedItems.add(responseItem);
+      }
+    }
+
+    if (updatedItems == null) {
+      return errorResponse;
+    }
+
+    return ApiResponse(true, 200, updatedItems, null);
+  }
+
+  @override
+  Future<ApiResponse> update(DietPlan item) async {
+    final Map<String, dynamic> values = convertItemToStorageMap(item);
+    final dynamic returnedItems = await _store.update(values, item.objectId);
+
+    if (returnedItems == null) {
+      return add(item);
+    }
+
+    return ApiResponse(
+        true, 200, convertRecordToItem(values: returnedItems), null);
+  }
+
+  Map<String, dynamic> convertItemToStorageMap(DietPlan item) {
+    final Map<String, dynamic> values = Map<String, dynamic>();
+    // ignore: invalid_use_of_protected_member
+    values['value'] = json.jsonEncode(item.toJson(full: true));
+    values['objectId'] = item.objectId;
+    if (item.updatedAt != null) {
+      values['updatedAt'] = item.updatedAt.millisecondsSinceEpoch;
+    }
+
+    return values;
+  }
+
+  DietPlan convertRecordToItem({Record record, Map<String, dynamic> values}) {
+    try {
+      values ??= record.value;
+      final DietPlan item =
+          DietPlan.clone().fromJson(json.jsonDecode(values['value']));
+      return item;
+    } catch (e) {
+      return null;
+    }
+  }
+
+  static ApiError error = ApiError(1, 'No records found', false, '');
+  ApiResponse errorResponse = ApiResponse(false, 1, null, error);
+}
diff --git a/example/lib/data/repositories/diet_plan/repository_diet_plan.dart b/example/lib/data/repositories/diet_plan/repository_diet_plan.dart
new file mode 100644
index 000000000..9d1b4775c
--- /dev/null
+++ b/example/lib/data/repositories/diet_plan/repository_diet_plan.dart
@@ -0,0 +1,174 @@
+import 'package:flutter_plugin_example/data/base/api_response.dart';
+import 'package:flutter_plugin_example/data/model/diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/contract_provider_diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/provider_api_diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/provider_db_diet_plan.dart';
+import 'package:flutter_plugin_example/domain/utils/collection_utils.dart';
+import 'package:sembast/sembast.dart';
+
+class DietPlanRepository implements DietPlanProviderContract {
+  static DietPlanRepository init(Database dbConnection,
+      {DietPlanProviderContract repositoryDB,
+      DietPlanProviderContract repositoryAPI}) {
+    final DietPlanRepository repository = DietPlanRepository();
+
+    if (repositoryDB != null) {
+      repository.db = repositoryDB;
+    } else {
+      final Store store = dbConnection.getStore('repository-$keyDietPlan');
+      repository.db = DietPlanProviderDB(dbConnection, store);
+    }
+
+    if (repositoryAPI != null) {
+      repository.api = repositoryAPI;
+    } else {
+      repository.api = DietPlanProviderApi();
+    }
+
+    return repository;
+  }
+
+  DietPlanProviderContract api;
+  DietPlanProviderContract db;
+
+  @override
+  Future<ApiResponse> add(DietPlan item,
+      {bool apiOnly = false, bool dbOnly = false}) async {
+    if (apiOnly) {
+      return await api.add(item);
+    }
+    if (dbOnly) {
+      return await db.add(item);
+    }
+
+    final ApiResponse response = await api.add(item);
+    if (response.success) {
+      await db.add(item);
+    }
+
+    return response;
+  }
+
+  @override
+  Future<ApiResponse> addAll(List<DietPlan> items,
+      {bool apiOnly = false, bool dbOnly = false}) async {
+    if (apiOnly) {
+      return await api.addAll(items);
+    }
+    if (dbOnly) {
+      return await db.addAll(items);
+    }
+
+    final ApiResponse response = await api.addAll(items);
+
+    if (response.success && isValidList(response.result)) {
+      await db.addAll(items);
+    }
+
+    return response;
+  }
+
+  @override
+  Future<ApiResponse> getAll(
+      {bool fromApi = false, bool fromDb = false}) async {
+    if (fromApi) {
+      return api.getAll();
+    }
+    if (fromDb) {
+      return db.getAll();
+    }
+
+    ApiResponse response = await db.getAll();
+    if (response.result == null) {
+      response = await api.getAll();
+    }
+
+    return db.getAll();
+  }
+
+  @override
+  Future<ApiResponse> getById(String id,
+      {bool fromApi = false, bool fromDb = false}) async {
+    if (fromApi) {
+      return api.getAll();
+    }
+    if (fromDb) {
+      return db.getAll();
+    }
+
+    ApiResponse response = await db.getById(id);
+    if (response.result == null) {
+      response = await api.getById(id);
+    }
+
+    return response;
+  }
+
+  @override
+  Future<ApiResponse> getNewerThan(DateTime date,
+      {bool fromApi = false, bool fromDb = false}) async {
+    if (fromApi) {
+      return await api.getNewerThan(date);
+    }
+    if (fromDb) {
+      return await db.getNewerThan(date);
+    }
+
+    final ApiResponse response = await api.getNewerThan(date);
+
+    if (response.success && response.result != null) {
+      final List<DietPlan> list = response.result;
+      await db.updateAll(list);
+    }
+
+    return response;
+  }
+
+  @override
+  Future<ApiResponse> remove(DietPlan item,
+      {bool apiOnly = false, bool dbOnly = false}) async {
+    if (apiOnly) {
+      return await api.remove(item);
+    }
+    if (dbOnly) {
+      return await db.remove(item);
+    }
+
+    ApiResponse response = await api.remove(item);
+    response = await db.remove(item);
+    return response;
+  }
+
+  @override
+  Future<ApiResponse> update(DietPlan item,
+      {bool apiOnly = false, bool dbOnly = false}) async {
+    if (apiOnly) {
+      return await api.update(item);
+    }
+    if (dbOnly) {
+      return await db.update(item);
+    }
+
+    ApiResponse response = await api.update(item);
+    response = await db.update(item);
+    return response;
+  }
+
+  @override
+  Future<ApiResponse> updateAll(List<DietPlan> items,
+      {bool apiOnly = false, bool dbOnly = false}) async {
+    if (apiOnly) {
+      await api.updateAll(items);
+    }
+    if (dbOnly) {
+      await db.updateAll(items);
+    }
+
+    ApiResponse response = await api.updateAll(items);
+    if (response.success && isValidList(response.result)) {
+      response = await db.updateAll(items);
+    }
+
+    return response;
+  }
+}
diff --git a/example/lib/diet_plan.dart b/example/lib/diet_plan.dart
deleted file mode 100644
index 747bc65bc..000000000
--- a/example/lib/diet_plan.dart
+++ /dev/null
@@ -1,39 +0,0 @@
-import 'dart:core';
-
-import 'package:parse_server_sdk/parse_server_sdk.dart';
-
-class DietPlan extends ParseObject implements ParseCloneable {
-  DietPlan() : super(_keyTableName, debug: true);
-  DietPlan.clone() : this();
-
-  /// Looks strangely hacky but due to Flutter not using reflection, we have to
-  /// mimic a clone
-  @override
-  DietPlan clone(Map<String, dynamic> map) => DietPlan.clone()..fromJson(map);
-
-  static const String _keyTableName = 'Diet_Plans';
-  static const String keyName = 'Name';
-  static const String keyDescription = 'Description';
-  static const String keyProtein = 'Protein';
-  static const String keyCarbs = 'Carbs';
-  static const String keyFat = 'Fat';
-  static const String keyStatus = 'Status';
-
-  String get name => get<String>(keyName);
-  set name(String name) => set<String>(keyName, name);
-
-  String get description => get<String>(keyDescription);
-  set description(String description) => set<String>(keyDescription, name);
-
-  int get protein => get<int>(keyProtein);
-  set protein(int protein) => super.set<int>(keyProtein, protein);
-
-  int get carbs => get<int>(keyCarbs);
-  set carbs(int carbs) => set<int>(keyCarbs, carbs);
-
-  int get fat => get<int>(keyFat);
-  set fat(int fat) => set<int>(keyFat, fat);
-
-  int get status => get<int>(keyStatus);
-  set status(int status) => set<int>(keyStatus, status);
-}
diff --git a/example/lib/domain/constants/application_constants.dart b/example/lib/domain/constants/application_constants.dart
new file mode 100644
index 000000000..01491a0dc
--- /dev/null
+++ b/example/lib/domain/constants/application_constants.dart
@@ -0,0 +1,4 @@
+const String keyApplicationName = '';
+const String keyParseApplicationId = '';
+const String keyParseMasterKey = '';
+const String keyParseServerUrl = '';
diff --git a/example/lib/domain/utils/collection_utils.dart b/example/lib/domain/utils/collection_utils.dart
new file mode 100644
index 000000000..5451c85ea
--- /dev/null
+++ b/example/lib/domain/utils/collection_utils.dart
@@ -0,0 +1,5 @@
+bool isValidList(List<dynamic> list) =>
+    (list != null && list.isNotEmpty) ? true : false;
+
+bool isValidMap(Map<String, dynamic> map) =>
+    (map != null && map.isNotEmpty) ? true : false;
diff --git a/example/lib/main.dart b/example/lib/ui/main.dart
similarity index 77%
rename from example/lib/main.dart
rename to example/lib/ui/main.dart
index 946941e82..1a578609c 100644
--- a/example/lib/main.dart
+++ b/example/lib/ui/main.dart
@@ -1,6 +1,10 @@
+import 'dart:convert';
+
 import 'package:flutter/material.dart';
-import 'package:flutter_plugin_example/application_constants.dart';
-import 'package:flutter_plugin_example/diet_plan.dart';
+import 'package:flutter_plugin_example/data/base/api_response.dart';
+import 'package:flutter_plugin_example/data/model/diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/repository_diet_plan.dart';
+import 'package:flutter_plugin_example/domain/constants/application_constants.dart';
 import 'package:flutter_stetho/flutter_stetho.dart';
 import 'package:parse_server_sdk/parse_server_sdk.dart';
 
@@ -38,21 +42,25 @@ class _MyAppState extends State<MyApp> {
 
   Future<void> initParse() async {
     // Initialize parse
-    Parse().initialize(ApplicationConstants.keyParseApplicationId,
-        ApplicationConstants.keyParseServerUrl,
-        masterKey: ApplicationConstants.keyParseMasterKey, debug: true);
+    Parse().initialize(keyParseApplicationId, keyParseServerUrl,
+        masterKey: keyParseMasterKey, debug: true);
 
     // Check server is healthy and live - Debug is on in this instance so check logs for result
     final ParseResponse response = await Parse().healthCheck();
     if (response.success) {
-      runTestQueries();
+      await runTestQueries();
     } else {
       print('Server health check failed');
     }
   }
 
-  void runTestQueries() {
-    createItem();
+  Future<void> runTestQueries() async {
+    // Basic repository example
+    await repositoryAddItems();
+    await repositoryGetAllItems();
+
+    // Basic usage
+    /*createItem();
     getAllItems();
     getAllItemsByName();
     getSingleItem();
@@ -60,7 +68,7 @@ class _MyAppState extends State<MyApp> {
     query();
     initUser();
     function();
-    functionWithParameters();
+    functionWithParameters();*/
   }
 
   Future<void> createItem() async {
@@ -71,9 +79,7 @@ class _MyAppState extends State<MyApp> {
     final ParseResponse apiResponse = await newObject.create();
 
     if (apiResponse.success && apiResponse.result != null) {
-      print(ApplicationConstants.keyAppName +
-          ': ' +
-          apiResponse.result.toString());
+      print(keyAppName + ': ' + apiResponse.result.toString());
     }
   }
 
@@ -83,7 +89,7 @@ class _MyAppState extends State<MyApp> {
 
     if (apiResponse.success && apiResponse.result != null) {
       for (final ParseObject testObject in apiResponse.result) {
-        print(ApplicationConstants.keyAppName + ': ' + testObject.toString());
+        print(keyAppName + ': ' + testObject.toString());
       }
     }
   }
@@ -93,10 +99,10 @@ class _MyAppState extends State<MyApp> {
 
     if (apiResponse.success && apiResponse.result != null) {
       for (final DietPlan plan in apiResponse.result) {
-        print(ApplicationConstants.keyAppName + ': ' + plan.name);
+        print(keyAppName + ': ' + plan.name);
       }
     } else {
-      print(ApplicationConstants.keyAppName + ': ' + apiResponse.error.message);
+      print(keyAppName + ': ' + apiResponse.error.message);
     }
   }
 
@@ -124,7 +130,7 @@ class _MyAppState extends State<MyApp> {
         print('Retreiving from pin worked!');
       }
     } else {
-      print(ApplicationConstants.keyAppName + ': ' + apiResponse.error.message);
+      print(keyAppName + ': ' + apiResponse.error.message);
     }
   }
 
@@ -221,7 +227,7 @@ class _MyAppState extends State<MyApp> {
     if (apiResponse.success) {
       final List<ParseUser> users = response.result;
       for (final ParseUser user in users) {
-        print(ApplicationConstants.keyAppName + ': ' + user.toString());
+        print(keyAppName + ': ' + user.toString());
       }
     }
   }
@@ -259,4 +265,26 @@ class _MyAppState extends State<MyApp> {
       print('We have our configs.');
     }
   }
+
+  Future<void> repositoryAddItems() async {
+    final List<DietPlan> dietPlans =
+        const JsonDecoder().convert(dietPlansToAdd);
+
+    final DietPlanRepository repository = DietPlanRepository();
+    final ApiResponse response = await repository.addAll(dietPlans);
+    if (response.success) {
+      print(response.result);
+    }
+  }
+
+  Future<void> repositoryGetAllItems() async {
+    final DietPlanRepository repository = DietPlanRepository();
+    final ApiResponse response = await repository.getAll();
+    if (response.success) {
+      print(response.result);
+    }
+  }
 }
+
+const String dietPlansToAdd =
+    '[{"className":"Diet_Plans","objectId":"RlOj8JGnEX","createdAt":"2017-10-17T10:44:11.355Z","updatedAt":"2018-01-30T10:15:21.228Z","Name":"Textbook","Description":"For an active lifestyle and a straight forward macro plan, we suggest this plan.","Fat":25,"Carbs":50,"Protein":25,"Status":0}]';
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index ba2f6813c..0c81f9927 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -9,10 +9,14 @@ dependencies:
   # Use with the CupertinoIcons class for iOS style icons.
   cupertino_icons: ^0.1.2
   flutter_stetho: ^0.2.2
+  sembast: ^1.13.3+1
 
 dev_dependencies:
   parse_server_sdk:
     path: ../
+  flutter_test:
+    sdk: flutter
+  mockito: ^4.0.0
 
 # For information on the generic Dart part of this file, see the
 # following page: https://www.dartlang.org/tools/pub/pubspec
diff --git a/example/test/data/repository/diet_plan/repository_diet_plan_api_test.dart b/example/test/data/repository/diet_plan/repository_diet_plan_api_test.dart
new file mode 100644
index 000000000..c56b9312d
--- /dev/null
+++ b/example/test/data/repository/diet_plan/repository_diet_plan_api_test.dart
@@ -0,0 +1,192 @@
+// ignore_for_file: invalid_use_of_protected_member
+import 'package:flutter_plugin_example/data/base/api_response.dart';
+import 'package:flutter_plugin_example/data/model/diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/contract_provider_diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/provider_api_diet_plan.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../repository_mock_utils.dart';
+
+void main() {
+  DietPlanProviderContract repository;
+
+  Future<DietPlanProviderContract> getRepository() async {
+    repository ??= DietPlanProviderApi();
+    return repository;
+  }
+
+  setUp(() async {
+    await setupParseInstance();
+    await getRepository();
+  });
+
+  tearDown(() async {
+    repository = null;
+  });
+
+  group('API Integration tests', () {
+    test('create DB instance', () async {
+      expect(true, repository != null);
+    });
+
+    test('add DietPlan from API', () async {
+      // Given
+      final DietPlan expected = getDummyDietPlan();
+      expected.getObjectData()['objectId'] = null;
+
+      // When
+      ApiResponse response = await repository.add(expected);
+      final DietPlan actual = response.result;
+
+      // CLEAR FROM DB
+      response = await repository.remove(actual);
+
+      // Then
+      expect(actual.protein, expected.protein);
+    });
+
+    test('addAll DietPlan from API', () async {
+      // Given
+      final List<DietPlan> actual = List<DietPlan>();
+      final DietPlan item1 = getDummyDietPlan();
+      item1.getObjectData()['objectId'] = null;
+      item1.protein = 5;
+      actual.add(item1);
+      final DietPlan item2 = getDummyDietPlan();
+      item2.getObjectData()['objectId'] = null;
+      item2.protein = 6;
+      actual.add(item2);
+
+      // When
+      final ApiResponse response = await repository.addAll(actual);
+      final List<DietPlan> items = await response.result;
+
+      // CLEAR FROM DB
+      for (final DietPlan item in items) {
+        await repository.remove(item);
+      }
+
+      // Then
+      expect(response.success, true);
+      expect(actual[1].objectId, items[1].objectId);
+    });
+
+    test('getById DietPlan from API', () async {
+      // Given
+      final DietPlan dummy = getDummyDietPlan();
+      dummy.getObjectData()['objectId'] = null;
+
+      // When
+      ApiResponse response = await repository.add(dummy);
+      final DietPlan expected = response.result;
+      response = await repository.getById(expected.objectId);
+      final DietPlan actual = response.result;
+
+      // CLEAR FROM DB
+      response = await repository.remove(actual);
+
+      // Then
+      expect(actual.objectId, expected.objectId);
+      expect(actual.protein, expected.protein);
+    });
+
+    test('getNewerThan DietPlan from API', () async {
+      // Given
+      final DietPlan dummy = getDummyDietPlan();
+      dummy.getObjectData()['objectId'] = null;
+
+      // When
+      final ApiResponse baseResponse = await repository.add(dummy);
+      final DietPlan userFood = baseResponse.result;
+      final ApiResponse responseWithResult = await repository
+          .getNewerThan(DateTime.now().subtract(Duration(days: 1)));
+      final ApiResponse responseWithoutResult =
+          await repository.getNewerThan(DateTime.now().add(Duration(days: 1)));
+
+      // CLEAR FROM DB
+      await repository.remove(userFood);
+
+      // Then
+      expect(responseWithResult.success, true);
+      expect(responseWithoutResult.success, true);
+      expect(responseWithResult.result, isNotNull);
+      expect(responseWithoutResult.result, isNull);
+    });
+
+    test('getAll DietPlan from API', () async {
+      final List<DietPlan> actual = List<DietPlan>();
+
+      final DietPlan item1 = getDummyDietPlan();
+      item1.getObjectData()['objectId'] = null;
+      item1.protein = 5;
+      actual.add(item1);
+      final DietPlan item2 = getDummyDietPlan();
+      item2.getObjectData()['objectId'] = null;
+      item2.protein = 6;
+      actual.add(item2);
+
+      // When
+      final ApiResponse response = await repository.addAll(actual);
+      final List<DietPlan> items = await response.result;
+
+      // CLEAR FROM DB
+      for (final DietPlan item in items) {
+        await repository.remove(item);
+      }
+
+      // Then
+      expect(response.success, true);
+      expect(response.result, isNotNull);
+    });
+
+    test('update DietPlan from API', () async {
+      // Given
+      final DietPlan expected = getDummyDietPlan();
+      expected.getObjectData()['objectId'] = null;
+      ApiResponse response = await repository.add(expected);
+      final DietPlan initialResponse = response.result;
+
+      // When
+      initialResponse.protein = 10;
+      final ApiResponse updateResponse =
+          await repository.update(initialResponse);
+      final DietPlan actual = updateResponse.result;
+
+      // CLEAR FROM DB
+      response = await repository.remove(actual);
+
+      // Then
+      expect(actual.protein, 10);
+    });
+
+    test('updateAll DietPlan from API', () async {
+      // Given
+      final List<DietPlan> actual = List<DietPlan>();
+
+      final DietPlan item1 = getDummyDietPlan();
+      item1.getObjectData()['objectId'] = null;
+      item1.protein = 7;
+      actual.add(item1);
+      final DietPlan item2 = getDummyDietPlan();
+      item2.getObjectData()['objectId'] = null;
+      item2.protein = 8;
+      actual.add(item2);
+      await repository.addAll(actual);
+
+      // When
+      item1.protein = 9;
+      item2.protein = 10;
+      final ApiResponse updateResponse = await repository.updateAll(actual);
+      final List<DietPlan> updated = updateResponse.result;
+
+      // CLEAR FROM DB
+      for (final DietPlan day in updated) {
+        await repository.remove(day);
+      }
+
+      // Then
+      expect(updated[0].protein, 9);
+      expect(updated[1].protein, 10);
+    });
+  });
+}
diff --git a/example/test/data/repository/diet_plan/repository_diet_plan_db_test.dart b/example/test/data/repository/diet_plan/repository_diet_plan_db_test.dart
new file mode 100644
index 000000000..d1d2e8394
--- /dev/null
+++ b/example/test/data/repository/diet_plan/repository_diet_plan_db_test.dart
@@ -0,0 +1,185 @@
+import 'package:flutter_plugin_example/data/base/api_response.dart';
+import 'package:flutter_plugin_example/data/model/diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/contract_provider_diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/provider_db_diet_plan.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:sembast/sembast.dart';
+
+import '../repository_mock_utils.dart';
+
+void main() {
+  DietPlanProviderContract repository;
+
+  Store _getStore(Database database) {
+    return database.getStore('repository_$keyDietPlan');
+  }
+
+  Future<DietPlanProviderContract> getRepository() async {
+    if (repository == null) {
+      final Database database = await getDB();
+      repository ??= DietPlanProviderDB(database, _getStore(database));
+    }
+
+    return repository;
+  }
+
+  setUp(() async {
+    await setupParseInstance();
+    await getRepository();
+  });
+
+  tearDown(() async {
+    final Database database = await getDB();
+    final Store store = _getStore(database);
+    store.clear();
+    database.clear();
+  });
+
+  test('create DB instance', () async {
+    expect(true, repository != null);
+  });
+
+  test('add DietPlan from DB', () async {
+    // Given
+    final DietPlan expected = getDummyDietPlan();
+
+    // When
+    final ApiResponse response = await repository.add(expected);
+    final DietPlan actual = response.result;
+
+    // Then
+    expect(actual.objectId, expected.objectId);
+    expect(actual.protein, expected.protein);
+  });
+
+  test('addAll DietPlan from DB', () async {
+    // Given
+    const String objectIdPrefix = '12345abc';
+    final List<DietPlan> actual = List<DietPlan>();
+
+    final DietPlan item1 = getDummyDietPlan();
+    item1.objectId = '${objectIdPrefix}0';
+    actual.add(item1);
+
+    final DietPlan item2 = getDummyDietPlan();
+    item2.objectId = '${objectIdPrefix}1';
+    actual.add(item2);
+
+    // When
+    final ApiResponse response = await repository.addAll(actual);
+    final List<DietPlan> items = await response.result;
+
+    // Then
+    expect(response.success, true);
+    expect(actual[0].objectId, items[0].objectId);
+    expect(actual[1].objectId, items[1].objectId);
+  });
+
+  test('getById DietPlan from DB', () async {
+    // Given
+    final DietPlan actual = getDummyDietPlan();
+
+    // When
+    await repository.add(actual);
+    final ApiResponse response = await repository.getById('1234abcd');
+
+    // Then
+    final DietPlan expected = response.result;
+    expect(actual.objectId, expected.objectId);
+    expect(actual.protein, expected.protein);
+  });
+
+  test('getAll DietPlan from DB', () async {
+    // Given
+    const String objectIdPrefix = '12345abc';
+    final DietPlan item1 = getDummyDietPlan()..objectId = '${objectIdPrefix}0';
+    final DietPlan item2 = getDummyDietPlan()..objectId = '${objectIdPrefix}1';
+    final List<DietPlan> actual = List<DietPlan>()..add(item1)..add(item2);
+
+    // When
+    await repository.addAll(actual);
+
+    // Then
+    final ApiResponse response = await repository.getAll();
+    final List<DietPlan> expected = response.result;
+
+    expect(2, expected.length);
+    expect(actual[0].objectId, expected[0].objectId);
+    expect(actual[1].objectId, expected[1].objectId);
+  });
+
+  test('getNewerThan DietPlan from DB', () async {
+    // Given
+    final DietPlan expected = getDummyDietPlan();
+    // ignore: invalid_use_of_protected_member
+    expected.getObjectData()['keyUpdatedAt'] = DateTime.now();
+    await repository.add(expected);
+
+    // When
+    DateTime dateTime = DateTime.now();
+    dateTime = dateTime.subtract(Duration(hours: 1));
+    final ApiResponse response = await repository.getNewerThan(dateTime);
+    final List<DietPlan> actual = response.result;
+
+    // Then
+    expect(actual.isNotEmpty, true);
+    expect(actual.first.objectId, expected.objectId);
+  });
+
+  test('update DietPlan from DB', () async {
+    // Given
+    final DietPlan item = getDummyDietPlan();
+    item.protein = 1000;
+    await repository.add(item);
+
+    // When
+    item.protein = 1000;
+    final ApiResponse response = await repository.update(item);
+    final DietPlan userFood = response.result;
+
+    // Then
+    expect(item.objectId, userFood.objectId);
+    expect(userFood.protein, 1000);
+  });
+
+  test('updateAll DietPlan from DB', () async {
+    // Given
+    const String objectIdPrefix = '12345abc';
+
+    final List<DietPlan> actual = List<DietPlan>();
+    final DietPlan item1 = getDummyDietPlan();
+    item1.objectId = '${objectIdPrefix}0';
+    actual.add(item1);
+
+    final DietPlan item2 = getDummyDietPlan();
+    item2.objectId = '${objectIdPrefix}1';
+    actual.add(item2);
+
+    await repository.addAll(actual);
+
+    // When
+    actual[0].protein = 1000;
+    actual[1].protein = 1000;
+    final ApiResponse response = await repository.updateAll(actual);
+    final List<DietPlan> expected = response.result;
+
+    // Then
+    expect(actual[0].objectId, expected[0].objectId);
+    expect(actual[1].objectId, expected[1].objectId);
+    expect(expected[0].protein, 1000);
+    expect(expected[1].protein, 1000);
+  });
+
+  test('delete DietPlan from DB', () async {
+    // Given
+    final DietPlan actual = getDummyDietPlan();
+    await repository.add(actual);
+
+    // When
+    await repository.remove(actual);
+    final ApiResponse response = await repository.getById(actual.objectId);
+
+    // Then
+    expect(response.result == null, true);
+  });
+}
diff --git a/example/test/data/repository/diet_plan/repository_diet_plan_test.dart b/example/test/data/repository/diet_plan/repository_diet_plan_test.dart
new file mode 100644
index 000000000..e10e34fc6
--- /dev/null
+++ b/example/test/data/repository/diet_plan/repository_diet_plan_test.dart
@@ -0,0 +1,130 @@
+import 'package:flutter_plugin_example/data/base/api_response.dart';
+import 'package:flutter_plugin_example/data/model/diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/contract_provider_diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/repository_diet_plan.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+import '../repository_mock_utils.dart';
+
+void main() {
+  DietPlanRepository repository;
+
+  DietPlanProviderContract apiRepository;
+  DietPlanProviderContract dbRepository;
+
+  Future<DietPlanProviderContract> getApiRepository() async {
+    final DietPlanProviderContract repositoryApi = MockDietPlanProviderApi();
+
+    const String objectIdPrefix = '12345abc';
+    final DietPlan item1 = getDummyDietPlan()..objectId = '${objectIdPrefix}0';
+    final DietPlan item2 = getDummyDietPlan()..objectId = '${objectIdPrefix}1';
+    final List<DietPlan> mockList = List<DietPlan>()..add(item1)..add(item2);
+
+    when(repositoryApi.add(any)).thenAnswer((_) async =>
+        Future<ApiResponse>.value(
+            ApiResponse(true, 200, getDummyDietPlan(), null)));
+    when(repositoryApi.addAll(any)).thenAnswer((_) async =>
+        Future<ApiResponse>.value(ApiResponse(true, 200, mockList, null)));
+    when(repositoryApi.update(any)).thenAnswer((_) async =>
+        Future<ApiResponse>.value(
+            ApiResponse(true, 200, getDummyDietPlan(), null)));
+    when(repositoryApi.updateAll(any)).thenAnswer((_) async =>
+        Future<ApiResponse>.value(ApiResponse(true, 200, mockList, null)));
+    when(repositoryApi.getNewerThan(any)).thenAnswer((_) async =>
+        Future<ApiResponse>.value(ApiResponse(true, 200, mockList, null)));
+    when(repositoryApi.getById(any)).thenAnswer((_) async =>
+        Future<ApiResponse>.value(
+            ApiResponse(true, 200, getDummyDietPlan(), null)));
+    when(repositoryApi.getById(any)).thenAnswer((_) async =>
+        Future<ApiResponse>.value(ApiResponse(true, 200, mockList, null)));
+
+    return repositoryApi;
+  }
+
+  Future<DietPlanProviderContract> getDBRepository() {
+    return Future<DietPlanProviderContract>.value(MockDietPlanProviderDB());
+  }
+
+  Future<DietPlanRepository> getRepository() async {
+    apiRepository = await getApiRepository();
+    dbRepository = await getDBRepository();
+
+    final DietPlanRepository repository = DietPlanRepository.init(null,
+        repositoryDB: dbRepository, repositoryAPI: apiRepository);
+
+    return repository;
+  }
+
+  setUp(() async {
+    await setupParseInstance();
+    repository = await getRepository();
+  });
+
+  test('create DB instance', () async {
+    expect(true, repository != null);
+  });
+
+  test('add DietPlan from DB', () async {
+    // Given && When
+    await repository.add(any);
+
+    // Then
+    verify(dbRepository.add(any)).called(1);
+    verify(apiRepository.add(any)).called(1);
+  });
+
+  test('addAll DietPlan from DB', () async {
+    // Given && When
+    await repository.addAll(any);
+
+    // Then
+    verify(dbRepository.addAll(any)).called(1);
+    verify(apiRepository.addAll(any)).called(1);
+  });
+
+  test('getAll DietPlan from DB', () async {
+    // Given && When
+    await repository.getAll();
+
+    // Then
+    verify(dbRepository.getAll()).called(1);
+    verifyNever(apiRepository.getAll());
+  });
+
+  test('getAll DietPlan from API', () async {
+    // Given && When
+    await repository.getAll(fromApi: true);
+
+    // Then
+    verifyNever(dbRepository.getAll());
+    verify(apiRepository.getAll()).called(1);
+  });
+
+  test('getNewerThan DietPlan from DB', () async {
+    // Given && When
+    await repository.getNewerThan(DateTime.now());
+
+    // Then
+    verifyNever(dbRepository.getNewerThan(DateTime.now()));
+    verify(apiRepository.getNewerThan(any));
+  });
+
+  test('updateAll DietPlan from DB', () async {
+    // Given && When
+    await repository.updateAll(any);
+
+    // Then
+    verify(dbRepository.updateAll(any)).called(1);
+    verify(apiRepository.updateAll(any)).called(1);
+  });
+
+  test('delete DietPlan from DB', () async {
+    // Given && When
+    await repository.remove(any);
+
+    // Then
+    verify(dbRepository.remove(any)).called(1);
+    verify(apiRepository.remove(any)).called(1);
+  });
+}
diff --git a/example/test/data/repository/repository_mock_utils.dart b/example/test/data/repository/repository_mock_utils.dart
new file mode 100644
index 000000000..6a89f3ca7
--- /dev/null
+++ b/example/test/data/repository/repository_mock_utils.dart
@@ -0,0 +1,39 @@
+import 'dart:io';
+
+import 'package:flutter_plugin_example/data/model/diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/provider_api_diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/diet_plan/provider_db_diet_plan.dart';
+import 'package:flutter_plugin_example/domain/constants/application_constants.dart';
+import 'package:mockito/mockito.dart';
+import 'package:parse_server_sdk/parse_server_sdk.dart';
+import 'package:path/path.dart';
+import 'package:sembast/sembast.dart';
+import 'package:sembast/sembast_io.dart';
+
+class MockDietPlanProviderApi extends Mock implements DietPlanProviderApi {}
+
+class MockDietPlanProviderDB extends Mock implements DietPlanProviderDB {}
+
+Future<Database> getDB() async {
+  final String dbDirectory = Directory.current.path;
+  final String dbPath = join(dbDirectory, 'no_sql_test');
+  final DatabaseFactory dbFactory = databaseFactoryIo;
+  return await dbFactory.openDatabase(dbPath);
+}
+
+Future<void> setupParseInstance() async {
+  Parse().initialize(keyParseApplicationId, keyParseServerUrl,
+      masterKey: keyParseMasterKey, appName: keyApplicationName, debug: true);
+}
+
+DietPlan getDummyDietPlan() {
+  return DietPlan()
+    ..set('objectId', '1234abcd')
+    ..set(keyVarUpdatedAt, DateTime.now())
+    ..name = 'Test Diet Plan'
+    ..description = 'Some random description about a diet plan'
+    ..protein = 40
+    ..carbs = 40
+    ..fat = 20
+    ..status = 0;
+}
diff --git a/lib/src/base/parse_constants.dart b/lib/src/base/parse_constants.dart
index cc3021fc2..1dc98e59d 100644
--- a/lib/src/base/parse_constants.dart
+++ b/lib/src/base/parse_constants.dart
@@ -1,7 +1,7 @@
 part of flutter_parse_sdk;
 
 // Library
-const String keySdkVersion = '1.0.16';
+const String keySdkVersion = '1.0.17';
 const String keyLibraryName = 'Flutter Parse SDK';
 
 // End Points

From f36885f6014932f9da2a65fe308eec4ff387272f Mon Sep 17 00:00:00 2001
From: Phill <phill.wiggins@gmail.com>
Date: Mon, 25 Mar 2019 18:59:48 +0000
Subject: [PATCH 02/25] Corrected repo example

---
 example/lib/domain/utils/db_utils.dart | 11 +++++
 example/lib/ui/main.dart               | 59 +++++++++++++++++---------
 lib/src/objects/parse_response.dart    |  4 +-
 3 files changed, 53 insertions(+), 21 deletions(-)
 create mode 100644 example/lib/domain/utils/db_utils.dart

diff --git a/example/lib/domain/utils/db_utils.dart b/example/lib/domain/utils/db_utils.dart
new file mode 100644
index 000000000..051c7ee3b
--- /dev/null
+++ b/example/lib/domain/utils/db_utils.dart
@@ -0,0 +1,11 @@
+import 'package:path/path.dart';
+import 'package:path_provider/path_provider.dart';
+import 'package:sembast/sembast.dart';
+import 'package:sembast/sembast_io.dart';
+
+Future<Database> getDB() async {
+  final String dbDirectory = (await getApplicationDocumentsDirectory()).path;
+  final String dbPath = join(dbDirectory, 'no_sql');
+  final DatabaseFactory dbFactory = databaseFactoryIo;
+  return await dbFactory.openDatabase(dbPath);
+}
diff --git a/example/lib/ui/main.dart b/example/lib/ui/main.dart
index 1a578609c..ee1116ff8 100644
--- a/example/lib/ui/main.dart
+++ b/example/lib/ui/main.dart
@@ -5,6 +5,7 @@ import 'package:flutter_plugin_example/data/base/api_response.dart';
 import 'package:flutter_plugin_example/data/model/diet_plan.dart';
 import 'package:flutter_plugin_example/data/repositories/diet_plan/repository_diet_plan.dart';
 import 'package:flutter_plugin_example/domain/constants/application_constants.dart';
+import 'package:flutter_plugin_example/domain/utils/db_utils.dart';
 import 'package:flutter_stetho/flutter_stetho.dart';
 import 'package:parse_server_sdk/parse_server_sdk.dart';
 
@@ -19,10 +20,12 @@ class MyApp extends StatefulWidget {
 }
 
 class _MyAppState extends State<MyApp> {
+  DietPlanRepository repository;
+
   @override
   void initState() {
     super.initState();
-    initParse();
+    WidgetsBinding.instance.addPostFrameCallback((_) => initData());
   }
 
   @override
@@ -35,12 +38,14 @@ class _MyAppState extends State<MyApp> {
         body: Center(
           child: const Text('Running Parse init'),
         ),
-        floatingActionButton: FloatingActionButton(onPressed: runTestQueries),
       ),
     );
   }
 
-  Future<void> initParse() async {
+  Future<void> initData() async {
+    // Initialize repository
+    await initRepository();
+
     // Initialize parse
     Parse().initialize(keyParseApplicationId, keyParseServerUrl,
         masterKey: keyParseMasterKey, debug: true);
@@ -56,19 +61,19 @@ class _MyAppState extends State<MyApp> {
 
   Future<void> runTestQueries() async {
     // Basic repository example
-    await repositoryAddItems();
-    await repositoryGetAllItems();
+    //await repositoryAddItems();
+    //await repositoryGetAllItems();
 
     // Basic usage
-    /*createItem();
-    getAllItems();
-    getAllItemsByName();
+    //createItem();
+    //getAllItems();
+    //getAllItemsByName();
     getSingleItem();
-    getConfigs();
-    query();
-    initUser();
-    function();
-    functionWithParameters();*/
+    //getConfigs();
+    //query();
+    //initUser();
+    //function();
+    //functionWithParameters();
   }
 
   Future<void> createItem() async {
@@ -98,6 +103,8 @@ class _MyAppState extends State<MyApp> {
     final ParseResponse apiResponse = await DietPlan().getAll();
 
     if (apiResponse.success && apiResponse.result != null) {
+      String json = JsonEncoder().convert(apiResponse.result);
+      print(json);
       for (final DietPlan plan in apiResponse.result) {
         print(keyAppName + ': ' + plan.name);
       }
@@ -107,7 +114,7 @@ class _MyAppState extends State<MyApp> {
   }
 
   Future<void> getSingleItem() async {
-    final ParseResponse apiResponse = await DietPlan().getObject('R5EonpUDWy');
+    final ParseResponse apiResponse = await DietPlan().getObject('B0xtU0Ekqi');
 
     if (apiResponse.success && apiResponse.result != null) {
       final DietPlan dietPlan = apiResponse.result;
@@ -267,10 +274,15 @@ class _MyAppState extends State<MyApp> {
   }
 
   Future<void> repositoryAddItems() async {
-    final List<DietPlan> dietPlans =
-        const JsonDecoder().convert(dietPlansToAdd);
+    final List<DietPlan> dietPlans = List();
+
+    final List<dynamic> json = const JsonDecoder().convert(dietPlansToAdd);
+    for (final Map<String, dynamic> element in json) {
+      final DietPlan dietPlan = DietPlan().fromJson(element);
+      dietPlans.add(dietPlan);
+    }
 
-    final DietPlanRepository repository = DietPlanRepository();
+    await initRepository();
     final ApiResponse response = await repository.addAll(dietPlans);
     if (response.success) {
       print(response.result);
@@ -278,13 +290,22 @@ class _MyAppState extends State<MyApp> {
   }
 
   Future<void> repositoryGetAllItems() async {
-    final DietPlanRepository repository = DietPlanRepository();
     final ApiResponse response = await repository.getAll();
     if (response.success) {
       print(response.result);
     }
   }
+
+  Future<void> initRepository() async {
+    repository ??= DietPlanRepository.init(await getDB());
+  }
 }
 
 const String dietPlansToAdd =
-    '[{"className":"Diet_Plans","objectId":"RlOj8JGnEX","createdAt":"2017-10-17T10:44:11.355Z","updatedAt":"2018-01-30T10:15:21.228Z","Name":"Textbook","Description":"For an active lifestyle and a straight forward macro plan, we suggest this plan.","Fat":25,"Carbs":50,"Protein":25,"Status":0}]';
+    '[{"className":"Diet_Plans","Name":"Textbook","Description":"For an active lifestyle and a straight forward macro plan, we suggest this plan.","Fat":25,"Carbs":50,"Protein":25,"Status":0},'
+    '{"className":"Diet_Plans","Name":"Body Builder","Description":"Default Body Builders Diet","Fat":20,"Carbs":40,"Protein":40,"Status":0},'
+    '{"className":"Diet_Plans","Name":"Zone Diet","Description":"Popular with CrossFit users. Zone Diet targets similar macros.","Fat":30,"Carbs":40,"Protein":30,"Status":0},'
+    '{"className":"Diet_Plans","Name":"Low Fat","Description":"Low fat diet.","Fat":15,"Carbs":60,"Protein":25,"Status":0},'
+    '{"className":"Diet_Plans","Name":"Low Carb","Description":"Low Carb diet, main focus on quality fats and protein.","Fat":35,"Carbs":25,"Protein":40,"Status":0},'
+    '{"className":"Diet_Plans","Name":"Paleo","Description":"Paleo diet.","Fat":60,"Carbs":25,"Protein":10,"Status":0},'
+    '{"className":"Diet_Plans","Name":"Ketogenic","Description":"High quality fats, low carbs.","Fat":65,"Carbs":5,"Protein":30,"Status":0}]';
diff --git a/lib/src/objects/parse_response.dart b/lib/src/objects/parse_response.dart
index b736ee51e..e566afe5d 100644
--- a/lib/src/objects/parse_response.dart
+++ b/lib/src/objects/parse_response.dart
@@ -1,8 +1,8 @@
 part of flutter_parse_sdk;
 
-class ParseResponse {
+class ParseResponse<T extends ParseBase> {
   bool success = false;
   int statusCode = -1;
   dynamic result;
   ParseError error;
-}
\ No newline at end of file
+}

From 2be1e1b5f130ea21c260e1da181479573ae3e681 Mon Sep 17 00:00:00 2001
From: Phill <phill.wiggins@gmail.com>
Date: Tue, 26 Mar 2019 20:46:20 +0000
Subject: [PATCH 03/25] ParseResponse accepts list results and count

---
 example/lib/ui/main.dart                      | 40 +++++++++----------
 lib/src/objects/parse_response.dart           |  9 ++++-
 .../response/parse_response_builder.dart      | 16 +++++---
 3 files changed, 37 insertions(+), 28 deletions(-)

diff --git a/example/lib/ui/main.dart b/example/lib/ui/main.dart
index ee1116ff8..4155e8eae 100644
--- a/example/lib/ui/main.dart
+++ b/example/lib/ui/main.dart
@@ -61,19 +61,19 @@ class _MyAppState extends State<MyApp> {
 
   Future<void> runTestQueries() async {
     // Basic repository example
-    //await repositoryAddItems();
-    //await repositoryGetAllItems();
+    await repositoryAddItems();
+    await repositoryGetAllItems();
 
-    // Basic usage
-    //createItem();
-    //getAllItems();
-    //getAllItemsByName();
+    //Basic usage
+    createItem();
+    getAllItems();
+    getAllItemsByName();
     getSingleItem();
-    //getConfigs();
-    //query();
-    //initUser();
-    //function();
-    //functionWithParameters();
+    getConfigs();
+    query();
+    initUser();
+    function();
+    functionWithParameters();
   }
 
   Future<void> createItem() async {
@@ -83,7 +83,7 @@ class _MyAppState extends State<MyApp> {
 
     final ParseResponse apiResponse = await newObject.create();
 
-    if (apiResponse.success && apiResponse.result != null) {
+    if (apiResponse.success && apiResponse.count > 0) {
       print(keyAppName + ': ' + apiResponse.result.toString());
     }
   }
@@ -92,8 +92,8 @@ class _MyAppState extends State<MyApp> {
     final ParseResponse apiResponse =
         await ParseObject('TestObjectForApi').getAll();
 
-    if (apiResponse.success && apiResponse.result != null) {
-      for (final ParseObject testObject in apiResponse.result) {
+    if (apiResponse.success && apiResponse.count > 0) {
+      for (final ParseObject testObject in apiResponse.results) {
         print(keyAppName + ': ' + testObject.toString());
       }
     }
@@ -102,10 +102,8 @@ class _MyAppState extends State<MyApp> {
   Future<void> getAllItems() async {
     final ParseResponse apiResponse = await DietPlan().getAll();
 
-    if (apiResponse.success && apiResponse.result != null) {
-      String json = JsonEncoder().convert(apiResponse.result);
-      print(json);
-      for (final DietPlan plan in apiResponse.result) {
+    if (apiResponse.success && apiResponse.count > 0) {
+      for (final DietPlan plan in apiResponse.results) {
         print(keyAppName + ': ' + plan.name);
       }
     } else {
@@ -116,7 +114,7 @@ class _MyAppState extends State<MyApp> {
   Future<void> getSingleItem() async {
     final ParseResponse apiResponse = await DietPlan().getObject('B0xtU0Ekqi');
 
-    if (apiResponse.success && apiResponse.result != null) {
+    if (apiResponse.success && apiResponse.count > 0) {
       final DietPlan dietPlan = apiResponse.result;
 
       // Shows example of storing values in their proper type and retrieving them
@@ -148,7 +146,7 @@ class _MyAppState extends State<MyApp> {
 
     final ParseResponse apiResponse = await queryBuilder.query();
 
-    if (apiResponse.success && apiResponse.result != null) {
+    if (apiResponse.success && apiResponse.count > 0) {
       final List<ParseObject> listFromApi = apiResponse.result;
       final ParseObject parseObject = listFromApi?.first;
       print('Result: ${parseObject.toString()}');
@@ -231,7 +229,7 @@ class _MyAppState extends State<MyApp> {
           ..whereStartsWith(ParseUser.keyUsername, 'phillw');
 
     final ParseResponse apiResponse = await queryBuilder.query();
-    if (apiResponse.success) {
+    if (apiResponse.success && apiResponse.count > 0) {
       final List<ParseUser> users = response.result;
       for (final ParseUser user in users) {
         print(keyAppName + ': ' + user.toString());
diff --git a/lib/src/objects/parse_response.dart b/lib/src/objects/parse_response.dart
index e566afe5d..62f673a7a 100644
--- a/lib/src/objects/parse_response.dart
+++ b/lib/src/objects/parse_response.dart
@@ -1,8 +1,15 @@
 part of flutter_parse_sdk;
 
-class ParseResponse<T extends ParseBase> {
+class ParseResponse {
   bool success = false;
   int statusCode = -1;
+
+  /// If result is a singular result, i.e. getByObjectID
   dynamic result;
+
+  /// All results stored as a list - Even if only one response is returned
+  // ignore: always_specify_types
+  List results;
+  int count = 0;
   ParseError error;
 }
diff --git a/lib/src/objects/response/parse_response_builder.dart b/lib/src/objects/response/parse_response_builder.dart
index 714a86e51..5fcc6dd42 100644
--- a/lib/src/objects/response/parse_response_builder.dart
+++ b/lib/src/objects/response/parse_response_builder.dart
@@ -8,9 +8,7 @@ part of flutter_parse_sdk;
 /// 3. Success with simple OK.
 /// 4. Success with results. Again [ParseResponse()] is returned
 class _ParseResponseBuilder {
-  ParseResponse handleResponse<T>(
-      dynamic object,
-      Response apiResponse,
+  ParseResponse handleResponse<T>(dynamic object, Response apiResponse,
       {bool returnAsResult = false}) {
     final ParseResponse parseResponse = ParseResponse();
 
@@ -69,9 +67,14 @@ class _ParseResponseBuilder {
       response.result = map;
     } else if (map != null && map.length == 1 && map.containsKey('results')) {
       final List<dynamic> results = map['results'];
-      response.result = _handleMultipleResults<T>(object, results);
+      final List<T> items = _handleMultipleResults<T>(object, results);
+      response.results = items;
+      response.count = items.length;
     } else {
-      response.result = _handleSingleResult<T>(object, map, false);
+      final T item = _handleSingleResult<T>(object, map, false);
+      response.count = 1;
+      response.result = item;
+      response.results = <T>[item];
     }
 
     return response;
@@ -89,7 +92,8 @@ class _ParseResponseBuilder {
   }
 
   /// Handles a response with a single result object
-  T _handleSingleResult<T>(T object, Map<String, dynamic> map, bool createNewObject) {
+  T _handleSingleResult<T>(
+      T object, Map<String, dynamic> map, bool createNewObject) {
     if (createNewObject && object is ParseCloneable) {
       return object.clone(map);
     } else if (object is ParseObject) {

From 2d6bb3a4608c8e187dbbdbf4ea1804aacf822811 Mon Sep 17 00:00:00 2001
From: Phill Wiggins <phill.wiggins@gmail.com>
Date: Thu, 28 Mar 2019 14:22:40 +0000
Subject: [PATCH 04/25] Private/test (#140)

* ParseResponse accepts list results and count

* Corrected toPointer logic

* Corrected date issue

* Code clean

* Code clean
---
 example/lib/data/model/day.dart               | 39 ++++++++++++++++
 example/lib/data/model/user.dart              | 42 +++++++++++++++++
 example/lib/ui/main.dart                      | 23 ++++++++++
 lib/parse_server_sdk.dart                     |  7 ++-
 lib/src/network/parse_http_client.dart        | 25 +----------
 lib/src/network/parse_query.dart              |  1 +
 lib/src/objects/parse_base.dart               |  6 +--
 lib/src/objects/parse_object.dart             | 45 ++++++++++---------
 lib/src/objects/parse_user.dart               | 20 +++------
 .../response/parse_exception_response.dart    |  3 +-
 .../response/parse_response_utils.dart        | 20 +++++++--
 lib/src/utils/parse_encoder.dart              | 20 +++++++--
 lib/src/utils/parse_logger.dart               | 44 ++++++++++++++----
 lib/src/utils/parse_utils.dart                | 16 ++++++-
 14 files changed, 228 insertions(+), 83 deletions(-)
 create mode 100644 example/lib/data/model/day.dart
 create mode 100644 example/lib/data/model/user.dart

diff --git a/example/lib/data/model/day.dart b/example/lib/data/model/day.dart
new file mode 100644
index 000000000..79ed02098
--- /dev/null
+++ b/example/lib/data/model/day.dart
@@ -0,0 +1,39 @@
+import 'dart:core';
+
+import 'package:flutter_plugin_example/data/model/user.dart';
+import 'package:parse_server_sdk/parse_server_sdk.dart';
+
+class Day extends ParseObject implements ParseCloneable {
+  Day() : super(_keyTableName);
+
+  Day.clone() : this();
+
+  @override
+  Day clone(Map<String, dynamic> map) => Day.clone()..fromJson(map);
+
+  @override
+  Day fromJson(Map<String, dynamic> objectData) {
+    super.fromJson(objectData);
+    if (objectData.containsKey(keyOwner)) {
+      owner = User.clone().fromJson(objectData[keyOwner]);
+    }
+    return this;
+  }
+
+  static const String _keyTableName = 'FoodDiary_Day';
+  static const String keyDate = 'Date';
+  static const String keyOwner = 'Owner';
+  static const String keyStatus = 'Status';
+
+  DateTime get date => get<DateTime>(keyDate);
+
+  set date(DateTime date) => set<DateTime>(keyDate, date);
+
+  User get owner => get<User>(keyOwner);
+
+  set owner(User owner) => set<User>(keyOwner, owner);
+
+  int get status => get<int>(keyStatus);
+
+  set status(int status) => set<int>(keyStatus, status);
+}
diff --git a/example/lib/data/model/user.dart b/example/lib/data/model/user.dart
new file mode 100644
index 000000000..9084debe8
--- /dev/null
+++ b/example/lib/data/model/user.dart
@@ -0,0 +1,42 @@
+import 'dart:core';
+
+import 'package:parse_server_sdk/parse_server_sdk.dart';
+
+class User extends ParseUser implements ParseCloneable {
+  User(String username, String password, String emailAddress)
+      : super(username, password, emailAddress);
+
+  User.clone() : this(null, null, null);
+
+  @override
+  User clone(Map<String, dynamic> map) => User.clone()..fromJson(map);
+
+  static const String keyDob = 'DOB';
+  static const String keyGender = 'Gender';
+  static const String keyHeight = 'Height';
+  static const String keyFirebaseID = 'FirebaseID';
+  static const String keyName = 'Name';
+  static const String keyDisplayPicture = 'DisplayPicture';
+  static const String keyProUser = 'ProUser';
+
+  DateTime get dob => get<DateTime>(keyDob);
+  set dob(DateTime dob) => set<DateTime>(keyDob, dob);
+
+  num get gender => get<num>(keyGender);
+  set gender(num gender) => set<num>(keyGender, gender);
+
+  num get height => get<num>(keyHeight);
+  set height(num height) => set<num>(keyHeight, height);
+
+  String get firebaseId => get<String>(keyHeight);
+  set firebaseId(String firebaseId) => set<String>(keyHeight, firebaseId);
+
+  String get name => get<String>(keyName);
+  set name(String name) => set<String>(keyName, name);
+
+  String get displayPicture => get<String>(keyDisplayPicture);
+  set displayPicture(String displayPicture) => set<String>(keyDisplayPicture, displayPicture);
+
+  bool get proUser => get<bool>(keyProUser);
+  set proUser(bool proUser) => set<bool>(keyProUser, proUser);
+}
diff --git a/example/lib/ui/main.dart b/example/lib/ui/main.dart
index 4155e8eae..e4d6b9751 100644
--- a/example/lib/ui/main.dart
+++ b/example/lib/ui/main.dart
@@ -2,7 +2,9 @@ import 'dart:convert';
 
 import 'package:flutter/material.dart';
 import 'package:flutter_plugin_example/data/base/api_response.dart';
+import 'package:flutter_plugin_example/data/model/day.dart';
 import 'package:flutter_plugin_example/data/model/diet_plan.dart';
+import 'package:flutter_plugin_example/data/model/user.dart';
 import 'package:flutter_plugin_example/data/repositories/diet_plan/repository_diet_plan.dart';
 import 'package:flutter_plugin_example/domain/constants/application_constants.dart';
 import 'package:flutter_plugin_example/domain/utils/db_utils.dart';
@@ -74,6 +76,27 @@ class _MyAppState extends State<MyApp> {
     initUser();
     function();
     functionWithParameters();
+    test();
+  }
+
+  Future<void> test() async {
+    User user = User('test_user', 'test_password', 'test@gmail.com');
+    final ParseResponse signUpResponse = await user.signUp();
+
+    if (signUpResponse.success) {
+      user = signUpResponse.result;
+    } else {
+      final ParseResponse loginResponse = await user.login();
+
+      if (loginResponse.success) {
+        user = loginResponse.result;
+      }
+    }
+
+    final QueryBuilder<Day> query = QueryBuilder<Day>(Day())
+      ..whereEqualTo(Day.keyOwner, user);
+    var item = await query.query();
+    print(item.toString());
   }
 
   Future<void> createItem() async {
diff --git a/lib/parse_server_sdk.dart b/lib/parse_server_sdk.dart
index b0fcda35c..0ec5bcf6e 100644
--- a/lib/parse_server_sdk.dart
+++ b/lib/parse_server_sdk.dart
@@ -99,7 +99,12 @@ class Parse {
       String sessionId,
       bool autoSendSessionId,
       SecurityContext securityContext}) {
-    ParseCoreData.init(appId, serverUrl,
+
+    final String url = removeTrailingSlash(serverUrl);
+
+    ParseCoreData.init(
+        appId,
+        url,
         debug: debug,
         appName: appName,
         liveQueryUrl: liveQueryUrl,
diff --git a/lib/src/network/parse_http_client.dart b/lib/src/network/parse_http_client.dart
index 4604f58e6..07c07e5c4 100644
--- a/lib/src/network/parse_http_client.dart
+++ b/lib/src/network/parse_http_client.dart
@@ -36,32 +36,9 @@ class ParseHTTPClient extends BaseClient {
     }
 
     if (data.debug) {
-      _logging(request);
+      logCUrl(request);
     }
 
     return _client.send(request);
   }
-
-  void _logging(BaseRequest request) {
-    String curlCmd = 'curl';
-    curlCmd += ' -X ' + request.method;
-    bool compressed = false;
-    request.headers.forEach((String name, String value) {
-      if (name?.toLowerCase() == 'accept-encoding' &&
-          value?.toLowerCase() == 'gzip') {
-        compressed = true;
-      }
-      curlCmd += ' -H \'$name: $value\'';
-    });
-    if (request.method == 'POST' || request.method == 'PUT') {
-      if (request is Request) {
-        final String body = latin1.decode(request.bodyBytes);
-        curlCmd += ' -d \'$body\'';
-      }
-    }
-    curlCmd += (compressed ? ' --compressed ' : ' ') + request.url.toString();
-    print('╭-- cURL');
-    print(curlCmd);
-    print('╰-- (copy and paste the above line to a terminal)');
-  }
 }
diff --git a/lib/src/network/parse_query.dart b/lib/src/network/parse_query.dart
index 877c4ceeb..a6514f90a 100644
--- a/lib/src/network/parse_query.dart
+++ b/lib/src/network/parse_query.dart
@@ -284,6 +284,7 @@ class QueryBuilder<T extends ParseObject> {
   /// that the column and value are being queried against
   MapEntry<String, dynamic> _buildQueryWithColumnValueAndOperator(
       MapEntry columnAndValue, String queryOperator) {
+
     final String key = columnAndValue.key;
     final dynamic value = convertValueToCorrectType(parseEncode(columnAndValue.value));
 
diff --git a/lib/src/objects/parse_base.dart b/lib/src/objects/parse_base.dart
index 55d116aae..bd703f27f 100644
--- a/lib/src/objects/parse_base.dart
+++ b/lib/src/objects/parse_base.dart
@@ -206,9 +206,5 @@ abstract class ParseBase {
     return null;
   }
 
-  Map<String, String> toPointer() => <String, String>{
-        '__type': 'Pointer',
-        keyVarClassName: className,
-        keyVarObjectId: objectId
-      };
+  Map<String, dynamic> toPointer() => encodeObject(className, objectId);
 }
diff --git a/lib/src/objects/parse_object.dart b/lib/src/objects/parse_object.dart
index d2949c890..9a60f5b45 100644
--- a/lib/src/objects/parse_object.dart
+++ b/lib/src/objects/parse_object.dart
@@ -33,7 +33,7 @@ class ParseObject extends ParseBase implements ParseCloneable {
   /// Gets an object from the server using it's [String] objectId
   Future<ParseResponse> getObject(String objectId) async {
     try {
-      String uri =_path;
+      String uri = _path;
 
       if (objectId != null) {
         uri += '/$objectId';
@@ -42,7 +42,8 @@ class ParseObject extends ParseBase implements ParseCloneable {
       final Uri url = getSanitisedUri(_client, '$uri');
 
       final Response result = await _client.get(url);
-      return handleResponse<ParseObject>(this, result, ParseApiRQ.get, _debug, className);
+      return handleResponse<ParseObject>(
+          this, result, ParseApiRQ.get, _debug, className);
     } on Exception catch (e) {
       return handleException(e, ParseApiRQ.get, _debug, className);
     }
@@ -53,7 +54,8 @@ class ParseObject extends ParseBase implements ParseCloneable {
     try {
       final Uri url = getSanitisedUri(_client, '$_path');
       final Response result = await _client.get(url);
-      return handleResponse<ParseObject>(this, result, ParseApiRQ.getAll, _debug, className);
+      return handleResponse<ParseObject>(
+          this, result, ParseApiRQ.getAll, _debug, className);
     } on Exception catch (e) {
       return handleException(e, ParseApiRQ.getAll, _debug, className);
     }
@@ -73,7 +75,8 @@ class ParseObject extends ParseBase implements ParseCloneable {
         objectId = map['objectId'].toString();
       }
 
-      return handleResponse<ParseObject>(this, result, ParseApiRQ.create, _debug, className);
+      return handleResponse<ParseObject>(
+          this, result, ParseApiRQ.create, _debug, className);
     } on Exception catch (e) {
       return handleException(e, ParseApiRQ.create, _debug, className);
     }
@@ -88,7 +91,8 @@ class ParseObject extends ParseBase implements ParseCloneable {
         final Uri url = getSanitisedUri(_client, '$_path/$objectId');
         final String body = json.encode(toJson(forApiRQ: true));
         final Response result = await _client.put(url, body: body);
-        return handleResponse<ParseObject>(this, result, ParseApiRQ.save, _debug, className);
+        return handleResponse<ParseObject>(
+            this, result, ParseApiRQ.save, _debug, className);
       } on Exception catch (e) {
         return handleException(e, ParseApiRQ.save, _debug, className);
       }
@@ -179,7 +183,8 @@ class ParseObject extends ParseBase implements ParseCloneable {
         final String body =
             '{\"$key\":{\"__op\":\"$arrayAction\",\"objects\":${json.encode(parseEncode(values))}}}';
         final Response result = await _client.put(url, body: body);
-        return handleResponse<ParseObject>(this, result, apiRQType, _debug, className);
+        return handleResponse<ParseObject>(
+            this, result, apiRQType, _debug, className);
       } else {
         return null;
       }
@@ -190,7 +195,8 @@ class ParseObject extends ParseBase implements ParseCloneable {
 
   /// Used in array Operations in save() method
   void _arrayOperation(String arrayAction, String key, List<dynamic> values) {
-    set<Map<String, dynamic>>(key, <String, dynamic>{'__op': arrayAction, 'objects': values});
+    set<Map<String, dynamic>>(
+        key, <String, dynamic>{'__op': arrayAction, 'objects': values});
   }
 
   /// Increases a num of an object by x amount
@@ -206,7 +212,7 @@ class ParseObject extends ParseBase implements ParseCloneable {
   /// Increases a num of an object by x amount
   void setIncrement(String key, num amount) {
     set<Map<String, dynamic>>(
-        key,  <String, dynamic>{'__op': 'Increment', 'amount': amount});
+        key, <String, dynamic>{'__op': 'Increment', 'amount': amount});
   }
 
   /// Decreases a num of an object by x amount
@@ -222,7 +228,7 @@ class ParseObject extends ParseBase implements ParseCloneable {
   /// Decreases a num of an object by x amount
   void setDecrement(String key, num amount) {
     set<Map<String, dynamic>>(
-        key,  <String, dynamic>{'__op': 'Increment', 'amount': -amount});
+        key, <String, dynamic>{'__op': 'Increment', 'amount': -amount});
   }
 
   /// Can be used to add arrays to a given type
@@ -231,9 +237,11 @@ class ParseObject extends ParseBase implements ParseCloneable {
     try {
       if (objectId != null) {
         final Uri url = getSanitisedUri(_client, '$_path/$objectId');
-        final String body = '{\"$key\":{\"__op\":\"$countAction\",\"amount\":$amount}}';
+        final String body =
+            '{\"$key\":{\"__op\":\"$countAction\",\"amount\":$amount}}';
         final Response result = await _client.put(url, body: body);
-        return handleResponse<ParseObject>(this, result, apiRQType, _debug, className);
+        return handleResponse<ParseObject>(
+            this, result, apiRQType, _debug, className);
       } else {
         return null;
       }
@@ -245,16 +253,10 @@ class ParseObject extends ParseBase implements ParseCloneable {
   /// Can be used to create custom queries
   Future<ParseResponse> query(String query) async {
     try {
-      final Uri tempUri = Uri.parse(ParseCoreData().serverUrl);
-
-      final Uri url = Uri(
-          scheme: tempUri.scheme,
-          host: tempUri.host,
-          port: tempUri.port,
-          path: '${tempUri.path}$_path',
-          query: query);
+      final Uri url = getSanitisedUri(_client, '$_path', query: query);
       final Response result = await _client.get(url);
-      return handleResponse<ParseObject>(this, result, ParseApiRQ.query, _debug, className);
+      return handleResponse<ParseObject>(
+          this, result, ParseApiRQ.query, _debug, className);
     } on Exception catch (e) {
       return handleException(e, ParseApiRQ.query, _debug, className);
     }
@@ -267,7 +269,8 @@ class ParseObject extends ParseBase implements ParseCloneable {
       objectId ??= objectId;
       final Uri url = getSanitisedUri(_client, '$_path/$objectId');
       final Response result = await _client.delete(url);
-      return handleResponse<ParseObject>(this, result, ParseApiRQ.delete, _debug, className);
+      return handleResponse<ParseObject>(
+          this, result, ParseApiRQ.delete, _debug, className);
     } on Exception catch (e) {
       return handleException(e, ParseApiRQ.delete, _debug, className);
     }
diff --git a/lib/src/objects/parse_user.dart b/lib/src/objects/parse_user.dart
index a5bf6e68f..c05eb4f3f 100644
--- a/lib/src/objects/parse_user.dart
+++ b/lib/src/objects/parse_user.dart
@@ -145,25 +145,19 @@ class ParseUser extends ParseObject implements ParseCloneable {
   /// provided, call this method to login.
   Future<ParseResponse> login() async {
     try {
-      final Uri tempUri = Uri.parse(_client.data.serverUrl);
-
-      final Uri url = Uri(
-          scheme: tempUri.scheme,
-          host: tempUri.host,
-          port: tempUri.port,
-          path: '${tempUri.path}$keyEndPointLogin',
-          queryParameters: <String, String>{
-            keyVarUsername: username,
-            keyVarPassword: password
-          });
+      final Map<String, dynamic> queryParams = <String, String>{
+        keyVarUsername: username,
+        keyVarPassword: password
+      };
+
+      final Uri url = getSanitisedUri(_client, '$keyEndPointLogin', queryParams: queryParams);
 
       final Response response =
           await _client.get(url, headers: <String, String>{
         keyHeaderRevocableSession: '1',
       });
 
-      return _handleResponse(
-          this, response, ParseApiRQ.login, _debug, className);
+      return _handleResponse(this, response, ParseApiRQ.login, _debug, className);
     } on Exception catch (e) {
       return handleException(e, ParseApiRQ.login, _debug, className);
     }
diff --git a/lib/src/objects/response/parse_exception_response.dart b/lib/src/objects/response/parse_exception_response.dart
index 9a798c8b0..e8b3f4367 100644
--- a/lib/src/objects/response/parse_exception_response.dart
+++ b/lib/src/objects/response/parse_exception_response.dart
@@ -3,7 +3,6 @@ part of flutter_parse_sdk;
 /// Handles exception instead of throwing an exception
 ParseResponse buildParseResponseWithException(Exception exception) {
   final ParseResponse response = ParseResponse();
-  response.error =
-      ParseError(message: exception.toString(), isTypeOfException: true);
+  response.error = ParseError(message: exception.toString(), isTypeOfException: true);
   return response;
 }
diff --git a/lib/src/objects/response/parse_response_utils.dart b/lib/src/objects/response/parse_response_utils.dart
index 17fc181b2..0fff98d21 100644
--- a/lib/src/objects/response/parse_response_utils.dart
+++ b/lib/src/objects/response/parse_response_utils.dart
@@ -4,12 +4,14 @@ part of flutter_parse_sdk;
 @protected
 ParseResponse handleResponse<T>(ParseCloneable object, Response response,
     ParseApiRQ type, bool debug, String className) {
+
   final ParseResponse parseResponse = _ParseResponseBuilder().handleResponse<T>(
-      object, response,
+      object,
+      response,
       returnAsResult: shouldReturnAsABaseResult(type));
 
   if (debug) {
-    logger(ParseCoreData().appName, className, type.toString(), parseResponse);
+    logAPIResponse(className, type.toString(), parseResponse);
   }
 
   return parseResponse;
@@ -19,11 +21,12 @@ ParseResponse handleResponse<T>(ParseCloneable object, Response response,
 @protected
 ParseResponse handleException(
     Exception exception, ParseApiRQ type, bool debug, String className) {
+
   final ParseResponse parseResponse =
   buildParseResponseWithException(exception);
 
   if (debug) {
-    logger(ParseCoreData().appName, className, type.toString(), parseResponse);
+    logAPIResponse(className, type.toString(), parseResponse);
   }
 
   return parseResponse;
@@ -49,5 +52,14 @@ bool shouldReturnAsABaseResult(ParseApiRQ type) {
 
 bool isUnsuccessfulResponse(Response apiResponse) => apiResponse.statusCode != 200 && apiResponse.statusCode != 201;
 
-bool isSuccessButNoResults(Response apiResponse) => apiResponse.body == '{\"results\":[]}';
+bool isSuccessButNoResults(Response apiResponse) {
+  final Map<String, dynamic> decodedResponse = jsonDecode(apiResponse.body);
+  final List<dynamic> results = decodedResponse['results'];
+
+  if (results == null) {
+    return false;
+  }
+
+  return results.isEmpty;
+}
 
diff --git a/lib/src/utils/parse_encoder.dart b/lib/src/utils/parse_encoder.dart
index 9b769c569..b7d31fc95 100644
--- a/lib/src/utils/parse_encoder.dart
+++ b/lib/src/utils/parse_encoder.dart
@@ -23,7 +23,7 @@ dynamic parseEncode(dynamic value, {bool full}) {
   if (value is ParseGeoPoint) {
     return value;
   }
-  
+
   if (value is ParseFile) {
     return value;
   }
@@ -40,9 +40,23 @@ dynamic parseEncode(dynamic value, {bool full}) {
 }
 
 Map<String, dynamic> _encodeUint8List(Uint8List value) {
-  return <String, dynamic>{'__type': 'Bytes', 'base64': base64.encode(value)};
+  return <String, dynamic>{
+    '__type': 'Bytes',
+    'base64': base64.encode(value)
+  };
 }
 
 Map<String, dynamic> _encodeDate(DateTime date) {
-  return <String, dynamic>{'__type': 'Date', 'iso': _parseDateFormat.format(date)};
+  return <String, dynamic>{
+    '__type': 'Date',
+    'iso': _parseDateFormat.format(date)
+  };
+}
+
+Map<String, String> encodeObject(String className, String objectId) {
+  return <String, String>{
+    '\"__type\"': '\"Pointer\"',
+    '\"$keyVarClassName\"': '\"$className\"',
+    '\"$keyVarObjectId\"': '\"$objectId\"'
+  };
 }
diff --git a/lib/src/utils/parse_logger.dart b/lib/src/utils/parse_logger.dart
index 8d62d0ea6..047d3d37d 100644
--- a/lib/src/utils/parse_logger.dart
+++ b/lib/src/utils/parse_logger.dart
@@ -1,14 +1,16 @@
 part of flutter_parse_sdk;
 
-void logger(String appName, String className, String type,
+void logAPIResponse(
+    String className, 
+    String type,
     ParseResponse parseResponse) {
-  String responseString = ' \n';
-  String name = appName;
-  if (name.isNotEmpty) {
-    name = '$appName ';
-  }
 
-  responseString += '----\n${name}API Response ($className : $type) :';
+  const String spacer = ' \n';
+  String responseString = '';
+
+  responseString += '╭-- Parse Response';
+  responseString += '\nClass: $className';
+  responseString += '\nFunction: $type';
 
   if (parseResponse.success) {
     responseString += '\nStatus Code: ${parseResponse.statusCode}';
@@ -27,10 +29,36 @@ void logger(String appName, String className, String type,
     responseString += '\n$errorOrException: ${parseResponse.error.message}';
   }
 
-  responseString += '\n----\n';
+  responseString += '\n╰-- \n';
+  responseString += spacer;
   print(responseString);
 }
 
+void logCUrl(BaseRequest request) {
+  String curlCmd = 'curl';
+  curlCmd += ' -X ' + request.method;
+  bool compressed = false;
+  request.headers.forEach((String name, String value) {
+    if (name?.toLowerCase() == 'accept-encoding' &&
+        value?.toLowerCase() == 'gzip') {
+      compressed = true;
+    }
+    curlCmd += ' -H \'$name: $value\'';
+  });
+  if (request.method == 'POST' || request.method == 'PUT') {
+    if (request is Request) {
+      final String body = latin1.decode(request.bodyBytes);
+      curlCmd += ' -d \'$body\'';
+    }
+  }
+
+  curlCmd += (compressed ? ' --compressed ' : ' ') + request.url.toString();
+  curlCmd += '\n\n ${Uri.decodeFull(request.url.toString())}';
+  print('╭-- Parse Request');
+  print(curlCmd);
+  print('╰--');
+}
+
 void logRequest(
     String appName, String className, String type, String uri, String body) {
   String requestString = ' \n';
diff --git a/lib/src/utils/parse_utils.dart b/lib/src/utils/parse_utils.dart
index b1a7fd6f7..2cc15f71a 100644
--- a/lib/src/utils/parse_utils.dart
+++ b/lib/src/utils/parse_utils.dart
@@ -24,14 +24,26 @@ dynamic convertValueToCorrectType(dynamic value) {
 }
 
 /// Sanitises a url
-Uri getSanitisedUri(ParseHTTPClient client, String pathToAppend) {
+Uri getSanitisedUri(ParseHTTPClient client, String pathToAppend,
+    {Map<String, dynamic> queryParams, String query}) {
+
   final Uri tempUri = Uri.parse(client.data.serverUrl);
 
   final Uri url = Uri(
       scheme: tempUri.scheme,
       host: tempUri.host,
       port: tempUri.port,
-      path: '${tempUri.path}$pathToAppend');
+      path: '${tempUri.path}$pathToAppend',
+      queryParameters: queryParams,
+      query: query);
 
   return url;
 }
+/// Removes unncessary /
+String removeTrailingSlash(String serverUrl) {
+  if (serverUrl.substring(serverUrl.length -1) == '/') {
+    return serverUrl.substring(0, serverUrl.length -1);
+  } else {
+    return serverUrl;
+  }
+}

From 0513df2e6a3481d753b0faa8776c711e212c257a Mon Sep 17 00:00:00 2001
From: Phill Wiggins <phill.wiggins@gmail.com>
Date: Sat, 30 Mar 2019 07:37:08 +0000
Subject: [PATCH 05/25] Private/test (#141)

* ParseResponse accepts list results and count

* Corrected toPointer logic

* Corrected date issue

* Code clean

* Code clean

* Code clean
---
 lib/src/network/parse_query.dart | 3 +--
 lib/src/utils/parse_encoder.dart | 6 +++---
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/lib/src/network/parse_query.dart b/lib/src/network/parse_query.dart
index a6514f90a..d06536b07 100644
--- a/lib/src/network/parse_query.dart
+++ b/lib/src/network/parse_query.dart
@@ -289,8 +289,7 @@ class QueryBuilder<T extends ParseObject> {
     final dynamic value = convertValueToCorrectType(parseEncode(columnAndValue.value));
 
     if (queryOperator == _NO_OPERATOR_NEEDED) {
-      return MapEntry<String, dynamic>(
-        _NO_OPERATOR_NEEDED, "\"${key}\": $value");
+      return MapEntry<String, dynamic>(_NO_OPERATOR_NEEDED, '\"$key\": ${jsonEncode(value)}');
     } else {
       String queryString = '\"$key\":';
       final Map<String, dynamic> queryOperatorAndValueMap = Map<String, dynamic>();
diff --git a/lib/src/utils/parse_encoder.dart b/lib/src/utils/parse_encoder.dart
index b7d31fc95..c64d439a4 100644
--- a/lib/src/utils/parse_encoder.dart
+++ b/lib/src/utils/parse_encoder.dart
@@ -55,8 +55,8 @@ Map<String, dynamic> _encodeDate(DateTime date) {
 
 Map<String, String> encodeObject(String className, String objectId) {
   return <String, String>{
-    '\"__type\"': '\"Pointer\"',
-    '\"$keyVarClassName\"': '\"$className\"',
-    '\"$keyVarObjectId\"': '\"$objectId\"'
+    '__type': 'Pointer',
+    keyVarClassName: className,
+    keyVarObjectId: objectId
   };
 }

From 45f4f5396a56f10a5e79b4b1081f75e0e49458c8 Mon Sep 17 00:00:00 2001
From: Phill <phill.wiggins@gmail.com>
Date: Sat, 30 Mar 2019 10:30:06 +0000
Subject: [PATCH 06/25] Fixed delete

---
 lib/src/objects/parse_object.dart | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lib/src/objects/parse_object.dart b/lib/src/objects/parse_object.dart
index 9a60f5b45..80f3ac00f 100644
--- a/lib/src/objects/parse_object.dart
+++ b/lib/src/objects/parse_object.dart
@@ -263,11 +263,11 @@ class ParseObject extends ParseBase implements ParseCloneable {
   }
 
   /// Deletes the current object locally and online
-  Future<ParseResponse> delete({String objectId, String path}) async {
+  Future<ParseResponse> delete({String id, String path}) async {
     try {
       path ??= _path;
-      objectId ??= objectId;
-      final Uri url = getSanitisedUri(_client, '$_path/$objectId');
+      id ??= objectId;
+      final Uri url = getSanitisedUri(_client, '$_path/$id');
       final Response result = await _client.delete(url);
       return handleResponse<ParseObject>(
           this, result, ParseApiRQ.delete, _debug, className);

From 5fd3b13debb357875b91ce4df3b3a8f47f5003de Mon Sep 17 00:00:00 2001
From: wigginsp <phillipwiggins@rentalcars.com>
Date: Mon, 1 Apr 2019 08:37:05 +0100
Subject: [PATCH 07/25] Fixed ParseUser setting ParseObject extensions

---
 lib/src/objects/parse_user.dart | 11 +----------
 1 file changed, 1 insertion(+), 10 deletions(-)

diff --git a/lib/src/objects/parse_user.dart b/lib/src/objects/parse_user.dart
index a5bf6e68f..2f093e77d 100644
--- a/lib/src/objects/parse_user.dart
+++ b/lib/src/objects/parse_user.dart
@@ -281,16 +281,7 @@ class ParseUser extends ParseObject implements ParseCloneable {
     if (objectId == null) {
       return signUp();
     } else {
-      try {
-        final Uri url = getSanitisedUri(_client, '$_path/$objectId');
-        final String body =
-            json.encode(toJson(forApiRQ: true), toEncodable: dateTimeEncoder);
-        final Response response = await _client.put(url, body: body);
-        return _handleResponse(
-            this, response, ParseApiRQ.save, _debug, className);
-      } on Exception catch (e) {
-        return handleException(e, ParseApiRQ.save, _debug, className);
-      }
+      return super.save();
     }
   }
 

From c2d34a0a6fa311a64a308f4546f1bf12fc1e17bd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Bj=C3=B8rnbakk?= <daniel.bjornbakk@gmail.com>
Date: Mon, 1 Apr 2019 11:24:56 +0200
Subject: [PATCH 08/25] Fix lost lat, lng data (#143)

Update so SubClass of ParseObject uses internal Map for value storage. Changes in v1.0.17 breaks ParseGeoPoint. This is tested and seems to fix the issue.
---
 lib/src/objects/parse_geo_point.dart | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/lib/src/objects/parse_geo_point.dart b/lib/src/objects/parse_geo_point.dart
index 3ffff43e6..f6c16168b 100644
--- a/lib/src/objects/parse_geo_point.dart
+++ b/lib/src/objects/parse_geo_point.dart
@@ -1,5 +1,8 @@
 part of flutter_parse_sdk;
 
+const String keyLatitude = 'latitude';
+const String keyLongitude = 'longitude';
+
 class ParseGeoPoint extends ParseObject {
 
   /// Creates a Parse Object of type GeoPoint
@@ -11,8 +14,8 @@ class ParseGeoPoint extends ParseObject {
       bool autoSendSessionId})
       : super(keyGeoPoint) {
 
-    latitude = latitude;
-    longitude = longitude;
+    this.latitude = latitude;
+    this.longitude = longitude;
 
     _debug = isDebugEnabled(objectLevelDebug: debug);
     _client = client ??
@@ -22,8 +25,11 @@ class ParseGeoPoint extends ParseObject {
             securityContext: ParseCoreData().securityContext);
   }
 
-  double latitude;
-  double longitude;
+  double get latitude => super.get<double>(keyLatitude);
+  set latitude(double latitude) => set<double>(keyLatitude, latitude);
+
+  double get longitude => super.get<double>(keyLongitude);
+  set longitude(double longitude) => set<double>(keyLongitude, longitude);
 
   @override
   Map<String, dynamic> toJson({bool full = false, bool forApiRQ = false}) => <String, dynamic>{

From c1fda8376fe6fa0aaba51ca43abfc8f2c897b73e Mon Sep 17 00:00:00 2001
From: wigginsp <phillipwiggins@rentalcars.com>
Date: Tue, 2 Apr 2019 08:30:50 +0100
Subject: [PATCH 09/25] Deprecated result

---
 lib/src/objects/parse_response.dart                  | 5 +++++
 lib/src/objects/response/parse_response_builder.dart | 3 +++
 2 files changed, 8 insertions(+)

diff --git a/lib/src/objects/parse_response.dart b/lib/src/objects/parse_response.dart
index 62f673a7a..a1afeaee9 100644
--- a/lib/src/objects/parse_response.dart
+++ b/lib/src/objects/parse_response.dart
@@ -5,6 +5,11 @@ class ParseResponse {
   int statusCode = -1;
 
   /// If result is a singular result, i.e. getByObjectID
+  ///
+  /// This is now deprecated - Please use results. This will contain a list of
+  /// results, no need to check if its a list or a list of elements anymore.
+  ///
+  @deprecated
   dynamic result;
 
   /// All results stored as a list - Even if only one response is returned
diff --git a/lib/src/objects/response/parse_response_builder.dart b/lib/src/objects/response/parse_response_builder.dart
index 5fcc6dd42..2ee69e563 100644
--- a/lib/src/objects/response/parse_response_builder.dart
+++ b/lib/src/objects/response/parse_response_builder.dart
@@ -70,6 +70,9 @@ class _ParseResponseBuilder {
       final List<T> items = _handleMultipleResults<T>(object, results);
       response.results = items;
       response.count = items.length;
+      if (items.isNotEmpty) {
+        response.result = items.first;
+      }
     } else {
       final T item = _handleSingleResult<T>(object, map, false);
       response.count = 1;

From 07bdae4801af3c2ba95ca3c01f7e73621c14e5b5 Mon Sep 17 00:00:00 2001
From: wigginsp <phillipwiggins@rentalcars.com>
Date: Tue, 2 Apr 2019 08:53:23 +0100
Subject: [PATCH 10/25] Corrected logic on response

---
 lib/src/objects/response/parse_response_builder.dart | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/lib/src/objects/response/parse_response_builder.dart b/lib/src/objects/response/parse_response_builder.dart
index 2ee69e563..74075ca03 100644
--- a/lib/src/objects/response/parse_response_builder.dart
+++ b/lib/src/objects/response/parse_response_builder.dart
@@ -69,10 +69,8 @@ class _ParseResponseBuilder {
       final List<dynamic> results = map['results'];
       final List<T> items = _handleMultipleResults<T>(object, results);
       response.results = items;
+      response.result = items;
       response.count = items.length;
-      if (items.isNotEmpty) {
-        response.result = items.first;
-      }
     } else {
       final T item = _handleSingleResult<T>(object, map, false);
       response.count = 1;

From 4a2756fa2bea122320da0ff194b2d26b5723bafa Mon Sep 17 00:00:00 2001
From: Phill <phill.wiggins@gmail.com>
Date: Tue, 2 Apr 2019 18:38:42 +0100
Subject: [PATCH 11/25] ParseFile fix

---
 lib/src/base/parse_constants.dart | 2 ++
 lib/src/objects/parse_file.dart   | 8 ++++++--
 lib/src/objects/parse_user.dart   | 6 ++++--
 lib/src/utils/parse_utils.dart    | 4 ++--
 4 files changed, 14 insertions(+), 6 deletions(-)

diff --git a/lib/src/base/parse_constants.dart b/lib/src/base/parse_constants.dart
index 1dc98e59d..b746b5569 100644
--- a/lib/src/base/parse_constants.dart
+++ b/lib/src/base/parse_constants.dart
@@ -26,6 +26,8 @@ const String keyVarEmail = 'email';
 const String keyVarPassword = 'password';
 const String keyVarSessionToken = 'sessionToken';
 const String keyVarAcl = 'ACL';
+const String keyVarName = 'name';
+const String keyVarURL = 'url';
 
 // Classes
 const String keyClassMain = 'ParseMain';
diff --git a/lib/src/objects/parse_file.dart b/lib/src/objects/parse_file.dart
index b5c869917..594626658 100644
--- a/lib/src/objects/parse_file.dart
+++ b/lib/src/objects/parse_file.dart
@@ -28,8 +28,12 @@ class ParseFile extends ParseObject {
   }
 
   File file;
-  String name;
-  String url;
+
+  String get name => super.get<String>(keyVarName);
+  set name(String name) => set<String>(keyVarName, name);
+
+  String get url => super.get<String>(keyVarURL);
+  set url(String url) => set<String>(keyVarURL, url);
 
   @override
   // ignore: overridden_fields
diff --git a/lib/src/objects/parse_user.dart b/lib/src/objects/parse_user.dart
index 87ad70939..25d1a5c8b 100644
--- a/lib/src/objects/parse_user.dart
+++ b/lib/src/objects/parse_user.dart
@@ -150,14 +150,16 @@ class ParseUser extends ParseObject implements ParseCloneable {
         keyVarPassword: password
       };
 
-      final Uri url = getSanitisedUri(_client, '$keyEndPointLogin', queryParams: queryParams);
+      final Uri url = getSanitisedUri(_client, '$keyEndPointLogin',
+          queryParams: queryParams);
 
       final Response response =
           await _client.get(url, headers: <String, String>{
         keyHeaderRevocableSession: '1',
       });
 
-      return _handleResponse(this, response, ParseApiRQ.login, _debug, className);
+      return _handleResponse(
+          this, response, ParseApiRQ.login, _debug, className);
     } on Exception catch (e) {
       return handleException(e, ParseApiRQ.login, _debug, className);
     }
diff --git a/lib/src/utils/parse_utils.dart b/lib/src/utils/parse_utils.dart
index 2cc15f71a..c2d8a090f 100644
--- a/lib/src/utils/parse_utils.dart
+++ b/lib/src/utils/parse_utils.dart
@@ -12,9 +12,9 @@ bool isDebugEnabled({bool objectLevelDebug}) {
 ///
 /// Strings are wrapped with "" but integers and others are not
 dynamic convertValueToCorrectType(dynamic value) {
-  if (value is String && !value.contains('__type')) {
+  /*if (value is String && !value.contains('__type')) {
     return '\"$value\"';
-  } 
+  }*/
   
   if (value is DateTime || value is ParseObject) {
     return parseEncode(value);

From 2ee4880eed404d2d28f5550eb23034eb6038f176 Mon Sep 17 00:00:00 2001
From: Phill <phill.wiggins@gmail.com>
Date: Tue, 2 Apr 2019 19:23:32 +0100
Subject: [PATCH 12/25] Removed protected

---
 lib/src/objects/parse_base.dart | 2 --
 1 file changed, 2 deletions(-)

diff --git a/lib/src/objects/parse_base.dart b/lib/src/objects/parse_base.dart
index bd703f27f..2cdd579db 100644
--- a/lib/src/objects/parse_base.dart
+++ b/lib/src/objects/parse_base.dart
@@ -75,7 +75,6 @@ abstract class ParseBase {
   @override
   String toString() => json.encode(toJson());
 
-  @protected
   dynamic fromJson(Map<String, dynamic> objectData) {
     if (objectData == null) {
       return this;
@@ -120,7 +119,6 @@ abstract class ParseBase {
   Map<String, dynamic> getObjectData() => _objectData ?? Map<String, dynamic>();
 
   /// Saves in storage
-  @protected
   Future<void> saveInStorage(String key) async {
     final String objectJson = json.encode(toJson(full: true));
     await ParseCoreData().getStore()

From f166bd5866eff47f72b692fc52d164ffdc163a15 Mon Sep 17 00:00:00 2001
From: Phill <phill.wiggins@gmail.com>
Date: Thu, 4 Apr 2019 21:30:01 +0100
Subject: [PATCH 13/25] Testing ParseUser conversion

---
 example/lib/data/base/api_response.dart       |  13 ++-
 .../diet_plan/provider_api_diet_plan.dart     |   4 +-
 .../diet_plan/provider_db_diet_plan.dart      |  13 ++-
 .../diet_plan/repository_diet_plan.dart       |  38 +++----
 .../user/contract_provider_user.dart          |  17 +++
 .../repositories/user/provider_api_user.dart  |  61 +++++++++++
 .../repositories/user/provider_db_user.dart   | 103 ++++++++++++++++++
 .../repositories/user/repository_user.dart    |  86 +++++++++++++++
 .../repository_diet_plan_api_test.dart        |  45 ++++----
 .../repository_diet_plan_db_test.dart         |  65 ++++++++---
 .../diet_plan/repository_diet_plan_test.dart  |  10 +-
 .../repository/repository_mock_utils.dart     |   8 ++
 lib/src/objects/parse_response.dart           |   2 -
 lib/src/objects/parse_user.dart               |  10 +-
 14 files changed, 386 insertions(+), 89 deletions(-)
 create mode 100644 example/lib/data/repositories/user/contract_provider_user.dart
 create mode 100644 example/lib/data/repositories/user/provider_api_user.dart
 create mode 100644 example/lib/data/repositories/user/provider_db_user.dart
 create mode 100644 example/lib/data/repositories/user/repository_user.dart

diff --git a/example/lib/data/base/api_response.dart b/example/lib/data/base/api_response.dart
index 787eec3b3..5f6fdf336 100644
--- a/example/lib/data/base/api_response.dart
+++ b/example/lib/data/base/api_response.dart
@@ -3,20 +3,20 @@ import 'package:parse_server_sdk/parse_server_sdk.dart';
 import 'api_error.dart';
 
 class ApiResponse {
-  ApiResponse(this.success, this.statusCode, this.result, this.error);
+  ApiResponse(this.success, this.statusCode, this.results, this.error)
+      : count = results?.length ?? 0,
+        result = results?.first;
 
   final bool success;
   final int statusCode;
+  final List<dynamic> results;
   final dynamic result;
+  int count;
   final ApiError error;
-
-  dynamic getResult<T extends ParseObject>() {
-    return result;
-  }
 }
 
 ApiResponse getApiResponse<T extends ParseObject>(ParseResponse response) {
-  return ApiResponse(response.success, response.statusCode, response.result,
+  return ApiResponse(response.success, response.statusCode, response.results,
       getApiError(response.error));
 }
 
@@ -24,6 +24,7 @@ ApiError getApiError(ParseError response) {
   if (response == null) {
     return null;
   }
+
   return ApiError(response.code, response.message, response.isTypeOfException,
       response.type);
 }
diff --git a/example/lib/data/repositories/diet_plan/provider_api_diet_plan.dart b/example/lib/data/repositories/diet_plan/provider_api_diet_plan.dart
index 0050ceb20..41583c797 100644
--- a/example/lib/data/repositories/diet_plan/provider_api_diet_plan.dart
+++ b/example/lib/data/repositories/diet_plan/provider_api_diet_plan.dart
@@ -22,7 +22,7 @@ class DietPlanProviderApi implements DietPlanProviderContract {
         return response;
       }
 
-      responses.add(response.result);
+      response?.results?.forEach(responses.add);
     }
 
     return ApiResponse(true, 200, responses, null);
@@ -66,7 +66,7 @@ class DietPlanProviderApi implements DietPlanProviderContract {
         return response;
       }
 
-      responses.add(response.result);
+      response?.results?.forEach(responses.add);
     }
 
     return ApiResponse(true, 200, responses, null);
diff --git a/example/lib/data/repositories/diet_plan/provider_db_diet_plan.dart b/example/lib/data/repositories/diet_plan/provider_db_diet_plan.dart
index 17e7e78f4..4ad891866 100644
--- a/example/lib/data/repositories/diet_plan/provider_db_diet_plan.dart
+++ b/example/lib/data/repositories/diet_plan/provider_db_diet_plan.dart
@@ -4,6 +4,7 @@ import 'package:flutter_plugin_example/data/base/api_error.dart';
 import 'package:flutter_plugin_example/data/base/api_response.dart';
 import 'package:flutter_plugin_example/data/model/diet_plan.dart';
 import 'package:flutter_plugin_example/data/repositories/diet_plan/contract_provider_diet_plan.dart';
+import 'package:parse_server_sdk/parse_server_sdk.dart';
 import 'package:sembast/sembast.dart';
 
 class DietPlanProviderDB implements DietPlanProviderContract {
@@ -18,7 +19,7 @@ class DietPlanProviderDB implements DietPlanProviderContract {
     final Record recordToAdd = Record(_store, values, item.objectId);
     final Record recordFromDB = await _db.putRecord(recordToAdd);
     return ApiResponse(
-        true, 200, convertRecordToItem(record: recordFromDB), null);
+        true, 200, <dynamic>[convertRecordToItem(record: recordFromDB)], null);
   }
 
   @override
@@ -66,7 +67,7 @@ class DietPlanProviderDB implements DietPlanProviderContract {
     final Record record = await _store.getRecord(id);
     if (record != null) {
       final DietPlan userFood = convertRecordToItem(record: record);
-      return ApiResponse(true, 200, userFood, null);
+      return ApiResponse(true, 200, <dynamic>[userFood], null);
     } else {
       return errorResponse;
     }
@@ -78,7 +79,7 @@ class DietPlanProviderDB implements DietPlanProviderContract {
 
     final Finder finder = Finder(
         filter:
-            Filter.greaterThan('keyUpdatedAt', date.millisecondsSinceEpoch));
+            Filter.greaterThan(keyVarUpdatedAt, date.millisecondsSinceEpoch));
 
     final List<Record> records = await _store.findRecords(finder);
 
@@ -129,16 +130,16 @@ class DietPlanProviderDB implements DietPlanProviderContract {
     }
 
     return ApiResponse(
-        true, 200, convertRecordToItem(values: returnedItems), null);
+        true, 200, <dynamic>[convertRecordToItem(values: returnedItems)], null);
   }
 
   Map<String, dynamic> convertItemToStorageMap(DietPlan item) {
     final Map<String, dynamic> values = Map<String, dynamic>();
     // ignore: invalid_use_of_protected_member
     values['value'] = json.jsonEncode(item.toJson(full: true));
-    values['objectId'] = item.objectId;
+    values[keyVarObjectId] = item.objectId;
     if (item.updatedAt != null) {
-      values['updatedAt'] = item.updatedAt.millisecondsSinceEpoch;
+      values[keyVarUpdatedAt] = item.updatedAt.millisecondsSinceEpoch;
     }
 
     return values;
diff --git a/example/lib/data/repositories/diet_plan/repository_diet_plan.dart b/example/lib/data/repositories/diet_plan/repository_diet_plan.dart
index 9d1b4775c..beaebff14 100644
--- a/example/lib/data/repositories/diet_plan/repository_diet_plan.dart
+++ b/example/lib/data/repositories/diet_plan/repository_diet_plan.dart
@@ -8,19 +8,19 @@ import 'package:sembast/sembast.dart';
 
 class DietPlanRepository implements DietPlanProviderContract {
   static DietPlanRepository init(Database dbConnection,
-      {DietPlanProviderContract repositoryDB,
-      DietPlanProviderContract repositoryAPI}) {
+      {DietPlanProviderContract mockDBProvider,
+      DietPlanProviderContract mockAPIProvider}) {
     final DietPlanRepository repository = DietPlanRepository();
 
-    if (repositoryDB != null) {
-      repository.db = repositoryDB;
+    if (mockDBProvider != null) {
+      repository.db = mockDBProvider;
     } else {
-      final Store store = dbConnection.getStore('repository-$keyDietPlan');
+      final Store store = dbConnection.getStore('repository_store');
       repository.db = DietPlanProviderDB(dbConnection, store);
     }
 
-    if (repositoryAPI != null) {
-      repository.api = repositoryAPI;
+    if (mockAPIProvider != null) {
+      repository.api = mockAPIProvider;
     } else {
       repository.api = DietPlanProviderApi();
     }
@@ -61,27 +61,18 @@ class DietPlanRepository implements DietPlanProviderContract {
 
     final ApiResponse response = await api.addAll(items);
 
-    if (response.success && isValidList(response.result)) {
-      await db.addAll(items);
+    if (response.success && isValidList(response.results)) {
+      await db.addAll(response.results);
     }
 
     return response;
   }
 
   @override
-  Future<ApiResponse> getAll(
-      {bool fromApi = false, bool fromDb = false}) async {
+  Future<ApiResponse> getAll({bool fromApi = false}) {
     if (fromApi) {
       return api.getAll();
     }
-    if (fromDb) {
-      return db.getAll();
-    }
-
-    ApiResponse response = await db.getAll();
-    if (response.result == null) {
-      response = await api.getAll();
-    }
 
     return db.getAll();
   }
@@ -116,9 +107,8 @@ class DietPlanRepository implements DietPlanProviderContract {
 
     final ApiResponse response = await api.getNewerThan(date);
 
-    if (response.success && response.result != null) {
-      final List<DietPlan> list = response.result;
-      await db.updateAll(list);
+    if (response.success && isValidList(response.results)) {
+      await db.updateAll(response.results);
     }
 
     return response;
@@ -165,8 +155,8 @@ class DietPlanRepository implements DietPlanProviderContract {
     }
 
     ApiResponse response = await api.updateAll(items);
-    if (response.success && isValidList(response.result)) {
-      response = await db.updateAll(items);
+    if (response.success && isValidList(response.results)) {
+      response = await db.updateAll(response.results);
     }
 
     return response;
diff --git a/example/lib/data/repositories/user/contract_provider_user.dart b/example/lib/data/repositories/user/contract_provider_user.dart
new file mode 100644
index 000000000..0c12f2edf
--- /dev/null
+++ b/example/lib/data/repositories/user/contract_provider_user.dart
@@ -0,0 +1,17 @@
+import 'package:flutter_plugin_example/data/base/api_response.dart';
+import 'package:flutter_plugin_example/data/model/user.dart';
+
+abstract class UserProviderContract {
+  Future<User> createUser(
+      String username, String password, String emailAddress);
+  Future<User> currentUser();
+  Future<ApiResponse> signUp(User user);
+  Future<ApiResponse> login(User user);
+  void logout(User user);
+  Future<ApiResponse> getCurrentUserFromServer();
+  Future<ApiResponse> requestPasswordReset(User user);
+  Future<ApiResponse> verificationEmailRequest(User user);
+  Future<ApiResponse> save(User user);
+  Future<ApiResponse> destroy(User user);
+  Future<ApiResponse> allUsers();
+}
diff --git a/example/lib/data/repositories/user/provider_api_user.dart b/example/lib/data/repositories/user/provider_api_user.dart
new file mode 100644
index 000000000..1a44e8899
--- /dev/null
+++ b/example/lib/data/repositories/user/provider_api_user.dart
@@ -0,0 +1,61 @@
+import 'package:flutter_plugin_example/data/base/api_response.dart';
+import 'package:flutter_plugin_example/data/model/user.dart';
+import 'package:parse_server_sdk/parse_server_sdk.dart';
+
+import 'contract_provider_user.dart';
+
+class UserProviderApi implements UserProviderContract {
+  @override
+  Future<User> createUser(
+      String username, String password, String emailAddress) {
+    return Future<User>.value(User(username, password, emailAddress));
+  }
+
+  @override
+  Future<User> currentUser() {
+    return ParseUser.currentUser();
+  }
+
+  @override
+  Future<ApiResponse> getCurrentUserFromServer() async {
+    return getApiResponse<User>(await ParseUser.getCurrentUserFromServer());
+  }
+
+  @override
+  Future<ApiResponse> destroy(User user) async {
+    return getApiResponse<User>(await user.destroy());
+  }
+
+  @override
+  Future<ApiResponse> login(User user) async {
+    return getApiResponse<User>(await user.login());
+  }
+
+  @override
+  Future<ApiResponse> requestPasswordReset(User user) async {
+    return getApiResponse<User>(await user.requestPasswordReset());
+  }
+
+  @override
+  Future<ApiResponse> save(User user) async {
+    return getApiResponse<User>(await user.save());
+  }
+
+  @override
+  Future<ApiResponse> signUp(User user) async {
+    return getApiResponse<User>(await user.signUp());
+  }
+
+  @override
+  Future<ApiResponse> verificationEmailRequest(User user) async {
+    return getApiResponse<User>(await user.verificationEmailRequest());
+  }
+
+  @override
+  Future<ApiResponse> allUsers() async {
+    return getApiResponse(await ParseUser.all());
+  }
+
+  @override
+  void logout(User user) => user.logout();
+}
diff --git a/example/lib/data/repositories/user/provider_db_user.dart b/example/lib/data/repositories/user/provider_db_user.dart
new file mode 100644
index 000000000..d135418c8
--- /dev/null
+++ b/example/lib/data/repositories/user/provider_db_user.dart
@@ -0,0 +1,103 @@
+import 'dart:convert' as json;
+
+import 'package:flutter_plugin_example/data/base/api_error.dart';
+import 'package:flutter_plugin_example/data/base/api_response.dart';
+import 'package:flutter_plugin_example/data/model/user.dart';
+import 'package:parse_server_sdk/parse_server_sdk.dart';
+import 'package:sembast/sembast.dart';
+
+import 'contract_provider_user.dart';
+
+class UserProviderDB implements UserProviderContract {
+  UserProviderDB(this._db, this._store);
+
+  final Store _store;
+  final Database _db;
+
+  @override
+  Future<User> createUser(
+      String username, String password, String emailAddress) async {
+    final User user = User(username, password, emailAddress);
+    final Map<String, dynamic> values = convertItemToStorageMap(user);
+    final Record recordToAdd = Record(_store, values, user.objectId);
+    final Record recordFromDB = await _db.putRecord(recordToAdd);
+    return convertRecordToItem(record: recordFromDB);
+  }
+
+  @override
+  Future<User> currentUser() {
+    return null;
+  }
+
+  @override
+  Future<ApiResponse> getCurrentUserFromServer() async {
+    return null;
+  }
+
+  @override
+  Future<ApiResponse> destroy(User user) async {
+    await _store.delete(user.objectId);
+    return ApiResponse(true, 200, null, null);
+  }
+
+  @override
+  Future<ApiResponse> login(User user) async {
+    return null;
+  }
+
+  @override
+  Future<ApiResponse> requestPasswordReset(User user) async {
+    return null;
+  }
+
+  @override
+  Future<ApiResponse> save(User user) async {
+    final Map<String, dynamic> values = convertItemToStorageMap(user);
+    final Record recordToAdd = Record(_store, values, user.objectId);
+    final Record recordFromDB = await _db.putRecord(recordToAdd);
+    return ApiResponse(
+        true, 200, <dynamic>[convertRecordToItem(record: recordFromDB)], null);
+  }
+
+  @override
+  Future<ApiResponse> signUp(User user) {
+    return null;
+  }
+
+  @override
+  Future<ApiResponse> verificationEmailRequest(User user) async {
+    return null;
+  }
+
+  @override
+  Future<ApiResponse> allUsers() async {
+    return null;
+  }
+
+  @override
+  void logout(User user) {}
+
+  Map<String, dynamic> convertItemToStorageMap(User item) {
+    final Map<String, dynamic> values = Map<String, dynamic>();
+    values['value'] = json.jsonEncode(item.toJson(full: true));
+    values[keyVarObjectId] = item.objectId;
+    item.updatedAt != null
+        ? values[keyVarUpdatedAt] = item.updatedAt.millisecondsSinceEpoch
+        : values[keyVarCreatedAt] = DateTime.now().millisecondsSinceEpoch;
+    return values;
+  }
+
+  User convertRecordToItem({Record record, Map<String, dynamic> values}) {
+    try {
+      values ??= record.value;
+      final User item =
+          User.clone().fromJson(json.jsonDecode(values['value']));
+      return item;
+    } catch (e) {
+      return null;
+    }
+  }
+
+  static ApiError error = ApiError(1, 'No records found', false, '');
+  ApiResponse errorResponse = ApiResponse(false, 1, null, error);
+}
diff --git a/example/lib/data/repositories/user/repository_user.dart b/example/lib/data/repositories/user/repository_user.dart
new file mode 100644
index 000000000..b9a5c26a0
--- /dev/null
+++ b/example/lib/data/repositories/user/repository_user.dart
@@ -0,0 +1,86 @@
+import 'package:flutter_plugin_example/data/base/api_response.dart';
+import 'package:flutter_plugin_example/data/model/user.dart';
+import 'package:sembast/sembast.dart';
+
+import 'contract_provider_user.dart';
+import 'provider_api_user.dart';
+import 'provider_db_user.dart';
+
+class UserRepository implements UserProviderContract {
+  static UserRepository init(Database dbConnection,
+      {UserProviderContract mockDBProvider,
+      UserProviderContract mockAPIProvider}) {
+    final UserRepository repository = UserRepository();
+
+    if (mockDBProvider != null) {
+      repository.db = mockDBProvider;
+    } else {
+      final Store store = dbConnection.getStore('reposutory_user');
+      repository.db = UserProviderDB(dbConnection, store);
+    }
+
+    if (mockAPIProvider != null) {
+      repository.api = mockAPIProvider;
+    } else {
+      repository.api = UserProviderApi();
+    }
+
+    return repository;
+  }
+
+  UserProviderContract api;
+  UserProviderContract db;
+
+  @override
+  Future<User> createUser(
+      String username, String password, String emailAddress) async {
+    api.createUser(username, password, emailAddress);
+
+    final User user = await api.createUser(username, password, emailAddress);
+    if (user != null) {
+      await db.createUser(username, password, emailAddress);
+    }
+
+    return user;
+  }
+
+  @override
+  Future<User> currentUser() => db.currentUser();
+
+  @override
+  Future<ApiResponse> destroy(User user) async {
+    ApiResponse response = await api.destroy(user);
+    response = await db.destroy(user);
+    return response;
+  }
+
+  @override
+  Future<ApiResponse> allUsers() => api.allUsers();
+
+  @override
+  Future<ApiResponse> getCurrentUserFromServer() =>
+      api.getCurrentUserFromServer();
+
+  @override
+  Future<ApiResponse> login(User user) => api.login(user);
+
+  @override
+  void logout(User user) => api.logout(user);
+
+  @override
+  Future<ApiResponse> requestPasswordReset(User user) =>
+      api.requestPasswordReset(user);
+
+  @override
+  Future<ApiResponse> save(User user) async {
+    ApiResponse response = await api.save(user);
+    response = await db.save(user);
+    return response;
+  }
+
+  @override
+  Future<ApiResponse> signUp(User user) => api.signUp(user);
+
+  @override
+  Future<ApiResponse> verificationEmailRequest(User user) => api.signUp(user);
+}
diff --git a/example/test/data/repository/diet_plan/repository_diet_plan_api_test.dart b/example/test/data/repository/diet_plan/repository_diet_plan_api_test.dart
index c56b9312d..9536a8ea8 100644
--- a/example/test/data/repository/diet_plan/repository_diet_plan_api_test.dart
+++ b/example/test/data/repository/diet_plan/repository_diet_plan_api_test.dart
@@ -1,14 +1,16 @@
-// ignore_for_file: invalid_use_of_protected_member
 import 'package:flutter_plugin_example/data/base/api_response.dart';
 import 'package:flutter_plugin_example/data/model/diet_plan.dart';
 import 'package:flutter_plugin_example/data/repositories/diet_plan/contract_provider_diet_plan.dart';
 import 'package:flutter_plugin_example/data/repositories/diet_plan/provider_api_diet_plan.dart';
 import 'package:flutter_test/flutter_test.dart';
+import 'package:shared_preferences/shared_preferences.dart';
 
 import '../repository_mock_utils.dart';
 
+// ignore_for_file: invalid_use_of_protected_member
 void main() {
   DietPlanProviderContract repository;
+  SharedPreferences.setMockInitialValues(Map<String, String>());
 
   Future<DietPlanProviderContract> getRepository() async {
     repository ??= DietPlanProviderApi();
@@ -35,11 +37,11 @@ void main() {
       expected.getObjectData()['objectId'] = null;
 
       // When
-      ApiResponse response = await repository.add(expected);
+      final ApiResponse response = await repository.add(expected);
       final DietPlan actual = response.result;
 
       // CLEAR FROM DB
-      response = await repository.remove(actual);
+      await deleteFromApi(response.results);
 
       // Then
       expect(actual.protein, expected.protein);
@@ -59,12 +61,10 @@ void main() {
 
       // When
       final ApiResponse response = await repository.addAll(actual);
-      final List<DietPlan> items = await response.result;
+      final List<DietPlan> items = response.results;
 
       // CLEAR FROM DB
-      for (final DietPlan item in items) {
-        await repository.remove(item);
-      }
+      await deleteFromApi(response.results);
 
       // Then
       expect(response.success, true);
@@ -77,13 +77,15 @@ void main() {
       dummy.getObjectData()['objectId'] = null;
 
       // When
-      ApiResponse response = await repository.add(dummy);
+      final ApiResponse response = await repository.add(dummy);
       final DietPlan expected = response.result;
-      response = await repository.getById(expected.objectId);
-      final DietPlan actual = response.result;
+      final ApiResponse updateResponse =
+          await repository.getById(expected.objectId);
+      final DietPlan actual = updateResponse.result;
 
       // CLEAR FROM DB
-      response = await repository.remove(actual);
+      await deleteFromApi(response.results);
+      await deleteFromApi(updateResponse.results);
 
       // Then
       expect(actual.objectId, expected.objectId);
@@ -97,14 +99,15 @@ void main() {
 
       // When
       final ApiResponse baseResponse = await repository.add(dummy);
-      final DietPlan userFood = baseResponse.result;
       final ApiResponse responseWithResult = await repository
           .getNewerThan(DateTime.now().subtract(Duration(days: 1)));
       final ApiResponse responseWithoutResult =
           await repository.getNewerThan(DateTime.now().add(Duration(days: 1)));
 
       // CLEAR FROM DB
-      await repository.remove(userFood);
+      await deleteFromApi(baseResponse.results);
+      await deleteFromApi(responseWithoutResult.results);
+      await deleteFromApi(responseWithResult.results);
 
       // Then
       expect(responseWithResult.success, true);
@@ -127,12 +130,9 @@ void main() {
 
       // When
       final ApiResponse response = await repository.addAll(actual);
-      final List<DietPlan> items = await response.result;
 
       // CLEAR FROM DB
-      for (final DietPlan item in items) {
-        await repository.remove(item);
-      }
+      await deleteFromApi(response.results);
 
       // Then
       expect(response.success, true);
@@ -143,7 +143,7 @@ void main() {
       // Given
       final DietPlan expected = getDummyDietPlan();
       expected.getObjectData()['objectId'] = null;
-      ApiResponse response = await repository.add(expected);
+      final ApiResponse response = await repository.add(expected);
       final DietPlan initialResponse = response.result;
 
       // When
@@ -153,7 +153,8 @@ void main() {
       final DietPlan actual = updateResponse.result;
 
       // CLEAR FROM DB
-      response = await repository.remove(actual);
+      await deleteFromApi(response.results);
+      await deleteFromApi(updateResponse.results);
 
       // Then
       expect(actual.protein, 10);
@@ -177,12 +178,10 @@ void main() {
       item1.protein = 9;
       item2.protein = 10;
       final ApiResponse updateResponse = await repository.updateAll(actual);
-      final List<DietPlan> updated = updateResponse.result;
+      final List<DietPlan> updated = updateResponse.results;
 
       // CLEAR FROM DB
-      for (final DietPlan day in updated) {
-        await repository.remove(day);
-      }
+      await deleteFromApi(updateResponse.results);
 
       // Then
       expect(updated[0].protein, 9);
diff --git a/example/test/data/repository/diet_plan/repository_diet_plan_db_test.dart b/example/test/data/repository/diet_plan/repository_diet_plan_db_test.dart
index d1d2e8394..f60dff301 100644
--- a/example/test/data/repository/diet_plan/repository_diet_plan_db_test.dart
+++ b/example/test/data/repository/diet_plan/repository_diet_plan_db_test.dart
@@ -3,15 +3,18 @@ import 'package:flutter_plugin_example/data/model/diet_plan.dart';
 import 'package:flutter_plugin_example/data/repositories/diet_plan/contract_provider_diet_plan.dart';
 import 'package:flutter_plugin_example/data/repositories/diet_plan/provider_db_diet_plan.dart';
 import 'package:flutter_test/flutter_test.dart';
+import 'package:parse_server_sdk/parse_server_sdk.dart';
 import 'package:sembast/sembast.dart';
+import 'package:shared_preferences/shared_preferences.dart';
 
 import '../repository_mock_utils.dart';
 
 void main() {
   DietPlanProviderContract repository;
+  SharedPreferences.setMockInitialValues(Map<String, String>());
 
   Store _getStore(Database database) {
-    return database.getStore('repository_$keyDietPlan');
+    return database.getStore('diet_plan_repository_test');
   }
 
   Future<DietPlanProviderContract> getRepository() async {
@@ -47,6 +50,9 @@ void main() {
     final ApiResponse response = await repository.add(expected);
     final DietPlan actual = response.result;
 
+    // CLEAR FROM DB
+    await deleteFromApi(response.results);
+
     // Then
     expect(actual.objectId, expected.objectId);
     expect(actual.protein, expected.protein);
@@ -67,7 +73,7 @@ void main() {
 
     // When
     final ApiResponse response = await repository.addAll(actual);
-    final List<DietPlan> items = await response.result;
+    final List<DietPlan> items = response.results;
 
     // Then
     expect(response.success, true);
@@ -80,8 +86,12 @@ void main() {
     final DietPlan actual = getDummyDietPlan();
 
     // When
-    await repository.add(actual);
-    final ApiResponse response = await repository.getById('1234abcd');
+    final ApiResponse response = await repository.add(actual);
+    final ApiResponse updateResponse = await repository.getById('1234abcd');
+
+    // CLEAR FROM DB
+    await deleteFromApi(response.results);
+    await deleteFromApi(updateResponse.results);
 
     // Then
     final DietPlan expected = response.result;
@@ -97,11 +107,15 @@ void main() {
     final List<DietPlan> actual = List<DietPlan>()..add(item1)..add(item2);
 
     // When
-    await repository.addAll(actual);
+    final ApiResponse response = await repository.addAll(actual);
 
     // Then
-    final ApiResponse response = await repository.getAll();
-    final List<DietPlan> expected = response.result;
+    final ApiResponse updateResponse = await repository.getAll();
+    final List<DietPlan> expected = updateResponse.results;
+
+    // CLEAR FROM DB
+    await deleteFromApi(response.results);
+    await deleteFromApi(updateResponse.results);
 
     expect(2, expected.length);
     expect(actual[0].objectId, expected[0].objectId);
@@ -112,14 +126,18 @@ void main() {
     // Given
     final DietPlan expected = getDummyDietPlan();
     // ignore: invalid_use_of_protected_member
-    expected.getObjectData()['keyUpdatedAt'] = DateTime.now();
-    await repository.add(expected);
+    expected.getObjectData()[keyVarUpdatedAt] = DateTime.now();
+    final ApiResponse response = await repository.add(expected);
 
     // When
     DateTime dateTime = DateTime.now();
     dateTime = dateTime.subtract(Duration(hours: 1));
-    final ApiResponse response = await repository.getNewerThan(dateTime);
-    final List<DietPlan> actual = response.result;
+    final ApiResponse updateResponse = await repository.getNewerThan(dateTime);
+    final List<DietPlan> actual = updateResponse.results;
+
+    // CLEAR FROM DB
+    await deleteFromApi(response.results);
+    await deleteFromApi(updateResponse.results);
 
     // Then
     expect(actual.isNotEmpty, true);
@@ -130,12 +148,16 @@ void main() {
     // Given
     final DietPlan item = getDummyDietPlan();
     item.protein = 1000;
-    await repository.add(item);
+    final ApiResponse apiResponse = await repository.add(item);
 
     // When
     item.protein = 1000;
-    final ApiResponse response = await repository.update(item);
-    final DietPlan userFood = response.result;
+    final ApiResponse updateResponse = await repository.update(item);
+    final DietPlan userFood = updateResponse.result;
+
+    // CLEAR FROM DB
+    await deleteFromApi(apiResponse.results);
+    await deleteFromApi(updateResponse.results);
 
     // Then
     expect(item.objectId, userFood.objectId);
@@ -155,13 +177,22 @@ void main() {
     item2.objectId = '${objectIdPrefix}1';
     actual.add(item2);
 
-    await repository.addAll(actual);
+    final ApiResponse apiResponse = await repository.addAll(actual);
+
+    // CLEAR FROM DB
+    await deleteFromApi(apiResponse.results);
 
     // When
     actual[0].protein = 1000;
     actual[1].protein = 1000;
-    final ApiResponse response = await repository.updateAll(actual);
-    final List<DietPlan> expected = response.result;
+    final ApiResponse updateResponse = await repository.updateAll(actual);
+    final List<DietPlan> expected = updateResponse.results;
+
+    // CLEAR FROM DB
+    await deleteFromApi(updateResponse.results);
+    // CLEAR FROM DB
+    await deleteFromApi(updateResponse.results);
+    await deleteFromApi(apiResponse.results);
 
     // Then
     expect(actual[0].objectId, expected[0].objectId);
diff --git a/example/test/data/repository/diet_plan/repository_diet_plan_test.dart b/example/test/data/repository/diet_plan/repository_diet_plan_test.dart
index e10e34fc6..adce1b58d 100644
--- a/example/test/data/repository/diet_plan/repository_diet_plan_test.dart
+++ b/example/test/data/repository/diet_plan/repository_diet_plan_test.dart
@@ -4,11 +4,13 @@ import 'package:flutter_plugin_example/data/repositories/diet_plan/contract_prov
 import 'package:flutter_plugin_example/data/repositories/diet_plan/repository_diet_plan.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:mockito/mockito.dart';
+import 'package:shared_preferences/shared_preferences.dart';
 
 import '../repository_mock_utils.dart';
 
 void main() {
   DietPlanRepository repository;
+  SharedPreferences.setMockInitialValues(Map<String, String>());
 
   DietPlanProviderContract apiRepository;
   DietPlanProviderContract dbRepository;
@@ -23,19 +25,19 @@ void main() {
 
     when(repositoryApi.add(any)).thenAnswer((_) async =>
         Future<ApiResponse>.value(
-            ApiResponse(true, 200, getDummyDietPlan(), null)));
+            ApiResponse(true, 200, <dynamic>[getDummyDietPlan()], null)));
     when(repositoryApi.addAll(any)).thenAnswer((_) async =>
         Future<ApiResponse>.value(ApiResponse(true, 200, mockList, null)));
     when(repositoryApi.update(any)).thenAnswer((_) async =>
         Future<ApiResponse>.value(
-            ApiResponse(true, 200, getDummyDietPlan(), null)));
+            ApiResponse(true, 200, <dynamic>[getDummyDietPlan()], null)));
     when(repositoryApi.updateAll(any)).thenAnswer((_) async =>
         Future<ApiResponse>.value(ApiResponse(true, 200, mockList, null)));
     when(repositoryApi.getNewerThan(any)).thenAnswer((_) async =>
         Future<ApiResponse>.value(ApiResponse(true, 200, mockList, null)));
     when(repositoryApi.getById(any)).thenAnswer((_) async =>
         Future<ApiResponse>.value(
-            ApiResponse(true, 200, getDummyDietPlan(), null)));
+            ApiResponse(true, 200, <dynamic>[getDummyDietPlan()], null)));
     when(repositoryApi.getById(any)).thenAnswer((_) async =>
         Future<ApiResponse>.value(ApiResponse(true, 200, mockList, null)));
 
@@ -51,7 +53,7 @@ void main() {
     dbRepository = await getDBRepository();
 
     final DietPlanRepository repository = DietPlanRepository.init(null,
-        repositoryDB: dbRepository, repositoryAPI: apiRepository);
+        mockDBProvider: dbRepository, mockAPIProvider: apiRepository);
 
     return repository;
   }
diff --git a/example/test/data/repository/repository_mock_utils.dart b/example/test/data/repository/repository_mock_utils.dart
index 6a89f3ca7..7d3a9e9ff 100644
--- a/example/test/data/repository/repository_mock_utils.dart
+++ b/example/test/data/repository/repository_mock_utils.dart
@@ -37,3 +37,11 @@ DietPlan getDummyDietPlan() {
     ..fat = 20
     ..status = 0;
 }
+
+Future<void> deleteFromApi(List<dynamic> results) async {
+  if (results != null && results.isNotEmpty) {
+    for (final ParseObject item in results) {
+      await item.delete();
+    }
+  }
+}
diff --git a/lib/src/objects/parse_response.dart b/lib/src/objects/parse_response.dart
index a1afeaee9..d278bbdd1 100644
--- a/lib/src/objects/parse_response.dart
+++ b/lib/src/objects/parse_response.dart
@@ -8,8 +8,6 @@ class ParseResponse {
   ///
   /// This is now deprecated - Please use results. This will contain a list of
   /// results, no need to check if its a list or a list of elements anymore.
-  ///
-  @deprecated
   dynamic result;
 
   /// All results stored as a list - Even if only one response is returned
diff --git a/lib/src/objects/parse_user.dart b/lib/src/objects/parse_user.dart
index 25d1a5c8b..11f1d2603 100644
--- a/lib/src/objects/parse_user.dart
+++ b/lib/src/objects/parse_user.dart
@@ -107,8 +107,9 @@ class ParseUser extends ParseObject implements ParseCloneable {
   /// Current user is stored locally, but in case of a server update [bool]
   /// fromServer can be called and an updated version of the [User] object will be
   /// returned
-  static Future<ParseUser> currentUser() {
-    return _getUserFromLocalStore();
+  static Future<ParseUser> currentUser({ParseCloneable user}) async {
+    final ParseCloneable cloneable = user ?? ParseUser._getEmptyUser();
+    return await _getUserFromLocalStore(cloneable);
   }
 
   /// Registers a user on Parse Server
@@ -318,7 +319,7 @@ class ParseUser extends ParseObject implements ParseCloneable {
     }
   }
 
-  static Future<ParseUser> _getUserFromLocalStore() async {
+  static Future<ParseUser> _getUserFromLocalStore(ParseCloneable object) async {
     final String userJson =
         (await ParseCoreData().getStore()).getString(keyParseStoreUser);
 
@@ -326,8 +327,7 @@ class ParseUser extends ParseObject implements ParseCloneable {
       final Map<String, dynamic> userMap = json.decode(userJson);
       if (userMap != null) {
         ParseCoreData().setSessionId(userMap[keyParamSessionToken]);
-        final ParseUser user = parseDecode(userMap);
-        return user;
+        return object.clone(userMap);
       }
     }
 

From 16a9fbf5436ae2b96c5ee345c7311aa0afe487ae Mon Sep 17 00:00:00 2001
From: wigginsp <phillipwiggins@rentalcars.com>
Date: Fri, 5 Apr 2019 13:14:54 +0100
Subject: [PATCH 14/25] Fixed ParseFile code saving

---
 lib/src/objects/parse_file.dart | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/src/objects/parse_file.dart b/lib/src/objects/parse_file.dart
index 594626658..bd2a53909 100644
--- a/lib/src/objects/parse_file.dart
+++ b/lib/src/objects/parse_file.dart
@@ -46,7 +46,7 @@ class ParseFile extends ParseObject {
       <String, String>{'__type': keyFile, 'name': name, 'url': url};
 
   @override
-  String toString() => json.encode(toString());
+  String toString() => json.encode(toJson(full: true));
 
   Future<ParseFile> loadStorage() async {
     final Directory tempPath = await getTemporaryDirectory();

From 52de117669138dbaf43d0879751e532b9db277f7 Mon Sep 17 00:00:00 2001
From: wigginsp <phillipwiggins@rentalcars.com>
Date: Fri, 5 Apr 2019 13:15:47 +0100
Subject: [PATCH 15/25] ParseInstallation fix

---
 lib/src/objects/parse_installation.dart | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/src/objects/parse_installation.dart b/lib/src/objects/parse_installation.dart
index 155b7cdfa..e4ed0df9f 100644
--- a/lib/src/objects/parse_installation.dart
+++ b/lib/src/objects/parse_installation.dart
@@ -123,7 +123,7 @@ class ParseInstallation extends ParseObject {
         (await ParseCoreData().getStore()).getString(keyParseStoreInstallation);
 
     if (installationJson != null) {
-      final dynamic installationMap = parseDecode(json.decode(installationJson));
+      final Map<String, dynamic> installationMap = json.decode(installationJson);
 
       if (installationMap != null) {
         return ParseInstallation()..fromJson(installationMap);

From 9c08776b650c4782145ea232886c92cc9b1d2278 Mon Sep 17 00:00:00 2001
From: Phill <phill.wiggins@gmail.com>
Date: Fri, 5 Apr 2019 20:16:27 +0100
Subject: [PATCH 16/25] ParseUser supporting custom user objects

---
 example/lib/data/model/user.dart |  3 ++-
 example/lib/{ui => }/main.dart   | 43 ++++++++++++++++++++++----------
 lib/src/objects/parse_user.dart  | 19 +++++++++-----
 3 files changed, 45 insertions(+), 20 deletions(-)
 rename example/lib/{ui => }/main.dart (89%)

diff --git a/example/lib/data/model/user.dart b/example/lib/data/model/user.dart
index 9084debe8..78a0f9761 100644
--- a/example/lib/data/model/user.dart
+++ b/example/lib/data/model/user.dart
@@ -35,7 +35,8 @@ class User extends ParseUser implements ParseCloneable {
   set name(String name) => set<String>(keyName, name);
 
   String get displayPicture => get<String>(keyDisplayPicture);
-  set displayPicture(String displayPicture) => set<String>(keyDisplayPicture, displayPicture);
+  set displayPicture(String displayPicture) =>
+      set<String>(keyDisplayPicture, displayPicture);
 
   bool get proUser => get<bool>(keyProUser);
   set proUser(bool proUser) => set<bool>(keyProUser, proUser);
diff --git a/example/lib/ui/main.dart b/example/lib/main.dart
similarity index 89%
rename from example/lib/ui/main.dart
rename to example/lib/main.dart
index e4d6b9751..b5426b9a0 100644
--- a/example/lib/ui/main.dart
+++ b/example/lib/main.dart
@@ -2,10 +2,10 @@ import 'dart:convert';
 
 import 'package:flutter/material.dart';
 import 'package:flutter_plugin_example/data/base/api_response.dart';
-import 'package:flutter_plugin_example/data/model/day.dart';
 import 'package:flutter_plugin_example/data/model/diet_plan.dart';
 import 'package:flutter_plugin_example/data/model/user.dart';
 import 'package:flutter_plugin_example/data/repositories/diet_plan/repository_diet_plan.dart';
+import 'package:flutter_plugin_example/data/repositories/user/repository_user.dart';
 import 'package:flutter_plugin_example/domain/constants/application_constants.dart';
 import 'package:flutter_plugin_example/domain/utils/db_utils.dart';
 import 'package:flutter_stetho/flutter_stetho.dart';
@@ -22,7 +22,8 @@ class MyApp extends StatefulWidget {
 }
 
 class _MyAppState extends State<MyApp> {
-  DietPlanRepository repository;
+  DietPlanRepository dietPlanRepo;
+  UserRepository userRepo;
 
   @override
   void initState() {
@@ -63,11 +64,12 @@ class _MyAppState extends State<MyApp> {
 
   Future<void> runTestQueries() async {
     // Basic repository example
-    await repositoryAddItems();
-    await repositoryGetAllItems();
+    await repositoryAddUser();
+    /*await repositoryAddItems();
+    await repositoryGetAllItems()*/
 
     //Basic usage
-    createItem();
+    /*createItem();
     getAllItems();
     getAllItemsByName();
     getSingleItem();
@@ -76,7 +78,7 @@ class _MyAppState extends State<MyApp> {
     initUser();
     function();
     functionWithParameters();
-    test();
+    test();*/
   }
 
   Future<void> test() async {
@@ -93,9 +95,9 @@ class _MyAppState extends State<MyApp> {
       }
     }
 
-    final QueryBuilder<Day> query = QueryBuilder<Day>(Day())
-      ..whereEqualTo(Day.keyOwner, user);
-    var item = await query.query();
+    final QueryBuilder<DietPlan> query = QueryBuilder<DietPlan>(DietPlan())
+      ..whereEqualTo(keyProtein, 30);
+    final ParseResponse item = await query.query();
     print(item.toString());
   }
 
@@ -294,8 +296,22 @@ class _MyAppState extends State<MyApp> {
     }
   }
 
+  Future<void> repositoryAddUser() async {
+    final User user = User('test_username', 'password', 'test@gmail.com');
+
+    final ApiResponse response = await userRepo.save(user);
+
+    if (!response.success) {
+      await userRepo.login(user);
+    }
+
+    final User currentUser =
+        await ParseUser.currentUser(customUserObject: User.clone());
+    print(currentUser);
+  }
+
   Future<void> repositoryAddItems() async {
-    final List<DietPlan> dietPlans = List();
+    final List<DietPlan> dietPlans = <DietPlan>[];
 
     final List<dynamic> json = const JsonDecoder().convert(dietPlansToAdd);
     for (final Map<String, dynamic> element in json) {
@@ -304,21 +320,22 @@ class _MyAppState extends State<MyApp> {
     }
 
     await initRepository();
-    final ApiResponse response = await repository.addAll(dietPlans);
+    final ApiResponse response = await dietPlanRepo.addAll(dietPlans);
     if (response.success) {
       print(response.result);
     }
   }
 
   Future<void> repositoryGetAllItems() async {
-    final ApiResponse response = await repository.getAll();
+    final ApiResponse response = await dietPlanRepo.getAll();
     if (response.success) {
       print(response.result);
     }
   }
 
   Future<void> initRepository() async {
-    repository ??= DietPlanRepository.init(await getDB());
+    dietPlanRepo ??= DietPlanRepository.init(await getDB());
+    userRepo ??= UserRepository.init(await getDB());
   }
 }
 
diff --git a/lib/src/objects/parse_user.dart b/lib/src/objects/parse_user.dart
index 11f1d2603..2cc6a7d18 100644
--- a/lib/src/objects/parse_user.dart
+++ b/lib/src/objects/parse_user.dart
@@ -107,9 +107,13 @@ class ParseUser extends ParseObject implements ParseCloneable {
   /// Current user is stored locally, but in case of a server update [bool]
   /// fromServer can be called and an updated version of the [User] object will be
   /// returned
-  static Future<ParseUser> currentUser({ParseCloneable user}) async {
-    final ParseCloneable cloneable = user ?? ParseUser._getEmptyUser();
-    return await _getUserFromLocalStore(cloneable);
+  static Future<dynamic> currentUser({ParseCloneable customUserObject}) async {
+
+    if (customUserObject != null) {
+      return await _getUserFromLocalStore(cloneable: customUserObject);
+    } else {
+      return await _getUserFromLocalStore();
+    }
   }
 
   /// Registers a user on Parse Server
@@ -319,15 +323,18 @@ class ParseUser extends ParseObject implements ParseCloneable {
     }
   }
 
-  static Future<ParseUser> _getUserFromLocalStore(ParseCloneable object) async {
+  static Future<dynamic> _getUserFromLocalStore(
+      {ParseCloneable cloneable}) async {
     final String userJson =
         (await ParseCoreData().getStore()).getString(keyParseStoreUser);
 
     if (userJson != null) {
       final Map<String, dynamic> userMap = json.decode(userJson);
-      if (userMap != null) {
+      if (cloneable != null) {
+        return cloneable.clone(userMap);
+      } else {
         ParseCoreData().setSessionId(userMap[keyParamSessionToken]);
-        return object.clone(userMap);
+        return parseDecode(userMap);
       }
     }
 

From 012539be660e4b715a737c7492eb26dd580ddf99 Mon Sep 17 00:00:00 2001
From: Rodrigo de Souza Marques <rodrigosmarques@gmail.com>
Date: Sat, 6 Apr 2019 03:14:19 -0300
Subject: [PATCH 17/25]  Add Support to Relational Queries / Counting Objects 
 (#146)

* Add Support to Relational Queries

Add Support to Relational Queries

* Add Support to Counting Objects

Add Support to Counting Objects
https://docs.parseplatform.org/rest/guide/#counting-objects
---
 lib/src/network/parse_query.dart              | 62 +++++++++++++++++--
 .../response/parse_response_builder.dart      |  5 ++
 2 files changed, 61 insertions(+), 6 deletions(-)

diff --git a/lib/src/network/parse_query.dart b/lib/src/network/parse_query.dart
index d06536b07..d32a5c1ea 100644
--- a/lib/src/network/parse_query.dart
+++ b/lib/src/network/parse_query.dart
@@ -237,6 +237,22 @@ class QueryBuilder<T extends ParseObject> {
         '\"$column\":{\"\$within\":{\"\$box\": [{\"__type\": \"GeoPoint\",\"latitude\":$latitudeS,\"longitude\":$longitudeS},{\"__type\": \"GeoPoint\",\"latitude\":$latitudeN,\"longitude\":$longitudeN}]}}'));
   }
 
+  // Add a constraint to the query that requires a particular key's value match another QueryBuilder
+  void whereMatchesQuery(String column, QueryBuilder query) {
+    String inQuery = query._buildQueryRelational(query.object.className);
+
+    queries.add(MapEntry<String, dynamic>(
+        _SINGLE_QUERY, '\"$column\":{\"\$inQuery\":$inQuery}'));
+  }
+
+  //Add a constraint to the query that requires a particular key's value does not match another QueryBuilder
+  void whereDoesNotMatchQuery(String column, QueryBuilder query) {
+    String inQuery = query._buildQueryRelational(query.object.className);
+
+    queries.add(MapEntry<String, dynamic>(
+        _SINGLE_QUERY, '\"$column\":{\"\$notInQuery\":$inQuery}'));
+  }
+
   /// Finishes the query and calls the server
   ///
   /// Make sure to call this after defining your queries
@@ -244,12 +260,29 @@ class QueryBuilder<T extends ParseObject> {
     return object.query(_buildQuery());
   }
 
+  ///Counts the number of objects that match this query
+  Future<ParseResponse> count() async {
+    return object.query(_buildQueryCount());
+  }
+
   /// Builds the query for Parse
   String _buildQuery() {
     queries = _checkForMultipleColumnInstances(queries);
     return 'where={${buildQueries(queries)}}${getLimiters(limiters)}';
   }
 
+  /// Builds the query relational for Parse
+  String _buildQueryRelational(String className) {
+    queries = _checkForMultipleColumnInstances(queries);
+    return '{\"where\":{${buildQueries(queries)}},\"className\":\"$className\"${getLimitersRelational(limiters)}}';
+  }
+
+  /// Builds the query for Parse
+  String _buildQueryCount() {
+    queries = _checkForMultipleColumnInstances(queries);
+    return 'where={${buildQueries(queries)}}&count=1';
+  }
+
   /// Runs through all queries and adds them to a query string
   String buildQueries(List<MapEntry<String, dynamic>> queries) {
     String queryBuilder = '';
@@ -284,17 +317,20 @@ class QueryBuilder<T extends ParseObject> {
   /// that the column and value are being queried against
   MapEntry<String, dynamic> _buildQueryWithColumnValueAndOperator(
       MapEntry columnAndValue, String queryOperator) {
-
     final String key = columnAndValue.key;
-    final dynamic value = convertValueToCorrectType(parseEncode(columnAndValue.value));
+    final dynamic value =
+        convertValueToCorrectType(parseEncode(columnAndValue.value));
 
     if (queryOperator == _NO_OPERATOR_NEEDED) {
-      return MapEntry<String, dynamic>(_NO_OPERATOR_NEEDED, '\"$key\": ${jsonEncode(value)}');
+      return MapEntry<String, dynamic>(
+          _NO_OPERATOR_NEEDED, '\"$key\": ${jsonEncode(value)}');
     } else {
       String queryString = '\"$key\":';
-      final Map<String, dynamic> queryOperatorAndValueMap = Map<String, dynamic>();
+      final Map<String, dynamic> queryOperatorAndValueMap =
+          Map<String, dynamic>();
       queryOperatorAndValueMap[queryOperator] = parseEncode(value);
-      final String formattedQueryOperatorAndValue = jsonEncode(queryOperatorAndValueMap);
+      final String formattedQueryOperatorAndValue =
+          jsonEncode(queryOperatorAndValueMap);
       queryString += '$formattedQueryOperatorAndValue';
       return MapEntry<String, dynamic>(key, queryString);
     }
@@ -336,7 +372,8 @@ class QueryBuilder<T extends ParseObject> {
         for (MapEntry<String, dynamic> queryToCompact in listOfQueriesCompact) {
           var queryToCompactValue = queryToCompact.value.toString();
           queryToCompactValue = queryToCompactValue.replaceFirst("{", "");
-          queryToCompactValue = queryToCompactValue.replaceRange(queryToCompactValue.length - 1, queryToCompactValue.length, "");
+          queryToCompactValue = queryToCompactValue.replaceRange(
+              queryToCompactValue.length - 1, queryToCompactValue.length, "");
           if (listOfQueriesCompact.first == queryToCompact) {
             queryEnd += queryToCompactValue.replaceAll(queryStart, ' ');
           } else {
@@ -364,4 +401,17 @@ class QueryBuilder<T extends ParseObject> {
     });
     return result;
   }
+
+  /// Adds the limiters to the query relational, i.e. skip=10, limit=10
+  String getLimitersRelational(Map<String, dynamic> map) {
+    String result = '';
+    map.forEach((String key, dynamic value) {
+      if (result != null) {
+        result = result + ',\"$key":$value';
+      } else {
+        result = '\"$key\":$value';
+      }
+    });
+    return result;
+  }
 }
diff --git a/lib/src/objects/response/parse_response_builder.dart b/lib/src/objects/response/parse_response_builder.dart
index 74075ca03..b7e06767b 100644
--- a/lib/src/objects/response/parse_response_builder.dart
+++ b/lib/src/objects/response/parse_response_builder.dart
@@ -71,6 +71,11 @@ class _ParseResponseBuilder {
       response.results = items;
       response.result = items;
       response.count = items.length;
+    } else if (map != null && map.length == 2 && map.containsKey('count')) {
+      final List<int> results = [map['count']];
+      response.results = results;
+      response.result = results;
+      response.count = map['count'];
     } else {
       final T item = _handleSingleResult<T>(object, map, false);
       response.count = 1;

From 8a0c3341f5606b4d2fc57d8a4bb30b6cae4405e6 Mon Sep 17 00:00:00 2001
From: Rodrigo de Souza Marques <rodrigosmarques@gmail.com>
Date: Sat, 6 Apr 2019 09:11:51 -0300
Subject: [PATCH 18/25] Update the documentation with Relational Queries and
 Count Objects (#147)

* Add Support to Relational Queries

Add Support to Relational Queries

* Add Support to Counting Objects

Add Support to Counting Objects
https://docs.parseplatform.org/rest/guide/#counting-objects

* Update README.md

Update documentation with example for Relational Queries and Counting Objects

* Update README.md
---
 README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/README.md b/README.md
index 37f1b35c6..157a13d12 100644
--- a/README.md
+++ b/README.md
@@ -106,6 +106,54 @@ The features available are:-
  * Ascending
  * Descending
  * Plenty more!
+ 
+## Relational queries
+If you want to retrieve objects where a field contains an object that matches another query, you can use the
+__whereMatchesQuery__ condition.
+For example, imagine you vave Post class and a Comment class, where each Comment has a pointer to its parent Post.
+You can find comments on posts with images by doing:
+
+```dart
+  QueryBuilder<ParseObject> queryPost =
+      QueryBuilder<ParseObject>(ParseObject('Post'))
+        ..whereValueExists('image', true);
+
+  QueryBuilder<ParseObject> queryComment =
+      QueryBuilder<ParseObject>(ParseObject('Comment'))
+        ..whereMatchesQuery('post', queryPost);
+
+  var apiResponse = await queryComment.query();
+```
+
+If you want to retrieve objects where a field contains an object that does not match another query,  you can use the
+__whereDoesNotMatchQuery__ condition.
+Imagine you have Post class and a Comment class, where each Comment has a pointer to its parent Post.
+You can find comments on posts without images by doing:
+
+```dart
+  QueryBuilder<ParseObject> queryPost =
+      QueryBuilder<ParseObject>(ParseObject('Post'))
+        ..whereValueExists('image', true);
+
+  QueryBuilder<ParseObject> queryComment =
+      QueryBuilder<ParseObject>(ParseObject('Comment'))
+        ..whereDoesNotMatchQuery('post', queryPost);
+
+  var apiResponse = await queryComment.query();
+```
+
+## Counting Objects
+If you only care about the number of games played by a particular player:
+
+```dart
+  QueryBuilder<ParseObject> queryPlayers =
+      QueryBuilder<ParseObject>(ParseObject('GameScore'))
+        ..whereEqualTo('playerName', 'Jonathan Walsh');
+  var apiResponse = await queryPlayers.count();
+  if (apiResponse.success && apiResponse.result != null) {
+    int countGames = apiResponse.count;
+  }
+```
 
 ## Objects
 

From efe937e4edc31fa0615f4b4f4ae542a49955b4c7 Mon Sep 17 00:00:00 2001
From: Rodrigo de Souza Marques <rodrigosmarques@gmail.com>
Date: Sat, 6 Apr 2019 14:48:20 -0300
Subject: [PATCH 19/25]  Update README.md - Small typo (#148)

* Add Support to Relational Queries

Add Support to Relational Queries

* Add Support to Counting Objects

Add Support to Counting Objects
https://docs.parseplatform.org/rest/guide/#counting-objects

* Update README.md

Update documentation with example for Relational Queries and Counting Objects

* Update README.md

* Update README.md
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 157a13d12..aeaefdc27 100644
--- a/README.md
+++ b/README.md
@@ -110,7 +110,7 @@ The features available are:-
 ## Relational queries
 If you want to retrieve objects where a field contains an object that matches another query, you can use the
 __whereMatchesQuery__ condition.
-For example, imagine you vave Post class and a Comment class, where each Comment has a pointer to its parent Post.
+For example, imagine you have Post class and a Comment class, where each Comment has a pointer to its parent Post.
 You can find comments on posts with images by doing:
 
 ```dart

From 4c0c56d7152d686ab5881d4b8f4b0ba18129282b Mon Sep 17 00:00:00 2001
From: Rodrigo de Souza Marques <rodrigosmarques@gmail.com>
Date: Sat, 13 Apr 2019 15:07:24 -0300
Subject: [PATCH 20/25] BugFix LiveQuery - update readme.md (#150)

* Add Support to Relational Queries

Add Support to Relational Queries

* Add Support to Counting Objects

Add Support to Counting Objects
https://docs.parseplatform.org/rest/guide/#counting-objects

* Update README.md

Update documentation with example for Relational Queries and Counting Objects

* Update README.md

* Update README.md

* BugFix LiveQuery

BugFix LiveQuery

* Bugfix LiveQuery

Bugfix LiveQuery

* Update README.md

Reorganization of sessions and inclusion of documentation on LiveQuery

* Update README.md

* Update README.md

* Bugfix LiveQuery

Bugfix LiveQuery

* Changed to make optional clientKey

Changed to make optional clientKey
---
 README.md                             | 263 +++++++++++++++++++-------
 lib/src/enums/parse_enum_api_rq.dart  |   3 +-
 lib/src/network/parse_live_query.dart | 203 ++++++++++++++++----
 3 files changed, 358 insertions(+), 111 deletions(-)

diff --git a/README.md b/README.md
index aeaefdc27..2320c7f04 100644
--- a/README.md
+++ b/README.md
@@ -35,11 +35,95 @@ Parse().initialize(
         masterKey: ApplicationConstants.keyParseMasterKey,
         clientKey: ApplicationConstants.keyParseClientKey,
         debug: true,
-        liveQuery: true,
+        liveQueryUrl: ApplicationConstants.keyLiveQueryUrl,
         autoSendSessionId: true,
         securityContext: securityContext);
 ```
 
+## Objects
+You can create custom objects by calling:
+```dart
+var dietPlan = ParseObject('DietPlan')
+	..set('Name', 'Ketogenic')
+	..set('Fat', 65);
+```
+You then have the ability to do the following with that object:
+The features available are:-
+ * Get
+ * GetAll
+ * Create
+ * Save
+ * Query - By object Id
+ * Delete
+ * Complex queries as shown above
+ * Pin
+ * Plenty more
+ * Counters
+ * Array Operators
+
+## Custom Objects
+You can create your own ParseObjects or convert your existing objects into Parse Objects by doing the following:
+
+```dart
+class DietPlan extends ParseObject implements ParseCloneable {
+
+  DietPlan() : super(_keyTableName);
+  DietPlan.clone(): this();
+
+  /// Looks strangely hacky but due to Flutter not using reflection, we have to
+  /// mimic a clone
+  @override clone(Map map) => DietPlan.clone()..fromJson(map);
+
+  static const String _keyTableName = 'Diet_Plans';
+  static const String keyName = 'Name';
+  
+  String get name => get<String>(keyName);
+  set name(String name) => set<String>(keyName, name);
+}
+  
+```
+
+## Add new values to objects
+To add a variable to an object call and retrieve it, call
+
+```dart
+dietPlan.set<int>('RandomInt', 8);
+var randomInt = dietPlan.get<int>('RandomInt');
+```
+
+## Save objects using pins
+You can now save an object by calling .pin() on an instance of an object
+
+```dart
+dietPlan.pin();
+```
+
+and to retrieve it
+
+```dart
+var dietPlan = DietPlan().fromPin('OBJECT ID OF OBJECT');
+```
+
+## Increment Counter values in objects
+Retrieve it, call
+
+```dart
+var response = await dietPlan.increment("count", 1);
+
+```
+
+## Array Operator in objects
+Retrieve it, call
+
+```dart
+var response = await dietPlan.add("listKeywords", ["a", "a","d"]);
+
+var response = await dietPlan.addUnique("listKeywords", ["a", "a","d"]);
+
+var response = await dietPlan.remove("listKeywords", ["a"]);
+
+```
+
 ## Queries
 Once you have setup the project and initialised the instance, you can then retreive data from your server by calling:
 ```dart
@@ -63,7 +147,6 @@ var dietPlan = await DietPlan().getObject('R5EonpUDWy');
     }
 ```
 
-
 ## Complex queries
 You can create complex queries to really put your database to the test:
 
@@ -155,98 +238,136 @@ If you only care about the number of games played by a particular player:
   }
 ```
 
-## Objects
+## Live Queries
+This tool allows you to subscribe to a QueryBuilder you are interested in. Once subscribed, the server will notify clients
+whenever a ParseObject that matches the QueryBuilder is created or updated, in real-time.
 
-You can create custom objects by calling:
-```dart
-var dietPlan = ParseObject('DietPlan')
-	..set('Name', 'Ketogenic')
-	..set('Fat', 65);
-```
-You then have the ability to do the following with that object:
-The features available are:-
- * Get
- * GetAll
- * Create
- * Save
- * Query - By object Id
- * Delete
- * Complex queries as shown above
- * Pin
- * Plenty more
- * Counters
- * Array Operators
+Parse LiveQuery contains two parts, the LiveQuery server and the LiveQuery clients. In order to use live queries, you need
+to set up both of them.
 
-## Custom Objects
-You can create your own ParseObjects or convert your existing objects into Parse Objects by doing the following:
+The Parse Server configuration guide on the server is found here https://docs.parseplatform.org/parse-server/guide/#live-queries and is not part of this documentation.
 
+Initialize the Parse Live Query by entering the parameter liveQueryUrl in Parse().initialize:
 ```dart
-class DietPlan extends ParseObject implements ParseCloneable {
-
-  DietPlan() : super(_keyTableName);
-  DietPlan.clone(): this();
-
-  /// Looks strangely hacky but due to Flutter not using reflection, we have to
-  /// mimic a clone
-  @override clone(Map map) => DietPlan.clone()..fromJson(map);
-
-  static const String _keyTableName = 'Diet_Plans';
-  static const String keyName = 'Name';
-  
-  String get name => get<String>(keyName);
-  set name(String name) => set<String>(keyName, name);
-}
-  
+  Parse().initialize(
+        ApplicationConstants.keyApplicationId,
+        ApplicationConstants.keyParseServerUrl,
+        clientKey: ApplicationConstants.keyParseClientKey,
+        debug: true,
+        liveQueryUrl: ApplicationConstants.keyLiveQueryUrl,
+        autoSendSessionId: true);
 ```
 
-## Add new values to objects
-
-To add a variable to an object call and retrieve it, call
-
+Declare LiveQuery:
 ```dart
-dietPlan.set<int>('RandomInt', 8);
-var randomInt = dietPlan.get<int>('RandomInt');
+  final LiveQuery liveQuery = LiveQuery();
 ```
 
-## Save objects using pins
-
-You can now save an object by calling .pin() on an instance of an object
-
+Set the QueryBuilder that will be monitored by LiveQuery:
 ```dart
-dietPlan.pin();
+  QueryBuilder<ParseObject> query =
+    QueryBuilder<ParseObject>(ParseObject('TestAPI'))
+    ..whereEqualTo('intNumber', 1);
 ```
-
-and to retrieve it
+__Create a subscription__
+You’ll get the LiveQuery events through this subscription. 
+The first time you call subscribe, we’ll try to open the WebSocket connection to the LiveQuery server for you.
 
 ```dart
-var dietPlan = DietPlan().fromPin('OBJECT ID OF OBJECT');
+  await liveQuery.subscribe(query);
 ```
 
-## Increment Counter values in objects
-
-Retrieve it, call
+__Event Handling__
+We define several types of events you’ll get through a subscription object:
 
+__Create event__
+When a new ParseObject is created and it fulfills the QueryBuilder you subscribe, you’ll get this event. 
+The object is the ParseObject which was created.
 ```dart
-var response = await dietPlan.increment("count", 1);
-
+  liveQuery.on(LiveQueryEvent.create, (value) {
+      print('*** CREATE ***: ${DateTime.now().toString()}\n $value ');
+      print((value as ParseObject).objectId);
+      print((value as ParseObject).updatedAt);
+      print((value as ParseObject).createdAt);
+      print((value as ParseObject).get('objectId'));
+      print((value as ParseObject).get('updatedAt'));
+      print((value as ParseObject).get('createdAt'));
+    });
 ```
 
-## Array Operator in objects
+__Update event__
+When an existing ParseObject which fulfills the QueryBuilder you subscribe is updated (The ParseObject fulfills the 
+QueryBuilder before and after changes), you’ll get this event. 
+The object is the ParseObject which was updated. Its content is the latest value of the ParseObject.
+```dart
+  liveQuery.on(LiveQueryEvent.update, (value) {
+      print('*** UPDATE ***: ${DateTime.now().toString()}\n $value ');
+      print((value as ParseObject).objectId);
+      print((value as ParseObject).updatedAt);
+      print((value as ParseObject).createdAt);
+      print((value as ParseObject).get('objectId'));
+      print((value as ParseObject).get('updatedAt'));
+      print((value as ParseObject).get('createdAt'));
+    });
+```
 
-Retrieve it, call
+__Enter event__
+When an existing ParseObject’s old value does not fulfill the QueryBuilder but its new value fulfills the QueryBuilder, 
+you’ll get this event. The object is the ParseObject which enters the QueryBuilder. 
+Its content is the latest value of the ParseObject.
+```dart
+  liveQuery.on(LiveQueryEvent.enter, (value) {
+      print('*** ENTER ***: ${DateTime.now().toString()}\n $value ');
+      print((value as ParseObject).objectId);
+      print((value as ParseObject).updatedAt);
+      print((value as ParseObject).createdAt);
+      print((value as ParseObject).get('objectId'));
+      print((value as ParseObject).get('updatedAt'));
+      print((value as ParseObject).get('createdAt'));
+    });
+```
 
+__Leave event__
+When an existing ParseObject’s old value fulfills the QueryBuilder but its new value doesn’t fulfill the QueryBuilder, 
+you’ll get this event. The object is the ParseObject which leaves the QueryBuilder. 
+Its content is the latest value of the ParseObject.
 ```dart
-var response = await dietPlan.add("listKeywords", ["a", "a","d"]);
+  liveQuery.on(LiveQueryEvent.leave, (value) {
+      print('*** LEAVE ***: ${DateTime.now().toString()}\n $value ');
+      print((value as ParseObject).objectId);
+      print((value as ParseObject).updatedAt);
+      print((value as ParseObject).createdAt);
+      print((value as ParseObject).get('objectId'));
+      print((value as ParseObject).get('updatedAt'));
+      print((value as ParseObject).get('createdAt'));
+    });
+```
 
-var response = await dietPlan.addUnique("listKeywords", ["a", "a","d"]);
+__Delete event__
+When an existing ParseObject which fulfills the QueryBuilder is deleted, you’ll get this event. 
+The object is the ParseObject which is deleted
+```dart
+  liveQuery.on(LiveQueryEvent.delete, (value) {
+      print('*** DELETE ***: ${DateTime.now().toString()}\n $value ');
+      print((value as ParseObject).objectId);
+      print((value as ParseObject).updatedAt);
+      print((value as ParseObject).createdAt);
+      print((value as ParseObject).get('objectId'));
+      print((value as ParseObject).get('updatedAt'));
+      print((value as ParseObject).get('createdAt'));
+    });
+```
 
-var response = await dietPlan.remove("listKeywords", ["a"]);
+__Unsubscribe__
+If you would like to stop receiving events from a QueryBuilder, you can just unsubscribe the subscription. 
+After that, you won’t get any events from the subscription object and will close the WebSocket connection to the 
+LiveQuery server.
 
+```dart
+  await liveQuery.unSubscribe();
 ```
 
-
 ## Users
-
 You can create and control users just as normal using this SDK.
 
 To register a user, first create one :
@@ -277,7 +398,6 @@ Other user features are:-
  * Queries 
 
 ## Config
-
 The SDK now supports Parse Config. A map of all configs can be grabbed from the server by calling :
 ```dart
 var response = await ParseConfig().getConfigs();
@@ -289,13 +409,8 @@ ParseConfig().addConfig('TestConfig', 'testing');
 ```
 
 ## Other Features of this library
-
 Main:
-* Users
 * Installation
-* Objects
-* Queries
-* LiveQueries
 * GeoPoints
 * Files
 * Persistent storage
@@ -313,11 +428,13 @@ User:
 * Save
 * Destroy
 * Queries
+* Anonymous
+* 3rd Party Authentication
 
 Objects:
 * Create new object
 * Extend Parse Object and create local objects that can be saved and retreived
-* Queries:
+* Queries
 
 ## Author:-
 This project was authored by Phill Wiggins. You can contact me at phill.wiggins@gmail.com
diff --git a/lib/src/enums/parse_enum_api_rq.dart b/lib/src/enums/parse_enum_api_rq.dart
index ea0b9b15d..73eafd895 100644
--- a/lib/src/enums/parse_enum_api_rq.dart
+++ b/lib/src/enums/parse_enum_api_rq.dart
@@ -30,5 +30,6 @@ enum ParseApiRQ {
   increment,
   decrement,
   getConfigs,
-  addConfig
+  addConfig,
+  liveQuery
 }
diff --git a/lib/src/network/parse_live_query.dart b/lib/src/network/parse_live_query.dart
index d04988682..fde65d862 100644
--- a/lib/src/network/parse_live_query.dart
+++ b/lib/src/network/parse_live_query.dart
@@ -1,52 +1,181 @@
 part of flutter_parse_sdk;
 
-/// Still under development
+enum LiveQueryEvent { create, enter, update, leave, delete, error }
+
 class LiveQuery {
+  LiveQuery({bool debug, ParseHTTPClient client, bool autoSendSessionId}) {
+    _client = client ??
+        ParseHTTPClient(
+            sendSessionId:
+                autoSendSessionId ?? ParseCoreData().autoSendSessionId,
+            securityContext: ParseCoreData().securityContext);
 
-  LiveQuery(ParseHTTPClient client) : client = client {
-    connectMessage = <String, String>{
-      'op': 'connect',
-      'applicationId': client.data.applicationId,
-    };
+    _debug = isDebugEnabled(objectLevelDebug: debug);
+    _sendSessionId = autoSendSessionId ?? ParseCoreData().autoSendSessionId;
+  }
 
-    final Map<String, dynamic> whereMap = Map<String, dynamic>();
+  WebSocket _webSocket;
+  ParseHTTPClient _client;
+  bool _debug;
+  bool _sendSessionId;
+  IOWebSocketChannel _channel;
+  Map<String, dynamic> _connectMessage;
+  Map<String, dynamic> _subscribeMessage;
+  Map<String, dynamic> _unsubscribeMessage;
+  Map<String, Function> eventCallbacks = {};
+  int _requestIdCount = 1;
+  final List<String> _liveQueryEvent = [
+    'create',
+    'enter',
+    'update',
+    'leave',
+    'delete',
+    'error'
+  ];
+  final String _printConstLiveQuery = 'LiveQuery: ';
 
-    subscribeMessage = {
-      'op': 'subscribe',
-      'requestId': 1,
-      'query': {
-        'className': null,
-        'where': whereMap,
-      }
-    };
+  int _requestIdGenerator() {
+    return _requestIdCount++;
   }
 
-  final ParseHTTPClient client;
-  IOWebSocketChannel channel;
-  Map<String, Object> connectMessage;
-  Map<String, Object> subscribeMessage;
-  Map<String, Function> eventCallbacks = {};
+  Future<void> subscribe(QueryBuilder query) async {
+    String _liveQueryURL = _client.data.liveQueryURL;
+    if (_liveQueryURL.contains('https')) {
+      _liveQueryURL = _liveQueryURL.replaceAll('https', 'wss');
+    } else if (_liveQueryURL.contains('http')) {
+      _liveQueryURL = _liveQueryURL.replaceAll('http', 'ws');
+    }
+
+    final String _className = query.object.className;
+    query.limiters.clear(); //Remove limites in LiveQuery
+    final String _where = query._buildQuery().replaceAll('where=', '');
+    //Convert where condition to Map
+    Map<String, dynamic> _whereMap = Map<String, dynamic>();
+    if (_where != '') {
+      _whereMap = json.decode(_where);
+    }
+
+    final int requestId = _requestIdGenerator();
+
+    try {
+      _webSocket = await WebSocket.connect(_liveQueryURL);
+
+      if (_webSocket != null && _webSocket.readyState == WebSocket.open) {
+        if (_debug) {
+          print('$_printConstLiveQuery: Socket opened');
+        }
+      } else {
+        if (_debug) {
+          print('$_printConstLiveQuery: Error when connection client');
+          return Future.value(null);
+        }
+      }
+
+      _channel = IOWebSocketChannel(_webSocket);
+      _channel.stream.listen((dynamic message) {
+        if (_debug) {
+          print('$_printConstLiveQuery: Listen: ${message}');
+        }
+
+        final Map<String, dynamic> actionData = jsonDecode(message);
+
+        if (eventCallbacks.containsKey(actionData['op'])) {
+          if (actionData.containsKey('object')) {
+            final Map<String, dynamic> map = actionData['object'];
+            final String className = map['className'];
+            if (className == '_User') {
+              eventCallbacks[actionData['op']](
+                  ParseUser._getEmptyUser().fromJson(map));
+            } else {
+              eventCallbacks[actionData['op']](
+                  ParseObject(className).fromJson(map));
+            }
+          } else {
+            eventCallbacks[actionData['op']](actionData);
+          }
+        }
+      }, onDone: () {
+        if (_debug) {
+          print('$_printConstLiveQuery: Done');
+        }
+      }, onError: (error, StackTrace stackTrace) {
+        if (_debug) {
+          print(
+              '$_printConstLiveQuery: Error: ${error.runtimeType.toString()}');
+        }
+        return Future.value(
+            handleException(error, ParseApiRQ.liveQuery, _debug, _className));
+      });
 
-  Future<void> subscribe(String className) async {
-    final WebSocket webSocket = await WebSocket.connect(client.data.liveQueryURL);
-    channel = IOWebSocketChannel(webSocket);
-    channel.sink.add(jsonEncode(connectMessage));
-    final Map<String, dynamic> classNameMap = subscribeMessage['query'];
-    classNameMap['className'] = className;
-    channel.sink.add(jsonEncode(subscribeMessage));
-
-    channel.stream.listen((dynamic message) {
-      final Map<String, dynamic> actionData = jsonDecode(message);
-      if (eventCallbacks.containsKey(actionData['op']))
-        eventCallbacks[actionData['op']](actionData);
-    });
+      //The connect message is sent from a client to the LiveQuery server.
+      //It should be the first message sent from a client after the WebSocket connection is established.
+      _connectMessage = {
+        'op': 'connect',
+        'applicationId': _client.data.applicationId,
+        'clientKey': _client.data.clientKey ?? ''
+      };
+      if (_sendSessionId) {
+        _connectMessage['sessionToken'] = _client.data.sessionId;
+      }
+
+      if (_debug) {
+        print('$_printConstLiveQuery: ConnectMessage: $_connectMessage');
+      }
+      _channel.sink.add(jsonEncode(_connectMessage));
+
+      //After a client connects to the LiveQuery server,
+      //it can send a subscribe message to subscribe a ParseQuery.
+      _subscribeMessage = {
+        'op': 'subscribe',
+        'requestId': requestId,
+        'query': {
+          'className': _className,
+          'where': _whereMap,
+        }
+      };
+      if (_sendSessionId) {
+        _subscribeMessage['sessionToken'] = _client.data.sessionId;
+      }
+
+      if (_debug) {
+        print('$_printConstLiveQuery: SubscribeMessage: $_subscribeMessage');
+      }
+
+      _channel.sink.add(jsonEncode(_subscribeMessage));
+
+      //Mount message for Unsubscribe
+      _unsubscribeMessage = {
+        'op': 'unsubscribe',
+        'requestId': requestId,
+      };
+    } on Exception catch (e) {
+      if (_debug) {
+        print('$_printConstLiveQuery: Error: ${e.toString()}');
+      }
+      return handleException(e, ParseApiRQ.liveQuery, _debug, _className);
+    }
   }
 
-  void on(String op, Function callback) {
-    eventCallbacks[op] = callback;
+  void on(LiveQueryEvent op, Function callback) {
+    eventCallbacks[_liveQueryEvent[op.index]] = callback;
   }
 
-  Future<void> close() async {
-    await channel.sink.close();
+  Future<void> unSubscribe() async {
+    if (_channel != null) {
+      if (_channel.sink != null) {
+        if (_debug) {
+          print(
+              '$_printConstLiveQuery: UnsubscribeMessage: $_unsubscribeMessage');
+        }
+        await _channel.sink.add(jsonEncode(_unsubscribeMessage));
+        await _channel.sink.close();
+      }
+    }
+    if (_webSocket != null && _webSocket.readyState == WebSocket.open) {
+      if (_debug) {
+        print('$_printConstLiveQuery: Socket closed');
+      }
+      await _webSocket.close();
+    }
   }
 }

From 6f9e2fc2435dfaebf5c792377334c3b704cbf649 Mon Sep 17 00:00:00 2001
From: Rodrigo de Souza Marques <rodrigosmarques@gmail.com>
Date: Sun, 14 Apr 2019 04:48:55 -0300
Subject: [PATCH 21/25] Bugfix parseEncode function with ParseObjects in List
 (#151)

* Add Support to Relational Queries

Add Support to Relational Queries

* Add Support to Counting Objects

Add Support to Counting Objects
https://docs.parseplatform.org/rest/guide/#counting-objects

* Update README.md

Update documentation with example for Relational Queries and Counting Objects

* Update README.md

* Update README.md

* BugFix LiveQuery

BugFix LiveQuery

* Bugfix LiveQuery

Bugfix LiveQuery

* Update README.md

Reorganization of sessions and inclusion of documentation on LiveQuery

* Update README.md

* Update README.md

* Bugfix LiveQuery

Bugfix LiveQuery

* Changed to make optional clientKey

Changed to make optional clientKey

* Bugfix parseEncode function with ParseObjects in List

Bugfix parseEncode function with ParseObjects in List
---
 lib/src/utils/parse_encoder.dart | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/lib/src/utils/parse_encoder.dart b/lib/src/utils/parse_encoder.dart
index c64d439a4..f2929c376 100644
--- a/lib/src/utils/parse_encoder.dart
+++ b/lib/src/utils/parse_encoder.dart
@@ -20,6 +20,12 @@ dynamic parseEncode(dynamic value, {bool full}) {
     return _encodeDate(value);
   }
 
+  if (value is List) {
+    return value.map<dynamic>((dynamic value) {
+      return parseEncode(value);
+    }).toList();
+  }
+
   if (value is ParseGeoPoint) {
     return value;
   }
@@ -40,10 +46,7 @@ dynamic parseEncode(dynamic value, {bool full}) {
 }
 
 Map<String, dynamic> _encodeUint8List(Uint8List value) {
-  return <String, dynamic>{
-    '__type': 'Bytes',
-    'base64': base64.encode(value)
-  };
+  return <String, dynamic>{'__type': 'Bytes', 'base64': base64.encode(value)};
 }
 
 Map<String, dynamic> _encodeDate(DateTime date) {

From 99ac1ad3eb35be4a285dfa539c3d10dff3f0f38a Mon Sep 17 00:00:00 2001
From: Phill <phill.wiggins@gmail.com>
Date: Sun, 14 Apr 2019 08:51:15 +0100
Subject: [PATCH 22/25] 1.0.17 - Cody tidy

---
 lib/src/network/parse_live_query.dart | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lib/src/network/parse_live_query.dart b/lib/src/network/parse_live_query.dart
index fde65d862..8ce3f9ccf 100644
--- a/lib/src/network/parse_live_query.dart
+++ b/lib/src/network/parse_live_query.dart
@@ -98,18 +98,18 @@ class LiveQuery {
         if (_debug) {
           print('$_printConstLiveQuery: Done');
         }
-      }, onError: (error, StackTrace stackTrace) {
+      }, onError: (Error error, StackTrace stackTrace) {
         if (_debug) {
           print(
               '$_printConstLiveQuery: Error: ${error.runtimeType.toString()}');
         }
         return Future.value(
-            handleException(error, ParseApiRQ.liveQuery, _debug, _className));
+            handleException(Exception(error), ParseApiRQ.liveQuery, _debug, _className));
       });
 
       //The connect message is sent from a client to the LiveQuery server.
       //It should be the first message sent from a client after the WebSocket connection is established.
-      _connectMessage = {
+      _connectMessage = <String, String>{
         'op': 'connect',
         'applicationId': _client.data.applicationId,
         'clientKey': _client.data.clientKey ?? ''
@@ -125,7 +125,7 @@ class LiveQuery {
 
       //After a client connects to the LiveQuery server,
       //it can send a subscribe message to subscribe a ParseQuery.
-      _subscribeMessage = {
+      _subscribeMessage = <String, dynamic>{
         'op': 'subscribe',
         'requestId': requestId,
         'query': {
@@ -144,7 +144,7 @@ class LiveQuery {
       _channel.sink.add(jsonEncode(_subscribeMessage));
 
       //Mount message for Unsubscribe
-      _unsubscribeMessage = {
+      _unsubscribeMessage = <String, dynamic>{
         'op': 'unsubscribe',
         'requestId': requestId,
       };

From 20f4ed4cadaa825e02642c9df55a5d8aa18f9f18 Mon Sep 17 00:00:00 2001
From: Phill <phill.wiggins@gmail.com>
Date: Sun, 14 Apr 2019 08:52:33 +0100
Subject: [PATCH 23/25] ParseResponse accepts list results and count

---
 lib/src/network/parse_live_query.dart | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/src/network/parse_live_query.dart b/lib/src/network/parse_live_query.dart
index 8ce3f9ccf..3663b0a6f 100644
--- a/lib/src/network/parse_live_query.dart
+++ b/lib/src/network/parse_live_query.dart
@@ -103,8 +103,8 @@ class LiveQuery {
           print(
               '$_printConstLiveQuery: Error: ${error.runtimeType.toString()}');
         }
-        return Future.value(
-            handleException(Exception(error), ParseApiRQ.liveQuery, _debug, _className));
+        return Future.value(handleException(
+            Exception(error), ParseApiRQ.liveQuery, _debug, _className));
       });
 
       //The connect message is sent from a client to the LiveQuery server.

From 55aeae211c1179e0e002d5f6efb90cd1ab069776 Mon Sep 17 00:00:00 2001
From: Phill <phill.wiggins@gmail.com>
Date: Sun, 14 Apr 2019 09:00:33 +0100
Subject: [PATCH 24/25] Release 1.0.18 created

---
 CHANGELOG.md                      | 5 ++++-
 README.md                         | 2 +-
 lib/src/base/parse_constants.dart | 2 +-
 pubspec.yaml                      | 2 +-
 4 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45c7ff7bc..b51a698db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
-## 1.0.17
+## 1.0.18
 
+## 1.0.17
+LiveQuery fix 
+Bug fixes
 
 ## 1.0.16
 Bug fixes
diff --git a/README.md b/README.md
index 2320c7f04..e270ab562 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ Want to get involved? Join our Slack channel and help out! (http://flutter-parse
 To install, either add to your pubspec.yaml
 ```yml
 dependencies:  
-    parse_server_sdk: ^1.0.17
+    parse_server_sdk: ^1.0.18
 ```
 or clone this repository and add to your project. As this is an early development with multiple contributors, it is probably best to download/clone and keep updating as an when a new feature is added.
 
diff --git a/lib/src/base/parse_constants.dart b/lib/src/base/parse_constants.dart
index b746b5569..530ab6dd6 100644
--- a/lib/src/base/parse_constants.dart
+++ b/lib/src/base/parse_constants.dart
@@ -1,7 +1,7 @@
 part of flutter_parse_sdk;
 
 // Library
-const String keySdkVersion = '1.0.17';
+const String keySdkVersion = '1.0.18';
 const String keyLibraryName = 'Flutter Parse SDK';
 
 // End Points
diff --git a/pubspec.yaml b/pubspec.yaml
index af7d53b12..c084f0a07 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
 name: parse_server_sdk
 description: Flutter plugin for Parse Server, (https://parseplatform.org), (https://back4app.com)
-version: 1.0.16
+version: 1.0.18
 homepage: https://github.com/phillwiggins/flutter_parse_sdk
 author: PhillWiggins <phill.wiggins@gmail.com>
 

From 9ef1217f0a81ac2129914e0a90519eb2d5af8d03 Mon Sep 17 00:00:00 2001
From: Phill <phill.wiggins@gmail.com>
Date: Sun, 14 Apr 2019 14:37:25 +0100
Subject: [PATCH 25/25] save fix

---
 lib/src/objects/parse_object.dart | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/src/objects/parse_object.dart b/lib/src/objects/parse_object.dart
index 80f3ac00f..74d0b1936 100644
--- a/lib/src/objects/parse_object.dart
+++ b/lib/src/objects/parse_object.dart
@@ -89,7 +89,7 @@ class ParseObject extends ParseBase implements ParseCloneable {
     } else {
       try {
         final Uri url = getSanitisedUri(_client, '$_path/$objectId');
-        final String body = json.encode(toJson(forApiRQ: true));
+        final String body = json.encode(toJson());
         final Response result = await _client.put(url, body: body);
         return handleResponse<ParseObject>(
             this, result, ParseApiRQ.save, _debug, className);