├── .gitignore ├── example ├── ios │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ └── project.pbxproj │ ├── .gitignore │ └── Podfile ├── android │ ├── gradle.properties │ ├── .gitignore │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ └── values │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── app │ │ │ │ │ │ └── example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ └── build.gradle ├── .metadata ├── lib │ ├── myModel.dart │ ├── main.dart │ └── myMobync.dart ├── README.md ├── .gitignore ├── test │ └── widget_test.dart ├── pubspec.yaml └── pubspec.lock ├── CHANGELOG.md ├── lib ├── constants │ ├── constants.dart │ ├── filter_types.dart │ └── sync_diff_types.dart ├── models │ ├── models.dart │ ├── sync_metadata_model.dart │ ├── read_filter.dart │ ├── response_model.dart │ ├── server_sync_response.dart │ └── sync_diff_model.dart └── mobync.dart ├── .travis.yml ├── test ├── client_logical_clock_test.dart ├── local_server_mockup.dart ├── client_implementation.dart ├── client_component_test.dart └── client_integration_test.dart ├── mobync.iml ├── LICENSE ├── pubspec.yaml ├── pubspec.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .dart_tool/ 2 | .idea/ 3 | .packages 4 | build/ 5 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.0.1] - TODO: Add release date. 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /lib/constants/constants.dart: -------------------------------------------------------------------------------- 1 | export 'filter_types.dart'; 2 | export 'sync_diff_types.dart'; 3 | -------------------------------------------------------------------------------- /lib/constants/filter_types.dart: -------------------------------------------------------------------------------- 1 | enum FilterType { major, minor, majorOrEqual, minorOrEqual, inside } 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /lib/models/models.dart: -------------------------------------------------------------------------------- 1 | export 'sync_metadata_model.dart'; 2 | export 'read_filter.dart'; 3 | export 'response_model.dart'; 4 | export 'server_sync_response.dart'; 5 | export 'sync_diff_model.dart'; 6 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/app/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.app.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobync/flutter-client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | dist: xenial 3 | addons: 4 | apt: 5 | packages: 6 | - lib32stdc++6 7 | install: 8 | - git clone https://github.com/flutter/flutter.git -b stable 9 | - ./flutter/bin/flutter doctor 10 | script: 11 | - ./flutter/bin/flutter test 12 | cache: 13 | directories: 14 | - $HOME/.pub-cache -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 2738a1148ba6c9a6114df62358109407c3ef2553 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /lib/models/sync_metadata_model.dart: -------------------------------------------------------------------------------- 1 | class SyncMetaData { 2 | SyncMetaData({this.logicalClock}) : assert(logicalClock >= 0); 3 | 4 | static final String tableName = 'SyncMetaDataTable'; 5 | static final id = '0'; 6 | int logicalClock; 7 | 8 | @override 9 | String toString() { 10 | return 'MetaSync: {\n\tclock: $logicalClock\n}'; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/lib/myModel.dart: -------------------------------------------------------------------------------- 1 | class MyModel { 2 | MyModel({ 3 | this.id, 4 | this.field1, 5 | }); 6 | 7 | static final String tableName = 'MyModel'; 8 | String id, field1; 9 | 10 | MyModel.fromMap(Map map) { 11 | id = map['id']; 12 | field1 = map['field1']; 13 | } 14 | 15 | Map toMap() { 16 | return { 17 | 'id': id, 18 | 'field1': field1, 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/constants/sync_diff_types.dart: -------------------------------------------------------------------------------- 1 | enum SyncDiffType { create, update, delete } 2 | 3 | const Map SyncDiffTypesMap = { 4 | SyncDiffType.create: 'create', 5 | SyncDiffType.update: 'update', 6 | SyncDiffType.delete: 'delete', 7 | }; 8 | 9 | const Map SyncDiffTypesReversedMap = { 10 | 'create': SyncDiffType.create, 11 | 'update': SyncDiffType.update, 12 | 'delete': SyncDiffType.delete, 13 | }; 14 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/client_logical_clock_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'client_implementation.dart'; 4 | 5 | void main() { 6 | MyMobyncClient client; 7 | 8 | setUpAll(() async { 9 | client = MyMobyncClient(); 10 | }); 11 | 12 | test('test logical clock get and set', () async { 13 | int logicalClock1 = await client.getLogicalClock(); 14 | expect(logicalClock1, 0); 15 | 16 | await client.setLogicalClock(10); 17 | 18 | int logicalClock2 = await client.getLogicalClock(); 19 | expect(logicalClock2, 10); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | Mobync Example using SQLite. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /lib/models/read_filter.dart: -------------------------------------------------------------------------------- 1 | import 'package:mobync/constants/constants.dart'; 2 | 3 | /// Typed filters to be given as parameters to the read function. 4 | class ReadFilter { 5 | ReadFilter(this.fieldName, this.filterBy, this.data) 6 | : assert(fieldName != null && filterBy != null && data != null); 7 | 8 | /// Field that will be used to filter out some element. 9 | final String fieldName; 10 | 11 | /// Criteria that will be used to compare the field from the element and the 12 | /// provided data. 13 | final FilterType filterBy; 14 | 15 | /// Data data will be used to make the comparision. 16 | final data; 17 | } 18 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | include ':app' 6 | 7 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 8 | def properties = new Properties() 9 | 10 | assert localPropertiesFile.exists() 11 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 12 | 13 | def flutterSdkPath = properties.getProperty("flutter.sdk") 14 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 15 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 16 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /lib/models/response_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | 5 | /// Typed response to be returned when performing Mobync operations on the Flutter app. 6 | /// The [success] flag can not not be null. 7 | class MobyncResponse { 8 | MobyncResponse({ 9 | @required this.success, 10 | this.message, 11 | this.data, 12 | }) : assert(success != null); 13 | 14 | /// Flag to indicate if the operation succeeded. 15 | final bool success; 16 | 17 | /// Message in case it has failed. 18 | final String message; 19 | 20 | /// Data in case it succeeded and might return data. 21 | final List data; 22 | 23 | @override 24 | String toString() { 25 | String msgJsonString = json.encode(data); 26 | return '$success $message $msgJsonString'; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Exceptions to above rules. 44 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 45 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/models/server_sync_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:mobync/models/models.dart'; 2 | 3 | /// Typed response to be returned when performing Mobync operations on the Flutter app. 4 | /// The [success] flag can not not be null. 5 | class ServerSyncResponse { 6 | ServerSyncResponse({ 7 | this.success, 8 | this.message, 9 | this.logicalClock, 10 | this.diffs, 11 | }) : assert((success == false) || 12 | (success == true && logicalClock >= 0 && diffs is List)); 13 | 14 | /// Flag to indicate if the operation succeeded. 15 | final bool success; 16 | 17 | /// Message in case it has failed. 18 | final String message; 19 | 20 | /// Logical clock from upstream. 21 | final int logicalClock; 22 | 23 | /// Diffs from upstream to be executed on the local storage. 24 | final List diffs; 25 | 26 | @override 27 | String toString() { 28 | return 'ServerSyncResponse: {logicalClock: $logicalClock, diffs: $diffs, success: $success, message: $message}'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mobync.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Igor Bragaia and Carlos Matheus Barros da Silva 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /test/local_server_mockup.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:mobync/models/models.dart'; 3 | 4 | class ServerMockup { 5 | ServerMockup._privateConstructor(); 6 | static final ServerMockup instance = ServerMockup._privateConstructor(); 7 | 8 | List serverDiffs = []; 9 | int serverLogicalClock = 0; 10 | 11 | void mergeDiffs( 12 | int userLogicalClock, 13 | List userDiffs, 14 | ) { 15 | if (userDiffs.length > 0) { 16 | userDiffs.forEach((e) { 17 | e.logicalClock = serverLogicalClock; 18 | serverDiffs.add(e); 19 | }); 20 | serverLogicalClock = max(serverLogicalClock, userLogicalClock) + 1; 21 | } 22 | } 23 | 24 | Future syncEndpoint( 25 | int userLogicalClock, 26 | List userDiffs, 27 | ) { 28 | var diffs = 29 | serverDiffs.where((e) => e.logicalClock >= userLogicalClock).toList(); 30 | 31 | mergeDiffs(userLogicalClock, userDiffs ?? []); 32 | 33 | return Future.value(ServerSyncResponse( 34 | success: true, 35 | logicalClock: serverLogicalClock, 36 | diffs: diffs, 37 | )); 38 | } 39 | 40 | void reset() { 41 | serverDiffs = []; 42 | serverLogicalClock = 0; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: mobync 2 | description: Mobync is a protocol that allows mobile applications running on distributed clients to get synced to a single source of truth. 3 | version: 0.0.8 4 | homepage: https://github.com/mobync/flutter-client 5 | 6 | environment: 7 | sdk: ">=2.7.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | equatable: ^1.2.5 14 | uuid: ^2.0.2 15 | http: ^0.12.2 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | 21 | equatable: ^1.2.5 22 | uuid: ^2.0.2 23 | http: ^0.12.2 24 | 25 | 26 | # For information on the generic Dart part of this file, see the 27 | # following page: https://dart.dev/tools/pub/pubspec 28 | 29 | # The following section is specific to Flutter. 30 | flutter: 31 | 32 | # To add assets to your package, add an assets section, like this: 33 | # assets: 34 | # - images/a_dot_burr.jpeg 35 | # - images/a_dot_ham.jpeg 36 | # 37 | # For details regarding assets in packages, see 38 | # https://flutter.dev/assets-and-images/#from-packages 39 | # 40 | # An image asset can refer to one or more resolution-specific "variants", see 41 | # https://flutter.dev/assets-and-images/#resolution-aware. 42 | 43 | # To add custom fonts to your package, add a fonts section here, 44 | # in this "flutter" section. Each entry in this list should have a 45 | # "family" key with the font family name, and a "fonts" key with a 46 | # list giving the asset and other descriptors for the font. For 47 | # example: 48 | # fonts: 49 | # - family: Schyler 50 | # fonts: 51 | # - asset: fonts/Schyler-Regular.ttf 52 | # - asset: fonts/Schyler-Italic.ttf 53 | # style: italic 54 | # - family: Trajan Pro 55 | # fonts: 56 | # - asset: fonts/TrajanPro.ttf 57 | # - asset: fonts/TrajanPro_Bold.ttf 58 | # weight: 700 59 | # 60 | # For details regarding fonts in packages, see 61 | # https://flutter.dev/custom-fonts/#from-packages 62 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.app.example" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/models/sync_diff_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:equatable/equatable.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:mobync/constants/constants.dart'; 6 | 7 | class SyncDiff extends Comparable with EquatableMixin { 8 | SyncDiff({ 9 | this.id, 10 | this.logicalClock, 11 | this.utcTimestamp, 12 | this.type, 13 | this.model, 14 | this.jsonData, 15 | }) : assert(id != null && 16 | logicalClock >= 0 && 17 | utcTimestamp >= 0 && 18 | type != null && 19 | model.length > 0 && 20 | jsonDecode(jsonData) is Map); 21 | 22 | static final String tableName = 'MobyncSyncDiffsTable'; 23 | String id, model, jsonData; 24 | SyncDiffType type; 25 | int logicalClock, utcTimestamp; 26 | 27 | @override 28 | List get props => 29 | [id, logicalClock, utcTimestamp, type, model, jsonData]; 30 | 31 | /// Comparision criteria. 32 | @override 33 | int compareTo(other) { 34 | if (this.logicalClock < other.logicalClock) 35 | return -1; 36 | else if (this.logicalClock > other.logicalClock) 37 | return 1; 38 | else { 39 | if (this.utcTimestamp < other.utcTimestamp) 40 | return -1; 41 | else if (this.utcTimestamp > other.utcTimestamp) 42 | return 1; 43 | else 44 | return 0; 45 | } 46 | } 47 | 48 | /// Static from map constructor. 49 | SyncDiff.fromMap(Map map) { 50 | id = map['id']; 51 | logicalClock = map['logicalClock']; 52 | utcTimestamp = map['utcTimestamp']; 53 | type = map['type'] is SyncDiffType 54 | ? map['type'] 55 | : SyncDiffTypesReversedMap[map['type']]; 56 | model = map['model']; 57 | jsonData = map['jsonData']; 58 | } 59 | 60 | /// Static to map parser. 61 | Map toMap({List onlyFields}) { 62 | Map map = { 63 | 'id': id, 64 | 'logicalClock': logicalClock, 65 | 'utcTimestamp': utcTimestamp, 66 | 'type': describeEnum(type), 67 | 'model': model, 68 | 'jsonData': jsonData, 69 | }; 70 | 71 | if (onlyFields != null) { 72 | var keys = map.keys.toList(); 73 | keys.forEach((key) { 74 | if (!onlyFields.contains(key)) map.remove(key); 75 | }); 76 | } 77 | 78 | return map; 79 | } 80 | 81 | @override 82 | String toString() { 83 | return 'SyncDiff: {' 84 | 'id: $id,' 85 | 'logicalClock: $logicalClock,' 86 | 'utcTimestamp: $utcTimestamp,' 87 | 'type: ${describeEnum(type)},' 88 | 'model: $model,' 89 | 'jsonData: $jsonData' 90 | '}'; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /test/client_implementation.dart: -------------------------------------------------------------------------------- 1 | import 'package:mobync/mobync.dart'; 2 | import 'package:mobync/constants/constants.dart'; 3 | import 'package:mobync/models/models.dart'; 4 | 5 | import 'local_server_mockup.dart'; 6 | 7 | class MyMobyncClient extends MobyncClient { 8 | Map db = { 9 | 'model1': [], 10 | SyncMetaData.tableName: [], 11 | SyncDiff.tableName: [], 12 | }; 13 | 14 | @override 15 | Future commitLocalCreate(String model, Map data) { 16 | for (int i = 0; i < db[model].length; i++) 17 | if (db[model][i]['id'] == data['id']) { 18 | throw Exception('Id already exists for $model and $data!'); 19 | } 20 | 21 | db[model].add(data); 22 | 23 | return Future.value(1); 24 | } 25 | 26 | @override 27 | Future commitLocalUpdate(String model, Map data) { 28 | for (int i = 0; i < db[model].length; i++) 29 | if (db[model][i]['id'] == data['id']) { 30 | data.forEach((key, value) { 31 | db[model][i][key] = value; 32 | }); 33 | return Future.value(1); 34 | } 35 | return Future.value(0); 36 | } 37 | 38 | @override 39 | Future commitLocalDelete(String model, String id) { 40 | for (int i = 0; i < db[model].length; i++) 41 | if (db[model][i]['id'] == id) { 42 | db[model].removeAt(i); 43 | return Future.value(1); 44 | } 45 | return Future.value(0); 46 | } 47 | 48 | @override 49 | Future> executeLocalRead(String model, {List filters}) { 50 | List filteredData = []; 51 | for (Map v in db[model]) { 52 | bool accepted = filters == null; 53 | if (filters != null) 54 | filters.forEach((filter) { 55 | switch (filter.filterBy) { 56 | case FilterType.inside: 57 | accepted = filter.data.contains(v[filter.fieldName]); 58 | break; 59 | case FilterType.major: 60 | accepted = v[filter.fieldName] > filter.data; 61 | break; 62 | case FilterType.majorOrEqual: 63 | accepted = v[filter.fieldName] >= filter.data; 64 | break; 65 | case FilterType.minor: 66 | accepted = v[filter.fieldName] < filter.data; 67 | break; 68 | case FilterType.minorOrEqual: 69 | accepted = v[filter.fieldName] <= filter.data; 70 | break; 71 | default: 72 | break; 73 | } 74 | }); 75 | if (accepted) filteredData.add(v); 76 | } 77 | 78 | filteredData.sort((a, b) => (a['id'] as String).compareTo(b['id'])); 79 | 80 | return Future.value(filteredData); 81 | } 82 | 83 | @override 84 | Future getAuthToken() { 85 | return Future.value('asdf'); 86 | } 87 | 88 | String get syncEndpoint => "http://127.0.0.1:5000/sync"; 89 | 90 | @override 91 | Future postSyncEndpoint( 92 | int logicalClock, List localDiffs, String authToken) async { 93 | ServerMockup instance = ServerMockup.instance; 94 | ServerSyncResponse res = 95 | await instance.syncEndpoint(logicalClock, localDiffs); 96 | return Future.value(res); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: Mobync Example using SQLite. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.7.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | 28 | # The following adds the Cupertino Icons font to your application. 29 | # Use with the CupertinoIcons class for iOS style icons. 30 | cupertino_icons: ^0.1.3 31 | mobync: 32 | path: ../ 33 | uuid: ^2.2.2 34 | sqflite: ^1.1.7 35 | http: ^0.12.2 36 | 37 | dev_dependencies: 38 | flutter_test: 39 | sdk: flutter 40 | 41 | # For information on the generic Dart part of this file, see the 42 | # following page: https://dart.dev/tools/pub/pubspec 43 | 44 | # The following section is specific to Flutter. 45 | flutter: 46 | 47 | # The following line ensures that the Material Icons font is 48 | # included with your application, so that you can use the icons in 49 | # the material Icons class. 50 | uses-material-design: true 51 | 52 | # To add assets to your application, add an assets section, like this: 53 | # assets: 54 | # - images/a_dot_burr.jpeg 55 | # - images/a_dot_ham.jpeg 56 | 57 | # An image asset can refer to one or more resolution-specific "variants", see 58 | # https://flutter.dev/assets-and-images/#resolution-aware. 59 | 60 | # For details regarding adding assets from package dependencies, see 61 | # https://flutter.dev/assets-and-images/#from-packages 62 | 63 | # To add custom fonts to your application, add a fonts section here, 64 | # in this "flutter" section. Each entry in this list should have a 65 | # "family" key with the font family name, and a "fonts" key with a 66 | # list giving the asset and other descriptors for the font. For 67 | # example: 68 | # fonts: 69 | # - family: Schyler 70 | # fonts: 71 | # - asset: fonts/Schyler-Regular.ttf 72 | # - asset: fonts/Schyler-Italic.ttf 73 | # style: italic 74 | # - family: Trajan Pro 75 | # fonts: 76 | # - asset: fonts/TrajanPro.ttf 77 | # - asset: fonts/TrajanPro_Bold.ttf 78 | # weight: 700 79 | # 80 | # For details regarding fonts from package dependencies, 81 | # see https://flutter.dev/custom-fonts/#from-packages 82 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | use_modular_headers! 38 | 39 | # Flutter Pod 40 | 41 | copied_flutter_dir = File.join(__dir__, 'Flutter') 42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 48 | 49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 50 | unless File.exist?(generated_xcode_build_settings_path) 51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 52 | end 53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 55 | 56 | unless File.exist?(copied_framework_path) 57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 58 | end 59 | unless File.exist?(copied_podspec_path) 60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 61 | end 62 | end 63 | 64 | # Keep pod path relative so it can be checked into Podfile.lock. 65 | pod 'Flutter', :path => 'Flutter' 66 | 67 | # Plugin Pods 68 | 69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 70 | # referring to absolute paths on developers' machines. 71 | system('rm -rf .symlinks') 72 | system('mkdir -p .symlinks/plugins') 73 | plugin_pods = parse_KV_file('../.flutter-plugins') 74 | plugin_pods.each do |name, path| 75 | symlink = File.join('.symlinks', 'plugins', name) 76 | File.symlink(path, symlink) 77 | pod name, :path => File.join(symlink, 'ios') 78 | end 79 | end 80 | 81 | post_install do |installer| 82 | installer.pods_project.targets.each do |target| 83 | target.build_configurations.each do |config| 84 | config.build_settings['ENABLE_BITCODE'] = 'NO' 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:mobync/models/models.dart'; 3 | import 'package:example/myMobync.dart'; 4 | import 'package:example/myModel.dart'; 5 | import 'package:uuid/uuid.dart'; 6 | 7 | void main() { 8 | runApp(MyApp()); 9 | } 10 | 11 | class MyApp extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | return MaterialApp( 15 | title: 'Mobync Demo using SQLite', 16 | theme: ThemeData( 17 | primarySwatch: Colors.blue, 18 | visualDensity: VisualDensity.adaptivePlatformDensity, 19 | ), 20 | home: MyHomePage(), 21 | ); 22 | } 23 | } 24 | 25 | class MyHomePage extends StatefulWidget { 26 | MyHomePage({Key key}) : super(key: key); 27 | 28 | @override 29 | _MyHomePageState createState() => _MyHomePageState(); 30 | } 31 | 32 | class _MyHomePageState extends State { 33 | List data = []; 34 | 35 | Future addRandomElement() async { 36 | MyMobyncClient mobync = MyMobyncClient.instance; 37 | MobyncResponse res = await mobync.create( 38 | MyModel.tableName, 39 | MyModel( 40 | id: Uuid().v1(), 41 | field1: 'DEVICE2_${DateTime.now().toIso8601String()}') 42 | .toMap(), 43 | ); 44 | if (res.success) 45 | await getData(); 46 | else 47 | print('Create failed.'); 48 | } 49 | 50 | Future getData() async { 51 | MyMobyncClient mobync = MyMobyncClient.instance; 52 | MobyncResponse res = await mobync.read(MyModel.tableName); 53 | if (res.success) { 54 | setState(() { 55 | data = res.data.map((el) => MyModel.fromMap(el)).toList(); 56 | }); 57 | } 58 | } 59 | 60 | void sync() async { 61 | MyMobyncClient mobync = MyMobyncClient.instance; 62 | await mobync.synchronize(); 63 | await getData(); 64 | } 65 | 66 | Widget buildList() { 67 | List list = []; 68 | for (int i = 0; i < data.length; i++) 69 | list.add(Container( 70 | color: Colors.white, 71 | margin: EdgeInsets.all(5), 72 | child: ListTile( 73 | leading: Text( 74 | '$i', 75 | style: TextStyle( 76 | fontWeight: FontWeight.bold, 77 | fontSize: 20, 78 | ), 79 | ), 80 | title: Text( 81 | 'id: ${data[i].id}', 82 | style: TextStyle( 83 | fontWeight: FontWeight.bold, 84 | fontSize: 14, 85 | ), 86 | ), 87 | subtitle: Text( 88 | 'field1: ${data[i].field1}', 89 | style: TextStyle( 90 | fontWeight: FontWeight.bold, 91 | fontSize: 12, 92 | ), 93 | ), 94 | ), 95 | )); 96 | 97 | return ListView(children: list); 98 | } 99 | 100 | @override 101 | Widget build(BuildContext context) { 102 | return Scaffold( 103 | appBar: AppBar( 104 | title: Text('Mobync Demo using SQLite'), 105 | ), 106 | body: Container( 107 | color: Colors.black.withOpacity(0.05), 108 | child: Center( 109 | child: buildList(), 110 | ), 111 | ), 112 | persistentFooterButtons: [ 113 | FloatingActionButton( 114 | onPressed: addRandomElement, 115 | tooltip: 'Add', 116 | child: Icon(Icons.add), 117 | ), // 118 | FloatingActionButton( 119 | onPressed: sync, 120 | tooltip: 'Sync', 121 | child: Icon(Icons.sync), 122 | ), // 123 | ], 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /example/lib/myMobync.dart: -------------------------------------------------------------------------------- 1 | import 'package:mobync/mobync.dart'; 2 | import 'package:mobync/constants/constants.dart'; 3 | import 'package:mobync/models/models.dart'; 4 | import 'package:sqflite/sqflite.dart'; 5 | import 'package:example/myModel.dart'; 6 | 7 | class MyMobyncClient extends MobyncClient { 8 | MyMobyncClient._privateConstructor(); 9 | static final MyMobyncClient instance = MyMobyncClient._privateConstructor(); 10 | 11 | String get syncEndpoint => 'http://127.0.0.1:5000/sync'; 12 | 13 | Database _database; 14 | Future get database async { 15 | if (_database == null) { 16 | var databasesPath = await getDatabasesPath(); 17 | String path = '$databasesPath/demo.db'; 18 | _database = await openDatabase(path, version: 1, onCreate: _onCreate); 19 | } 20 | 21 | return _database; 22 | } 23 | 24 | Future _onCreate(Database db, int version) async { 25 | await db.execute(''' 26 | CREATE TABLE ${MyModel.tableName} ( 27 | id TEXT PRIMARY KEY, 28 | field1 TEXT 29 | )'''); 30 | await db.execute(''' 31 | CREATE TABLE ${SyncDiff.tableName} ( 32 | id TEXT PRIMARY KEY, 33 | logicalClock INTEGER, 34 | utcTimestamp INTEGER, 35 | type TEXT, 36 | model TEXT, 37 | jsonData TEXT 38 | )'''); 39 | await db.execute(''' 40 | CREATE TABLE ${SyncMetaData.tableName} ( 41 | id TEXT PRIMARY KEY, 42 | logicalClock INTEGER 43 | )'''); 44 | } 45 | 46 | @override 47 | Future commitLocalCreate(String model, Map data) async { 48 | Database db = await database; 49 | return await db.insert(model, data); 50 | } 51 | 52 | @override 53 | Future commitLocalUpdate(String model, Map data) async { 54 | Database db = await database; 55 | return await db.update(model, data, where: 'id=?', whereArgs: [data['id']]); 56 | } 57 | 58 | @override 59 | Future commitLocalDelete(String model, String id) async { 60 | Database db = await database; 61 | return await db.delete(model, where: 'id=?', whereArgs: [id]); 62 | } 63 | 64 | @override 65 | Future> executeLocalRead(String model, 66 | {List filters}) async { 67 | Database db = await database; 68 | List data = await db.query(model); 69 | List filteredData = []; 70 | for (Map v in data) { 71 | bool accepted = filters == null; 72 | if (filters != null) 73 | filters.forEach((filter) { 74 | switch (filter.filterBy) { 75 | case FilterType.inside: 76 | accepted = filter.data.contains(v[filter.fieldName]); 77 | break; 78 | case FilterType.major: 79 | accepted = v[filter.fieldName] > filter.data; 80 | break; 81 | case FilterType.majorOrEqual: 82 | accepted = v[filter.fieldName] >= filter.data; 83 | break; 84 | case FilterType.minor: 85 | accepted = v[filter.fieldName] < filter.data; 86 | break; 87 | case FilterType.minorOrEqual: 88 | accepted = v[filter.fieldName] <= filter.data; 89 | break; 90 | default: 91 | break; 92 | } 93 | }); 94 | if (accepted) filteredData.add(v); 95 | } 96 | 97 | filteredData.sort((a, b) => (a['id'] as String).compareTo(b['id'])); 98 | 99 | return Future.value(filteredData); 100 | } 101 | 102 | @override 103 | Future getAuthToken() { 104 | return Future.value('asdf'); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /test/client_component_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mobync/models/models.dart'; 3 | 4 | import 'client_implementation.dart'; 5 | import 'local_server_mockup.dart'; 6 | 7 | void main() { 8 | MyMobyncClient client1, client2; 9 | 10 | setUpAll(() async { 11 | client1 = MyMobyncClient(); 12 | client2 = MyMobyncClient(); 13 | ServerMockup.instance.reset(); 14 | }); 15 | 16 | test('client 1 and 2 sanity check', () async { 17 | int logicalClock1 = await client1.getLogicalClock(); 18 | expect(logicalClock1, 0); 19 | 20 | int logicalClock2 = await client2.getLogicalClock(); 21 | expect(logicalClock2, 0); 22 | 23 | MobyncResponse res1 = await client1.read('model1'); 24 | expect(res1.success, true); 25 | expect(res1.data, []); 26 | 27 | MobyncResponse res2 = await client2.read('model1'); 28 | expect(res2.success, true); 29 | expect(res2.data, []); 30 | }); 31 | 32 | test('Client 1 perform multiple operations and creates its local sync diffs', 33 | () async { 34 | final obj1 = {'id': 'uuid1', 'field1': 'abc'}; 35 | MobyncResponse res1 = await client1.create('model1', obj1); 36 | expect(res1.success, true); 37 | 38 | final obj2 = {'id': 'uuid1', 'field1': 'cde'}; 39 | MobyncResponse res2 = await client1.create('model1', obj2); 40 | expect(res2.success, false); 41 | 42 | final obj3 = {'id': 'uuid2', 'field1': 'fgh'}; 43 | MobyncResponse res3 = await client1.create('model1', obj3); 44 | expect(res3.success, true); 45 | 46 | final obj4 = {'id': 'uuid1', 'field1': 'xxx'}; 47 | MobyncResponse res4 = await client1.update('model1', obj4); 48 | expect(res4.success, true); 49 | 50 | MobyncResponse res5 = await client1.read('model1'); 51 | expect(res5.success, true); 52 | expect(res5.data, [ 53 | {'id': 'uuid1', 'field1': 'xxx'}, 54 | {'id': 'uuid2', 'field1': 'fgh'} 55 | ]); 56 | 57 | MobyncResponse res6 = await client1.delete('model1', 'uuid2'); 58 | expect(res6.success, true); 59 | 60 | MobyncResponse res7 = await client1.read('model1'); 61 | expect(res7.success, true); 62 | expect(res7.data, [ 63 | {'id': 'uuid1', 'field1': 'xxx'} 64 | ]); 65 | 66 | List res8 = await client1.getSyncDiffs(); 67 | expect( 68 | res8 69 | .map((e) => e 70 | .toMap( 71 | onlyFields: ['logicalClock', 'type', 'model', 'jsonData']) 72 | .values 73 | .toList()) 74 | .toList(), 75 | [ 76 | [0, 'create', 'model1', '{"id":"uuid1","field1":"abc"}'], 77 | [0, 'create', 'model1', '{"id":"uuid2","field1":"fgh"}'], 78 | [0, 'update', 'model1', '{"id":"uuid1","field1":"xxx"}'], 79 | [0, 'delete', 'model1', '{"id":"uuid2"}'] 80 | ]); 81 | }); 82 | 83 | test('Both clients sync', () async { 84 | await client1.synchronize(); 85 | await client2.synchronize(); 86 | 87 | MobyncResponse res1 = await client1.read('model1'); 88 | expect(res1.success, true); 89 | expect(res1.data, [ 90 | {'id': 'uuid1', 'field1': 'xxx'} 91 | ]); 92 | 93 | MobyncResponse res2 = await client2.read('model1'); 94 | expect(res2.success, true); 95 | expect(res2.data, [ 96 | {'id': 'uuid1', 'field1': 'xxx'} 97 | ]); 98 | }); 99 | 100 | test('Client 2 delete object and both clients sync', () async { 101 | MobyncResponse res1 = await client2.delete('model1', 'uuid1'); 102 | expect(res1.success, true); 103 | 104 | await client2.synchronize(); 105 | await client1.synchronize(); 106 | 107 | MobyncResponse res2 = await client1.read('model1'); 108 | expect(res2.success, true); 109 | expect(res2.data, []); 110 | 111 | MobyncResponse res3 = await client2.read('model1'); 112 | expect(res3.success, true); 113 | expect(res3.data, []); 114 | }); 115 | 116 | test('Check logical clocks', () async { 117 | int logicalClock1 = await client1.getLogicalClock(); 118 | expect(logicalClock1, 2); 119 | 120 | int logicalClock2 = await client2.getLogicalClock(); 121 | expect(logicalClock2, 2); 122 | }); 123 | } 124 | -------------------------------------------------------------------------------- /test/client_integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mobync/models/models.dart'; 3 | 4 | import 'client_implementation.dart'; 5 | import 'local_server_mockup.dart'; 6 | 7 | void main() { 8 | MyMobyncClient client1, client2; 9 | 10 | setUpAll(() async { 11 | client1 = MyMobyncClient(); 12 | client2 = MyMobyncClient(); 13 | ServerMockup.instance.reset(); 14 | }); 15 | 16 | test('client 1 and 2 sanity check', () async { 17 | int logicalClock1 = await client1.getLogicalClock(); 18 | expect(logicalClock1, 0); 19 | 20 | int logicalClock2 = await client2.getLogicalClock(); 21 | expect(logicalClock2, 0); 22 | 23 | MobyncResponse res1 = await client1.read('model1'); 24 | expect(res1.success, true); 25 | expect(res1.data, []); 26 | 27 | MobyncResponse res2 = await client2.read('model1'); 28 | expect(res2.success, true); 29 | expect(res2.data, []); 30 | }); 31 | 32 | test('client 1 creates local object and synchronizes', () async { 33 | final obj1 = {'id': 'uuid1', 'field1': 'a'}; 34 | MobyncResponse res1 = await client1.create('model1', obj1); 35 | expect(res1.success, true); 36 | 37 | await client1.synchronize(); 38 | 39 | int logicalClock1 = await client1.getLogicalClock(); 40 | expect(logicalClock1, 1); 41 | 42 | MobyncResponse res = await client1.read('model1'); 43 | expect(res.success, true); 44 | expect(res.data, [ 45 | {'id': 'uuid1', 'field1': 'a'}, 46 | ]); 47 | }); 48 | 49 | test('client 1 and 2 create local objects but neither synchronize', () async { 50 | final obj1 = {'id': 'uuid2', 'field1': 'b'}; 51 | MobyncResponse res1 = await client1.create('model1', obj1); 52 | expect(res1.success, true); 53 | 54 | final obj2 = {'id': 'uuid3', 'field1': 'c'}; 55 | MobyncResponse res2 = await client2.create('model1', obj2); 56 | expect(res2.success, true); 57 | }); 58 | 59 | test('client 2 synchonizes', () async { 60 | await client2.synchronize(); 61 | 62 | MobyncResponse res = await client2.read('model1'); 63 | expect(res.success, true); 64 | expect(res.data, [ 65 | {'id': 'uuid1', 'field1': 'a'}, 66 | {'id': 'uuid3', 'field1': 'c'}, 67 | ]); 68 | 69 | int logicalClock2 = await client2.getLogicalClock(); 70 | expect(logicalClock2, 2); 71 | }); 72 | 73 | test('client 1 synchronizes', () async { 74 | await client1.synchronize(); 75 | 76 | MobyncResponse res = await client1.read('model1'); 77 | expect(res.success, true); 78 | expect(res.data, [ 79 | {'id': 'uuid1', 'field1': 'a'}, 80 | {'id': 'uuid2', 'field1': 'b'}, 81 | {'id': 'uuid3', 'field1': 'c'}, 82 | ]); 83 | 84 | int logicalClock1 = await client1.getLogicalClock(); 85 | expect(logicalClock1, 3); 86 | }); 87 | 88 | test('client 2 synchronizes', () async { 89 | await client2.synchronize(); 90 | 91 | MobyncResponse res = await client2.read('model1'); 92 | expect(res.success, true); 93 | expect(res.data, [ 94 | {'id': 'uuid1', 'field1': 'a'}, 95 | {'id': 'uuid2', 'field1': 'b'}, 96 | {'id': 'uuid3', 'field1': 'c'}, 97 | ]); 98 | 99 | int logicalClock2 = await client2.getLogicalClock(); 100 | expect(logicalClock2, 3); 101 | }); 102 | 103 | test('client 2 updates client 1 object and both synchronizes', () async { 104 | final obj = {'id': 'uuid1', 'field1': 'x'}; 105 | MobyncResponse res = await client2.update('model1', obj); 106 | expect(res.success, true); 107 | 108 | await client2.synchronize(); 109 | await client1.synchronize(); 110 | 111 | MobyncResponse res1 = await client1.read('model1'); 112 | expect(res1.success, true); 113 | expect(res1.data, [ 114 | {'id': 'uuid1', 'field1': 'x'}, 115 | {'id': 'uuid2', 'field1': 'b'}, 116 | {'id': 'uuid3', 'field1': 'c'}, 117 | ]); 118 | 119 | int logicalClock1 = await client1.getLogicalClock(); 120 | expect(logicalClock1, 4); 121 | 122 | MobyncResponse res2 = await client2.read('model1'); 123 | expect(res2.success, true); 124 | expect(res2.data, [ 125 | {'id': 'uuid1', 'field1': 'x'}, 126 | {'id': 'uuid2', 'field1': 'b'}, 127 | {'id': 'uuid3', 'field1': 'c'}, 128 | ]); 129 | 130 | int logicalClock2 = await client2.getLogicalClock(); 131 | expect(logicalClock2, 4); 132 | }); 133 | 134 | test('client 1 deletes client 2 object and both synchronizes', () async { 135 | MobyncResponse res = await client1.delete('model1', 'uuid3'); 136 | expect(res.success, true); 137 | 138 | await client1.synchronize(); 139 | await client2.synchronize(); 140 | 141 | MobyncResponse res1 = await client1.read('model1'); 142 | expect(res1.success, true); 143 | expect(res1.data, [ 144 | {'id': 'uuid1', 'field1': 'x'}, 145 | {'id': 'uuid2', 'field1': 'b'}, 146 | ]); 147 | 148 | MobyncResponse res2 = await client2.read('model1'); 149 | expect(res2.success, true); 150 | expect(res2.data, [ 151 | {'id': 'uuid1', 'field1': 'x'}, 152 | {'id': 'uuid2', 'field1': 'b'}, 153 | ]); 154 | }); 155 | } 156 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.13" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.6.0" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.4.1" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.0.0" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.3" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.12" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.4" 60 | equatable: 61 | dependency: "direct main" 62 | description: 63 | name: equatable 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.2.5" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | http: 78 | dependency: "direct main" 79 | description: 80 | name: http 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "0.12.2" 84 | http_parser: 85 | dependency: transitive 86 | description: 87 | name: http_parser 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "3.1.4" 91 | image: 92 | dependency: transitive 93 | description: 94 | name: image 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "2.1.12" 98 | matcher: 99 | dependency: transitive 100 | description: 101 | name: matcher 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "0.12.6" 105 | meta: 106 | dependency: transitive 107 | description: 108 | name: meta 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "1.1.8" 112 | path: 113 | dependency: transitive 114 | description: 115 | name: path 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "1.6.4" 119 | pedantic: 120 | dependency: transitive 121 | description: 122 | name: pedantic 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "1.9.0" 126 | petitparser: 127 | dependency: transitive 128 | description: 129 | name: petitparser 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "2.4.0" 133 | quiver: 134 | dependency: transitive 135 | description: 136 | name: quiver 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "2.1.3" 140 | sky_engine: 141 | dependency: transitive 142 | description: flutter 143 | source: sdk 144 | version: "0.0.99" 145 | source_span: 146 | dependency: transitive 147 | description: 148 | name: source_span 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.7.0" 152 | stack_trace: 153 | dependency: transitive 154 | description: 155 | name: stack_trace 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.9.3" 159 | stream_channel: 160 | dependency: transitive 161 | description: 162 | name: stream_channel 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "2.0.0" 166 | string_scanner: 167 | dependency: transitive 168 | description: 169 | name: string_scanner 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "1.0.5" 173 | term_glyph: 174 | dependency: transitive 175 | description: 176 | name: term_glyph 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "1.1.0" 180 | test_api: 181 | dependency: transitive 182 | description: 183 | name: test_api 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "0.2.15" 187 | typed_data: 188 | dependency: transitive 189 | description: 190 | name: typed_data 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "1.1.6" 194 | uuid: 195 | dependency: "direct main" 196 | description: 197 | name: uuid 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "2.2.2" 201 | vector_math: 202 | dependency: transitive 203 | description: 204 | name: vector_math 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "2.0.8" 208 | xml: 209 | dependency: transitive 210 | description: 211 | name: xml 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "3.6.1" 215 | sdks: 216 | dart: ">=2.7.0 <3.0.0" 217 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.4.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.0.0" 18 | charcode: 19 | dependency: transitive 20 | description: 21 | name: charcode 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.3" 25 | clock: 26 | dependency: transitive 27 | description: 28 | name: clock 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.0.1" 32 | collection: 33 | dependency: transitive 34 | description: 35 | name: collection 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.14.12" 39 | convert: 40 | dependency: transitive 41 | description: 42 | name: convert 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "2.1.1" 46 | crypto: 47 | dependency: transitive 48 | description: 49 | name: crypto 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.5" 53 | cupertino_icons: 54 | dependency: "direct main" 55 | description: 56 | name: cupertino_icons 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "0.1.3" 60 | equatable: 61 | dependency: transitive 62 | description: 63 | name: equatable 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.2.5" 67 | fake_async: 68 | dependency: transitive 69 | description: 70 | name: fake_async 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.1.0" 74 | flutter: 75 | dependency: "direct main" 76 | description: flutter 77 | source: sdk 78 | version: "0.0.0" 79 | flutter_test: 80 | dependency: "direct dev" 81 | description: flutter 82 | source: sdk 83 | version: "0.0.0" 84 | http: 85 | dependency: "direct main" 86 | description: 87 | name: http 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "0.12.2" 91 | http_parser: 92 | dependency: transitive 93 | description: 94 | name: http_parser 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "3.1.4" 98 | matcher: 99 | dependency: transitive 100 | description: 101 | name: matcher 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "0.12.6" 105 | meta: 106 | dependency: transitive 107 | description: 108 | name: meta 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "1.1.8" 112 | mobync: 113 | dependency: "direct main" 114 | description: 115 | path: ".." 116 | relative: true 117 | source: path 118 | version: "0.0.6" 119 | path: 120 | dependency: transitive 121 | description: 122 | name: path 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "1.7.0" 126 | pedantic: 127 | dependency: transitive 128 | description: 129 | name: pedantic 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "1.9.0" 133 | sky_engine: 134 | dependency: transitive 135 | description: flutter 136 | source: sdk 137 | version: "0.0.99" 138 | source_span: 139 | dependency: transitive 140 | description: 141 | name: source_span 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "1.7.0" 145 | sqflite: 146 | dependency: "direct main" 147 | description: 148 | name: sqflite 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.3.1+2" 152 | sqflite_common: 153 | dependency: transitive 154 | description: 155 | name: sqflite_common 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.0.2+1" 159 | stack_trace: 160 | dependency: transitive 161 | description: 162 | name: stack_trace 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "1.9.3" 166 | stream_channel: 167 | dependency: transitive 168 | description: 169 | name: stream_channel 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "2.0.0" 173 | string_scanner: 174 | dependency: transitive 175 | description: 176 | name: string_scanner 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "1.0.5" 180 | synchronized: 181 | dependency: transitive 182 | description: 183 | name: synchronized 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "2.2.0+2" 187 | term_glyph: 188 | dependency: transitive 189 | description: 190 | name: term_glyph 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "1.1.0" 194 | test_api: 195 | dependency: transitive 196 | description: 197 | name: test_api 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "0.2.15" 201 | typed_data: 202 | dependency: transitive 203 | description: 204 | name: typed_data 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "1.1.6" 208 | uuid: 209 | dependency: "direct main" 210 | description: 211 | name: uuid 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "2.2.2" 215 | vector_math: 216 | dependency: transitive 217 | description: 218 | name: vector_math 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "2.0.8" 222 | sdks: 223 | dart: ">=2.8.0 <3.0.0" 224 | flutter: ">=1.10.1 <2.0.0" 225 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 |

5 |

The Mobync Flutter Lib

6 |
7 | 8 |
9 | 10 | [![Build Status](https://travis-ci.com/mobync/flutter-client.svg?token=zEuAJYpGFRGA9Uoccaqu&branch=master)](https://travis-ci.com/mobync/flutter-client) 11 | 12 |
13 | 14 | ## Why use mobync 15 | 16 | Mobync is a synchronization library aimed to facilitate online-offline sync between multiple devices for any frontend, any backend, and any database. 17 | 18 | This repository implements the Mobync client library in Flutter, which means you can start using Mobync sync on your client regardless of which backend you might be using or even which database. 19 | 20 | As Mobync aims to provide online-offline sync between client and server, you will need to use the mobync library both on your frontend application and backend. 21 | 22 | Currently, Mobync has a Dart client implementation and a Python server implementation. That means you can plug Mobync on your Flutter app and provide online-offline synchronization. 23 | 24 | ### Online-offline synchronization 25 | 26 | Online-offline synchronization means that your app will work seamlessly both online and offline, the user can use without noticing any difference, and you can implement your app not worrying about this when using Mobync. 27 | 28 | Mobync will automatically make your app store your changes locally on your app's database when the user has no connection, and automatically sync the data to the backend when the user has internet. 29 | 30 | ### Multiple devices support 31 | 32 | Your user can use your service across multiple devices at the same time and all will have their data synchronized with Mobync. 33 | 34 | Mobync implements a protocol that merges the user data and resolves conflicts. 35 | 36 | Mobync's protocol allows mobile applications running on distributed clients to get synced to a single source of truth to manage users’ data using any storage type. Mobync users Dart and Flutter to implement this protocol and communicate to a web server written in Python. 37 | 38 | ## Example projects 39 | 40 | You can see some example projects using mobync on [Examples](https://github.com/mobync/flutter-client/blob/master/example). 41 | 42 | 43 | ## Mobync Flutter Client Package 44 | 45 | Using Mobync, you will wrap your database operations in such a way that any local data will get synced to a remote server, what will allow users from multiple clients to have an offline-online experience. 46 | 47 | ## Common usage 48 | ```dart 49 | MyMobyncClient client = MyMobyncClient.instance; 50 | 51 | /// Create an instance. 52 | final obj1 = {'id': 'uuid1', 'field1': 'a', 'field2': 'b'}; 53 | MobyncResponse res1 = await client.create('model1', obj1); 54 | 55 | /// Update an instance. 56 | final obj = {'id': 'uuid1', 'field1': 'x'}; 57 | MobyncResponse res = await client.update('model1', obj); 58 | 59 | /// Delete an instance. 60 | MobyncResponse res = await client.delete('model1', 'uuid1'); 61 | 62 | /// Synchronize. 63 | await client.synchronize(); 64 | 65 | /// Read from model. 66 | MobyncResponse res = await client.read('model1'); 67 | /// Access data read. 68 | if(res.success) 69 | print(res.data); 70 | ``` 71 | 72 | You might implement the ```MobyncClient``` abstract class. At this moment we do not support any migration system so it is up to the developer to use one from his preferences. Despite of that, the developer still have to implement the library-specific models. 73 | 74 | On the following snippet you can check out a Mobync implementation using SQLite on the client storage. 75 | 76 | ```dart 77 | import 'package:mobync/mobync.dart'; 78 | import 'package:mobync/constants/constants.dart'; 79 | import 'package:mobync/models/models.dart'; 80 | import 'package:sqflite/sqflite.dart'; 81 | import 'package:example/myModel.dart'; 82 | 83 | class MyMobyncClient extends MobyncClient { 84 | MyMobyncClient._privateConstructor(); 85 | static final MyMobyncClient instance = MyMobyncClient._privateConstructor(); 86 | 87 | String get syncEndpoint => 'http://127.0.0.1:5000/sync'; 88 | 89 | Database _database; 90 | Future get database async { 91 | if (_database == null) { 92 | var databasesPath = await getDatabasesPath(); 93 | String path = '$databasesPath/demo.db'; 94 | _database = await openDatabase(path, version: 1, onCreate: _onCreate); 95 | } 96 | 97 | return _database; 98 | } 99 | 100 | Future _onCreate(Database db, int version) async { 101 | await db.execute(''' 102 | CREATE TABLE ${SyncDiff.tableName} ( 103 | id TEXT PRIMARY KEY, 104 | logicalClock INTEGER, 105 | utcTimestamp INTEGER, 106 | type TEXT, 107 | model TEXT, 108 | jsonData TEXT 109 | )'''); 110 | await db.execute(''' 111 | CREATE TABLE ${SyncMetaData.tableName} ( 112 | id TEXT PRIMARY KEY, 113 | logicalClock INTEGER 114 | )'''); 115 | await _createMyTables(db, version); 116 | } 117 | 118 | Future _createMyTables(Database db, int version) async { 119 | /// ... 120 | } 121 | 122 | @override 123 | Future commitLocalCreate(String model, Map data) async { 124 | Database db = await database; 125 | return await db.insert(model, data); 126 | } 127 | 128 | @override 129 | Future commitLocalUpdate(String model, Map data) async { 130 | Database db = await database; 131 | return await db.update(model, data, where: 'id=?', whereArgs: [data['id']]); 132 | } 133 | 134 | @override 135 | Future commitLocalDelete(String model, String id) async { 136 | Database db = await database; 137 | return await db.delete(model, where: 'id=?', whereArgs: [id]); 138 | } 139 | 140 | @override 141 | Future> executeLocalRead(String model, 142 | {List filters}) async { 143 | Database db = await database; 144 | List data = await db.query(model); 145 | List filteredData = []; 146 | for (Map v in data) { 147 | bool accepted = filters == null; 148 | if (filters != null) 149 | filters.forEach((filter) { 150 | switch (filter.filterBy) { 151 | case FilterType.inside: 152 | accepted = filter.data.contains(v[filter.fieldName]); 153 | break; 154 | case FilterType.major: 155 | accepted = v[filter.fieldName] > filter.data; 156 | break; 157 | case FilterType.majorOrEqual: 158 | accepted = v[filter.fieldName] >= filter.data; 159 | break; 160 | case FilterType.minor: 161 | accepted = v[filter.fieldName] < filter.data; 162 | break; 163 | case FilterType.minorOrEqual: 164 | accepted = v[filter.fieldName] <= filter.data; 165 | break; 166 | default: 167 | break; 168 | } 169 | }); 170 | if (accepted) filteredData.add(v); 171 | } 172 | return Future.value(filteredData); 173 | } 174 | 175 | @override 176 | Future getAuthToken() { 177 | return Future.value('asdf'); 178 | } 179 | } 180 | 181 | ``` 182 | -------------------------------------------------------------------------------- /lib/mobync.dart: -------------------------------------------------------------------------------- 1 | library mobync; 2 | 3 | import 'dart:convert'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:http/http.dart' as http; 6 | import 'package:mobync/constants/constants.dart'; 7 | import 'package:mobync/models/models.dart'; 8 | import 'package:uuid/uuid.dart'; 9 | 10 | /// Abstract class to wrap the local storage operations. 11 | abstract class MobyncClient { 12 | /// You might implement the following functions 13 | /// - [commitLocalCreate] 14 | /// - [commitLocalUpdate] 15 | /// - [commitLocalDelete] 16 | /// - [executeLocalRead] 17 | /// - [getAuthToken] 18 | /// and the getter for 19 | /// - [syncEndpoint] 20 | 21 | /// This function creates some data from a given model into a local storage. 22 | Future commitLocalCreate(String model, Map data); 23 | 24 | /// This function updates some data from a given model into a local storage. 25 | Future commitLocalUpdate(String model, Map data); 26 | 27 | /// This function deletes some data from a given model into a local storage. 28 | Future commitLocalDelete(String model, String id); 29 | 30 | /// This function reads from a local storage all entries from a given model using some filters. 31 | Future> executeLocalRead(String model, {List filters}); 32 | 33 | /// This functions gets the auth token to be used on the synchronization requests. 34 | Future getAuthToken(); 35 | 36 | /// API endpoint to make the synchronization requests. 37 | String get syncEndpoint; 38 | 39 | /// Use this function to perform a create operation that might be synchronized to the remote API. 40 | /// 41 | /// The response is wrapped into a [MobyncResponse] object to avoid bugs. 42 | Future create(String model, Map metadata) async { 43 | try { 44 | await commitLocalCreate(model, metadata); 45 | await commitLocalCreate( 46 | SyncDiff.tableName, 47 | SyncDiff( 48 | id: Uuid().v1(), 49 | logicalClock: await getLogicalClock(), 50 | utcTimestamp: DateTime.now().toUtc().millisecondsSinceEpoch, 51 | type: SyncDiffType.create, 52 | model: model, 53 | jsonData: jsonEncode(metadata), 54 | ).toMap()); 55 | return Future.value(MobyncResponse( 56 | success: true, 57 | message: 'Objected created.', 58 | )); 59 | } catch (e) { 60 | return Future.value(MobyncResponse( 61 | success: false, 62 | message: e.toString(), 63 | )); 64 | } 65 | } 66 | 67 | /// Use this function to perform an update operation that might be synchronized to the remote API. 68 | /// 69 | /// The response is wrapped into a [MobyncResponse] object to avoid bugs. 70 | Future update(String model, Map metadata) async { 71 | try { 72 | await commitLocalUpdate(model, metadata); 73 | await commitLocalCreate( 74 | SyncDiff.tableName, 75 | SyncDiff( 76 | id: Uuid().v1(), 77 | logicalClock: await getLogicalClock(), 78 | utcTimestamp: DateTime.now().toUtc().millisecondsSinceEpoch, 79 | type: SyncDiffType.update, 80 | model: model, 81 | jsonData: jsonEncode(metadata), 82 | ).toMap()); 83 | 84 | return Future.value(MobyncResponse( 85 | success: true, 86 | message: 'Objected updated.', 87 | )); 88 | } catch (e) { 89 | return Future.value(MobyncResponse( 90 | success: false, 91 | message: e.toString(), 92 | )); 93 | } 94 | } 95 | 96 | /// Use this function to perform a delete operation that might be synchronized to the remote API. 97 | /// 98 | /// The response is wrapped into a [MobyncResponse] object to avoid bugs. 99 | Future delete(String model, String id) async { 100 | try { 101 | await commitLocalDelete(model, id); 102 | await commitLocalCreate( 103 | SyncDiff.tableName, 104 | SyncDiff( 105 | id: Uuid().v1(), 106 | logicalClock: await getLogicalClock(), 107 | utcTimestamp: DateTime.now().toUtc().millisecondsSinceEpoch, 108 | type: SyncDiffType.delete, 109 | model: model, 110 | jsonData: jsonEncode({'id': id}), 111 | ).toMap()); 112 | 113 | return Future.value(MobyncResponse( 114 | success: true, 115 | message: 'Objected updated.', 116 | )); 117 | } catch (e) { 118 | return Future.value(MobyncResponse( 119 | success: false, 120 | message: e.toString(), 121 | )); 122 | } 123 | } 124 | 125 | /// Use this function to perform a read on the local storage. 126 | /// 127 | /// The response is wrapped into a [MobyncResponse] object to avoid bugs. 128 | Future read(String model, {List filters}) async { 129 | try { 130 | List filteredData = await executeLocalRead(model, filters: filters); 131 | return Future.value(MobyncResponse( 132 | success: true, 133 | data: filteredData, 134 | )); 135 | } catch (e) { 136 | return Future.value(MobyncResponse( 137 | success: false, 138 | message: e.toString(), 139 | )); 140 | } 141 | } 142 | 143 | /// Use this function to synchronize the local storage to the remote storage. 144 | Future synchronize() async { 145 | int logicalClock = await getLogicalClock(); 146 | List localDiffs = await getSyncDiffs(); 147 | String authToken = await getAuthToken(); 148 | ServerSyncResponse res = await postSyncEndpoint( 149 | logicalClock, 150 | localDiffs, 151 | authToken, 152 | ); 153 | 154 | if (res.success) { 155 | if (res.logicalClock > logicalClock) { 156 | try { 157 | await _executeSyncDiffs(res.diffs); 158 | await setLogicalClock(res.logicalClock); 159 | } catch (e) { 160 | print(e); 161 | return false; 162 | } 163 | } 164 | return true; 165 | } else { 166 | return false; 167 | } 168 | } 169 | 170 | /// This private function execute all a list of diffs received from upstream. 171 | Future _executeSyncDiffs(List diffs) async { 172 | diffs.forEach((el) async { 173 | int res; 174 | Map data = jsonDecode(el.jsonData); 175 | switch (el.type) { 176 | case SyncDiffType.create: 177 | res = await commitLocalCreate(el.model, data); 178 | break; 179 | case SyncDiffType.update: 180 | res = await commitLocalUpdate(el.model, data); 181 | break; 182 | case SyncDiffType.delete: 183 | res = await commitLocalDelete(el.model, data['id']); 184 | break; 185 | default: 186 | throw Exception('Invalid Operation.'); 187 | break; 188 | } 189 | 190 | if (res > 0) await commitLocalCreate(SyncDiff.tableName, el.toMap()); 191 | }); 192 | } 193 | 194 | /// This function makes the request to the synchronization endpoint on the API, 195 | /// parses the response and returns a [ServerSyncResponse] that contains the new 196 | /// logical clock and the new diffs from upstream. 197 | Future postSyncEndpoint( 198 | int logicalClock, List localDiffs, String authToken) async { 199 | try { 200 | String body = jsonEncode({ 201 | 'auth_token': authToken, 202 | 'logical_clock': logicalClock, 203 | 'diffs': localDiffs 204 | .map((e) => { 205 | 'id': e.id, 206 | 'owner': 'asdf', 207 | 'logical_clock': e.logicalClock, 208 | 'utc_timestamp': e.utcTimestamp, 209 | 'type': describeEnum(e.type), 210 | 'model': e.model, 211 | 'json_data': e.jsonData, 212 | }) 213 | .toList(), 214 | }); 215 | 216 | http.Response resp = await http.post(syncEndpoint, 217 | headers: {'Content-Type': 'application/json'}, body: body); 218 | 219 | if (resp.statusCode.toString().startsWith('2')) { 220 | Map res = jsonDecode(resp.body); 221 | List syncDiffs = (res['diffs'] as List) 222 | .map((e) => SyncDiff( 223 | id: e['id'], 224 | jsonData: e['json_data'], 225 | logicalClock: e['logical_clock'], 226 | model: e['model'], 227 | type: SyncDiffTypesReversedMap[e['type']], 228 | utcTimestamp: e['utc_timestamp'])) 229 | .toList(); 230 | syncDiffs.sort(); 231 | 232 | return Future.value(ServerSyncResponse( 233 | success: true, 234 | logicalClock: res['logical_clock'], 235 | diffs: syncDiffs, 236 | )); 237 | } else { 238 | throw Exception('Request failed.'); 239 | } 240 | } catch (e) { 241 | return Future.value(ServerSyncResponse( 242 | success: false, 243 | message: e.toString(), 244 | )); 245 | } 246 | } 247 | 248 | /// This function gets the local diffs that have not been synchronized yet. 249 | Future> getSyncDiffs({logicalClock}) async { 250 | if (logicalClock == null) logicalClock = await getLogicalClock(); 251 | List maps = await executeLocalRead( 252 | SyncDiff.tableName, 253 | filters: [ 254 | ReadFilter('logicalClock', FilterType.majorOrEqual, logicalClock) 255 | ], 256 | ); 257 | List diffs = maps.map((e) => SyncDiff.fromMap(e)).toList(); 258 | diffs.sort(); 259 | return diffs; 260 | } 261 | 262 | /// This function gets the local logical clock. 263 | Future getLogicalClock() async { 264 | int logicalClock = await executeLocalRead( 265 | SyncMetaData.tableName, 266 | filters: [ReadFilter('id', FilterType.inside, SyncMetaData.id)], 267 | ).then((value) { 268 | return value.length > 0 ? value[0]['logicalClock'] : 0; 269 | }); 270 | return Future.value(logicalClock); 271 | } 272 | 273 | /// This function sets the local logical clock. 274 | Future setLogicalClock(int logicalClock) async { 275 | int updatedMetadata = await commitLocalUpdate( 276 | SyncMetaData.tableName, 277 | {'id': SyncMetaData.id, 'logicalClock': logicalClock}, 278 | ); 279 | 280 | if (updatedMetadata <= 0) { 281 | await commitLocalCreate( 282 | SyncMetaData.tableName, 283 | {'id': SyncMetaData.id, 'logicalClock': logicalClock}, 284 | ); 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 97C146F11CF9000F007C117D /* Supporting Files */, 94 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 95 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 96 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 97 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 98 | ); 99 | path = Runner; 100 | sourceTree = ""; 101 | }; 102 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | ); 106 | name = "Supporting Files"; 107 | sourceTree = ""; 108 | }; 109 | /* End PBXGroup section */ 110 | 111 | /* Begin PBXNativeTarget section */ 112 | 97C146ED1CF9000F007C117D /* Runner */ = { 113 | isa = PBXNativeTarget; 114 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 115 | buildPhases = ( 116 | 9740EEB61CF901F6004384FC /* Run Script */, 117 | 97C146EA1CF9000F007C117D /* Sources */, 118 | 97C146EB1CF9000F007C117D /* Frameworks */, 119 | 97C146EC1CF9000F007C117D /* Resources */, 120 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 121 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 122 | ); 123 | buildRules = ( 124 | ); 125 | dependencies = ( 126 | ); 127 | name = Runner; 128 | productName = Runner; 129 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 130 | productType = "com.apple.product-type.application"; 131 | }; 132 | /* End PBXNativeTarget section */ 133 | 134 | /* Begin PBXProject section */ 135 | 97C146E61CF9000F007C117D /* Project object */ = { 136 | isa = PBXProject; 137 | attributes = { 138 | LastUpgradeCheck = 1020; 139 | ORGANIZATIONNAME = ""; 140 | TargetAttributes = { 141 | 97C146ED1CF9000F007C117D = { 142 | CreatedOnToolsVersion = 7.3.1; 143 | LastSwiftMigration = 1100; 144 | }; 145 | }; 146 | }; 147 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 148 | compatibilityVersion = "Xcode 9.3"; 149 | developmentRegion = en; 150 | hasScannedForEncodings = 0; 151 | knownRegions = ( 152 | en, 153 | Base, 154 | ); 155 | mainGroup = 97C146E51CF9000F007C117D; 156 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 157 | projectDirPath = ""; 158 | projectRoot = ""; 159 | targets = ( 160 | 97C146ED1CF9000F007C117D /* Runner */, 161 | ); 162 | }; 163 | /* End PBXProject section */ 164 | 165 | /* Begin PBXResourcesBuildPhase section */ 166 | 97C146EC1CF9000F007C117D /* Resources */ = { 167 | isa = PBXResourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 171 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 172 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 173 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | }; 177 | /* End PBXResourcesBuildPhase section */ 178 | 179 | /* Begin PBXShellScriptBuildPhase section */ 180 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 181 | isa = PBXShellScriptBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | ); 185 | inputPaths = ( 186 | ); 187 | name = "Thin Binary"; 188 | outputPaths = ( 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | shellPath = /bin/sh; 192 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 193 | }; 194 | 9740EEB61CF901F6004384FC /* Run Script */ = { 195 | isa = PBXShellScriptBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | ); 199 | inputPaths = ( 200 | ); 201 | name = "Run Script"; 202 | outputPaths = ( 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | shellPath = /bin/sh; 206 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 207 | }; 208 | /* End PBXShellScriptBuildPhase section */ 209 | 210 | /* Begin PBXSourcesBuildPhase section */ 211 | 97C146EA1CF9000F007C117D /* Sources */ = { 212 | isa = PBXSourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 216 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | /* End PBXSourcesBuildPhase section */ 221 | 222 | /* Begin PBXVariantGroup section */ 223 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C146FB1CF9000F007C117D /* Base */, 227 | ); 228 | name = Main.storyboard; 229 | sourceTree = ""; 230 | }; 231 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 232 | isa = PBXVariantGroup; 233 | children = ( 234 | 97C147001CF9000F007C117D /* Base */, 235 | ); 236 | name = LaunchScreen.storyboard; 237 | sourceTree = ""; 238 | }; 239 | /* End PBXVariantGroup section */ 240 | 241 | /* Begin XCBuildConfiguration section */ 242 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | ALWAYS_SEARCH_USER_PATHS = NO; 246 | CLANG_ANALYZER_NONNULL = YES; 247 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 248 | CLANG_CXX_LIBRARY = "libc++"; 249 | CLANG_ENABLE_MODULES = YES; 250 | CLANG_ENABLE_OBJC_ARC = YES; 251 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 252 | CLANG_WARN_BOOL_CONVERSION = YES; 253 | CLANG_WARN_COMMA = YES; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 256 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 257 | CLANG_WARN_EMPTY_BODY = YES; 258 | CLANG_WARN_ENUM_CONVERSION = YES; 259 | CLANG_WARN_INFINITE_RECURSION = YES; 260 | CLANG_WARN_INT_CONVERSION = YES; 261 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 262 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 263 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 264 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 265 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 266 | CLANG_WARN_STRICT_PROTOTYPES = YES; 267 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 268 | CLANG_WARN_UNREACHABLE_CODE = YES; 269 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 270 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 271 | COPY_PHASE_STRIP = NO; 272 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 273 | ENABLE_NS_ASSERTIONS = NO; 274 | ENABLE_STRICT_OBJC_MSGSEND = YES; 275 | GCC_C_LANGUAGE_STANDARD = gnu99; 276 | GCC_NO_COMMON_BLOCKS = YES; 277 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 278 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 279 | GCC_WARN_UNDECLARED_SELECTOR = YES; 280 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 281 | GCC_WARN_UNUSED_FUNCTION = YES; 282 | GCC_WARN_UNUSED_VARIABLE = YES; 283 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 284 | MTL_ENABLE_DEBUG_INFO = NO; 285 | SDKROOT = iphoneos; 286 | SUPPORTED_PLATFORMS = iphoneos; 287 | TARGETED_DEVICE_FAMILY = "1,2"; 288 | VALIDATE_PRODUCT = YES; 289 | }; 290 | name = Profile; 291 | }; 292 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 293 | isa = XCBuildConfiguration; 294 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 295 | buildSettings = { 296 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 297 | CLANG_ENABLE_MODULES = YES; 298 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 299 | ENABLE_BITCODE = NO; 300 | FRAMEWORK_SEARCH_PATHS = ( 301 | "$(inherited)", 302 | "$(PROJECT_DIR)/Flutter", 303 | ); 304 | INFOPLIST_FILE = Runner/Info.plist; 305 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 306 | LIBRARY_SEARCH_PATHS = ( 307 | "$(inherited)", 308 | "$(PROJECT_DIR)/Flutter", 309 | ); 310 | PRODUCT_BUNDLE_IDENTIFIER = com.app.example; 311 | PRODUCT_NAME = "$(TARGET_NAME)"; 312 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 313 | SWIFT_VERSION = 5.0; 314 | VERSIONING_SYSTEM = "apple-generic"; 315 | }; 316 | name = Profile; 317 | }; 318 | 97C147031CF9000F007C117D /* Debug */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ALWAYS_SEARCH_USER_PATHS = NO; 322 | CLANG_ANALYZER_NONNULL = YES; 323 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 324 | CLANG_CXX_LIBRARY = "libc++"; 325 | CLANG_ENABLE_MODULES = YES; 326 | CLANG_ENABLE_OBJC_ARC = YES; 327 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 328 | CLANG_WARN_BOOL_CONVERSION = YES; 329 | CLANG_WARN_COMMA = YES; 330 | CLANG_WARN_CONSTANT_CONVERSION = YES; 331 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 333 | CLANG_WARN_EMPTY_BODY = YES; 334 | CLANG_WARN_ENUM_CONVERSION = YES; 335 | CLANG_WARN_INFINITE_RECURSION = YES; 336 | CLANG_WARN_INT_CONVERSION = YES; 337 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 338 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 339 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 341 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 342 | CLANG_WARN_STRICT_PROTOTYPES = YES; 343 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 344 | CLANG_WARN_UNREACHABLE_CODE = YES; 345 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 346 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 347 | COPY_PHASE_STRIP = NO; 348 | DEBUG_INFORMATION_FORMAT = dwarf; 349 | ENABLE_STRICT_OBJC_MSGSEND = YES; 350 | ENABLE_TESTABILITY = YES; 351 | GCC_C_LANGUAGE_STANDARD = gnu99; 352 | GCC_DYNAMIC_NO_PIC = NO; 353 | GCC_NO_COMMON_BLOCKS = YES; 354 | GCC_OPTIMIZATION_LEVEL = 0; 355 | GCC_PREPROCESSOR_DEFINITIONS = ( 356 | "DEBUG=1", 357 | "$(inherited)", 358 | ); 359 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 360 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 361 | GCC_WARN_UNDECLARED_SELECTOR = YES; 362 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 363 | GCC_WARN_UNUSED_FUNCTION = YES; 364 | GCC_WARN_UNUSED_VARIABLE = YES; 365 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 366 | MTL_ENABLE_DEBUG_INFO = YES; 367 | ONLY_ACTIVE_ARCH = YES; 368 | SDKROOT = iphoneos; 369 | TARGETED_DEVICE_FAMILY = "1,2"; 370 | }; 371 | name = Debug; 372 | }; 373 | 97C147041CF9000F007C117D /* Release */ = { 374 | isa = XCBuildConfiguration; 375 | buildSettings = { 376 | ALWAYS_SEARCH_USER_PATHS = NO; 377 | CLANG_ANALYZER_NONNULL = YES; 378 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 379 | CLANG_CXX_LIBRARY = "libc++"; 380 | CLANG_ENABLE_MODULES = YES; 381 | CLANG_ENABLE_OBJC_ARC = YES; 382 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 383 | CLANG_WARN_BOOL_CONVERSION = YES; 384 | CLANG_WARN_COMMA = YES; 385 | CLANG_WARN_CONSTANT_CONVERSION = YES; 386 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 387 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 388 | CLANG_WARN_EMPTY_BODY = YES; 389 | CLANG_WARN_ENUM_CONVERSION = YES; 390 | CLANG_WARN_INFINITE_RECURSION = YES; 391 | CLANG_WARN_INT_CONVERSION = YES; 392 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 393 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 394 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 395 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 396 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 397 | CLANG_WARN_STRICT_PROTOTYPES = YES; 398 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 399 | CLANG_WARN_UNREACHABLE_CODE = YES; 400 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 401 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 402 | COPY_PHASE_STRIP = NO; 403 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 404 | ENABLE_NS_ASSERTIONS = NO; 405 | ENABLE_STRICT_OBJC_MSGSEND = YES; 406 | GCC_C_LANGUAGE_STANDARD = gnu99; 407 | GCC_NO_COMMON_BLOCKS = YES; 408 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 409 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 410 | GCC_WARN_UNDECLARED_SELECTOR = YES; 411 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 412 | GCC_WARN_UNUSED_FUNCTION = YES; 413 | GCC_WARN_UNUSED_VARIABLE = YES; 414 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 415 | MTL_ENABLE_DEBUG_INFO = NO; 416 | SDKROOT = iphoneos; 417 | SUPPORTED_PLATFORMS = iphoneos; 418 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 419 | TARGETED_DEVICE_FAMILY = "1,2"; 420 | VALIDATE_PRODUCT = YES; 421 | }; 422 | name = Release; 423 | }; 424 | 97C147061CF9000F007C117D /* Debug */ = { 425 | isa = XCBuildConfiguration; 426 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 427 | buildSettings = { 428 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 429 | CLANG_ENABLE_MODULES = YES; 430 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 431 | ENABLE_BITCODE = NO; 432 | FRAMEWORK_SEARCH_PATHS = ( 433 | "$(inherited)", 434 | "$(PROJECT_DIR)/Flutter", 435 | ); 436 | INFOPLIST_FILE = Runner/Info.plist; 437 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 438 | LIBRARY_SEARCH_PATHS = ( 439 | "$(inherited)", 440 | "$(PROJECT_DIR)/Flutter", 441 | ); 442 | PRODUCT_BUNDLE_IDENTIFIER = com.app.example; 443 | PRODUCT_NAME = "$(TARGET_NAME)"; 444 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 445 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 446 | SWIFT_VERSION = 5.0; 447 | VERSIONING_SYSTEM = "apple-generic"; 448 | }; 449 | name = Debug; 450 | }; 451 | 97C147071CF9000F007C117D /* Release */ = { 452 | isa = XCBuildConfiguration; 453 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 454 | buildSettings = { 455 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 456 | CLANG_ENABLE_MODULES = YES; 457 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 458 | ENABLE_BITCODE = NO; 459 | FRAMEWORK_SEARCH_PATHS = ( 460 | "$(inherited)", 461 | "$(PROJECT_DIR)/Flutter", 462 | ); 463 | INFOPLIST_FILE = Runner/Info.plist; 464 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 465 | LIBRARY_SEARCH_PATHS = ( 466 | "$(inherited)", 467 | "$(PROJECT_DIR)/Flutter", 468 | ); 469 | PRODUCT_BUNDLE_IDENTIFIER = com.app.example; 470 | PRODUCT_NAME = "$(TARGET_NAME)"; 471 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 472 | SWIFT_VERSION = 5.0; 473 | VERSIONING_SYSTEM = "apple-generic"; 474 | }; 475 | name = Release; 476 | }; 477 | /* End XCBuildConfiguration section */ 478 | 479 | /* Begin XCConfigurationList section */ 480 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 481 | isa = XCConfigurationList; 482 | buildConfigurations = ( 483 | 97C147031CF9000F007C117D /* Debug */, 484 | 97C147041CF9000F007C117D /* Release */, 485 | 249021D3217E4FDB00AE95B9 /* Profile */, 486 | ); 487 | defaultConfigurationIsVisible = 0; 488 | defaultConfigurationName = Release; 489 | }; 490 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 491 | isa = XCConfigurationList; 492 | buildConfigurations = ( 493 | 97C147061CF9000F007C117D /* Debug */, 494 | 97C147071CF9000F007C117D /* Release */, 495 | 249021D4217E4FDB00AE95B9 /* Profile */, 496 | ); 497 | defaultConfigurationIsVisible = 0; 498 | defaultConfigurationName = Release; 499 | }; 500 | /* End XCConfigurationList section */ 501 | }; 502 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 503 | } 504 | --------------------------------------------------------------------------------